@timurproko/a1 0.1.8-dev.b449b5c → 0.1.8-dev.c64be0e

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 (or a1 update:<commit> for a specific preview)
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,14 +72,15 @@ 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
72
- a1 update:7eabe9e # install the preview built from that commit
73
- a1 update:0.1.8-dev.7eabe9e # or name the version outright
75
+ a1 update:next # install the newest preview
76
+ a1 update:7eabe9e # install the preview built from that commit
74
77
  ```
75
78
 
76
79
  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.
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.
79
84
 
80
85
  ### Stable — npm `latest`
81
86
 
@@ -92,7 +97,7 @@ It lands the version on `develop` through a pull request that merges itself.
92
97
  **Landing it is what publishes** — the workflow builds, validates the packed release
93
98
  on Windows, Linux, and macOS, publishes to npm `latest` with provenance, and only
94
99
  then writes the `v<version>` tag and records the GitHub Release. The command waits
95
- 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
96
101
  immediately.
97
102
 
98
103
  ```sh
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "unsupported",
8
- "builtAt": "2026-08-24T06:35:58.522Z",
8
+ "builtAt": "2026-08-25T08:31:32.388Z",
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:35:49.229Z",
8
+ "builtAt": "2026-08-25T08:31:49.348Z",
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:36:28.685Z",
8
+ "builtAt": "2026-08-25T08:32:11.007Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "1d535ec647852d21736769e0d418b80f9a99d02a9721609a13f251300986b9b9",
11
+ "sha256": "b22997c85da32360cdb382f568e618757654c746cccb825b7c771d011cca9ba6",
12
12
  "size": 172544
13
13
  },
14
14
  "provenance": {
@@ -5,12 +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
10
  "update:<commit>",
11
- "install <source>",
12
- "remove <source>",
13
- "list",
11
+ "pi install <source>",
12
+ "pi remove <source>",
13
+ "pi list",
14
+ "pi update [--extensions|<source>]",
14
15
  ]);
15
16
  }
16
17
  const PROFILE_WORDS = new Set(["pi", "sandbox"]);
@@ -32,7 +33,13 @@ export function parseCliCommand(arguments_, capabilities) {
32
33
  if (arguments_.length === 0)
33
34
  return { kind: "launch", profileId: "a1" };
34
35
  const [command, ...rest] = arguments_;
35
- 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") {
36
43
  return withoutArguments(rest, { kind: "launch", profileId: command });
37
44
  }
38
45
  if (command === "version")
@@ -41,11 +48,9 @@ export function parseCliCommand(arguments_, capabilities) {
41
48
  return parseColonUpdate(command.slice("update:".length), rest);
42
49
  if (command === "update")
43
50
  return parseUpdate(rest);
44
- if (command === "install" || command === "remove" || command === "uninstall") {
45
- return parseSourceCommand(command === "install" ? "install" : "remove", rest);
51
+ if (command === "install" || command === "remove" || command === "uninstall" || command === "list") {
52
+ return packageNamespaceRejection(command, rest.join(" ") || undefined);
46
53
  }
47
- if (command === "list")
48
- return withoutArguments(rest, { kind: "packages", request: { verb: "list", source: null } });
49
54
  if (command === "ui")
50
55
  return { kind: "error", message: `The ui subcommand was removed; run bare ${PRODUCT_TEXT.commandName} for the owned UI.` };
51
56
  if (command === "agent")
@@ -71,10 +76,9 @@ function parseColonUpdate(suffix, rest) {
71
76
  return { kind: "update", channel: "next", target: suffix };
72
77
  }
73
78
  /**
74
- * `update` carries both meanings pinned Pi gives it: itself by default, and the
75
- * profile's packages when a target says so. Pi is refused as a target because A1
76
- * certifies each release against one pinned Pi, so moving Pi underneath it would
77
- * invalidate what was certified.
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.
78
82
  */
79
83
  function parseUpdate(rest) {
80
84
  if (rest.length === 0)
@@ -90,18 +94,42 @@ function parseUpdate(rest) {
90
94
  message: PRODUCT_TEXT.diagnostic(`pins the Pi version it was certified against; run ${PRODUCT_TEXT.commandName} update to move ${PRODUCT_TEXT.displayName} itself.`),
91
95
  };
92
96
  }
93
- // A release channel is spelled with the colon. Taking the bare word as a package
94
- // source would turn a near miss into a confident search for a package nobody has.
95
97
  if (target === "next" || target === "stable") {
96
98
  const form = target === "next" ? `${PRODUCT_TEXT.commandName} update:next` : `${PRODUCT_TEXT.commandName} update`;
97
99
  return { kind: "error", message: PRODUCT_TEXT.diagnostic(`selects a release channel with a colon; run ${form}.`) };
98
100
  }
99
- if (target === "--extensions")
100
- return { kind: "packages", request: { verb: "update", source: null } };
101
101
  if (target === "--models")
102
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
+ }
103
131
  if (target === undefined || target.startsWith("-"))
104
- return unknownOption(target ?? "", "update");
132
+ return unknownOption(target ?? "", "pi update");
105
133
  if (PROFILE_WORDS.has(target))
106
134
  return profileRejection("update");
107
135
  return { kind: "packages", request: { verb: "update", source: target } };
@@ -129,6 +157,13 @@ function withoutArguments(rest, command) {
129
157
  return profileRejection("list");
130
158
  return { kind: "error", message: PRODUCT_TEXT.diagnostic("commands do not accept additional arguments.") };
131
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
+ }
132
167
  function profileRejection(verb) {
133
168
  return {
134
169
  kind: "error",
@@ -11,6 +11,12 @@ export interface PackageCommandRequest {
11
11
  readonly verb: PackageCommandVerb;
12
12
  readonly source: string | null;
13
13
  }
14
+ export interface PackageCommandStyle {
15
+ readonly dim: (message: string) => string;
16
+ readonly bold: (message: string) => string;
17
+ readonly green: (message: string) => string;
18
+ readonly red: (message: string) => string;
19
+ }
14
20
  export interface PackageCommandEnvironment {
15
21
  readonly createPort: (input: AgentPackagesPortInput) => AgentPackagesPort;
16
22
  readonly cwd?: string;
@@ -18,6 +24,8 @@ export interface PackageCommandEnvironment {
18
24
  readonly stdout?: (message: string) => void;
19
25
  readonly stderr?: (message: string) => void;
20
26
  readonly initializeProfile?: typeof initializeProductProfile;
27
+ /** Defaults to Chalk's terminal-aware styles; injectable for transcript tests. */
28
+ readonly style?: PackageCommandStyle;
21
29
  }
22
30
  export declare function runPackageCommand(request: PackageCommandRequest, environment: PackageCommandEnvironment): Promise<number>;
23
- export declare function renderPackageOutcome(outcome: AgentPackageOutcome, profileRoot: string): string;
31
+ export declare function renderPackageOutcome(outcome: AgentPackageOutcome, profileRoot: string, style?: PackageCommandStyle): string;
@@ -1,3 +1,4 @@
1
+ import chalk from "chalk";
1
2
  import { configurationRootForProfile, initializeProductProfile, resolveLaunchProfilePaths } from "../features/launch/index.js";
2
3
  import { PRODUCT_TEXT } from "../product-identity.js";
3
4
  export async function runPackageCommand(request, environment) {
@@ -5,6 +6,7 @@ export async function runPackageCommand(request, environment) {
5
6
  const stderr = environment.stderr ?? (message => process.stderr.write(message));
6
7
  const cwd = environment.cwd ?? process.cwd();
7
8
  const processEnvironment = environment.environment ?? process.env;
9
+ const style = environment.style ?? chalk;
8
10
  const paths = resolveLaunchProfilePaths({ environment: processEnvironment });
9
11
  const profileRoot = configurationRootForProfile("a1", paths);
10
12
  if (profileRoot === null)
@@ -19,10 +21,10 @@ export async function runPackageCommand(request, environment) {
19
21
  const port = environment.createPort({
20
22
  profileRoot,
21
23
  cwd,
22
- onProgress: progress => stdout(`${progress.message}\n`),
24
+ onProgress: progress => stdout(`${style.dim(progress.message)}\n`),
23
25
  });
24
26
  const outcome = await runVerb(port, request);
25
- const rendered = renderPackageOutcome(outcome, profileRoot);
27
+ const rendered = renderPackageOutcome(outcome, profileRoot, style);
26
28
  (outcome.status === "completed" ? stdout : stderr)(rendered);
27
29
  return outcome.status === "completed" ? 0 : 1;
28
30
  }
@@ -37,38 +39,40 @@ async function runVerb(port, request) {
37
39
  throw new Error(PRODUCT_TEXT.diagnostic(`requires a source for ${request.verb}`));
38
40
  return request.verb === "install" ? await port.install(request.source) : await port.remove(request.source);
39
41
  }
40
- export function renderPackageOutcome(outcome, profileRoot) {
41
- const name = PRODUCT_TEXT.displayName;
42
+ export function renderPackageOutcome(outcome, profileRoot, style = chalk) {
43
+ // Model refresh remains an A1 top-level command. The `a1 pi` compatibility
44
+ // transcript applies to package operations only.
45
+ if (outcome.operation === "refresh-models") {
46
+ if (outcome.status === "failed") {
47
+ return `${PRODUCT_TEXT.diagnostic(`could not ${describeOperation(outcome.operation)}: ${outcome.detail ?? "unknown failure"}`)}\n`;
48
+ }
49
+ return `${PRODUCT_TEXT.displayName} refreshed the model catalogs in ${profileRoot}.\n`;
50
+ }
42
51
  if (outcome.status === "failed") {
43
- return `${PRODUCT_TEXT.diagnostic(`could not ${describeOperation(outcome.operation)}: ${outcome.detail ?? "unknown failure"}`)}\n`;
52
+ return `${style.red(`Error: ${outcome.detail ?? "Unknown package command error"}`)}\n`;
44
53
  }
45
54
  if (outcome.status === "not-found") {
46
- return `${PRODUCT_TEXT.diagnostic(`found no package matching ${outcome.source ?? "that source"} in ${profileRoot}.`)}\n`;
55
+ return `${style.red(`No matching package found for ${outcome.source ?? "that source"}`)}\n`;
47
56
  }
48
57
  switch (outcome.operation) {
49
58
  case "install":
50
- return `${name} installed ${outcome.source} into ${profileRoot}.\n`
51
- + `Restart ${PRODUCT_TEXT.commandName} for a running session to load it.\n`;
59
+ return `${style.green(`Installed ${outcome.source}`)}\n`;
52
60
  case "remove":
53
- return `${name} removed ${outcome.source} from ${profileRoot}.\n`;
61
+ return `${style.green(`Removed ${outcome.source}`)}\n`;
54
62
  case "update":
55
- return outcome.source === null
56
- ? `${name} updated the packages in ${profileRoot}.\n`
57
- : `${name} updated ${outcome.source}.\n`;
58
- case "refresh-models":
59
- return `${name} refreshed the model catalogs in ${profileRoot}.\n`;
63
+ return `${style.green(outcome.source === null ? "Updated packages" : `Updated ${outcome.source}`)}\n`;
60
64
  case "list":
61
- return renderPackageList(outcome, profileRoot);
65
+ return renderPackageList(outcome, style);
62
66
  }
63
67
  }
64
- function renderPackageList(outcome, profileRoot) {
68
+ function renderPackageList(outcome, style) {
65
69
  if (outcome.packages.length === 0)
66
- return `${PRODUCT_TEXT.displayName} has no packages installed in ${profileRoot}.\n`;
67
- const lines = [`Packages installed for ${PRODUCT_TEXT.commandName} in ${profileRoot}:`];
70
+ return `${style.dim("No packages installed.")}\n`;
71
+ const lines = [style.bold("User packages:")];
68
72
  for (const entry of outcome.packages) {
69
- lines.push(` ${entry.source}${entry.filtered ? " (partly enabled)" : ""}`);
73
+ lines.push(` ${entry.source}${entry.filtered ? " (filtered)" : ""}`);
70
74
  if (entry.installedPath !== null)
71
- lines.push(` ${entry.installedPath}`);
75
+ lines.push(style.dim(` ${entry.installedPath}`));
72
76
  }
73
77
  return `${lines.join("\n")}\n`;
74
78
  }
@@ -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;