@timurproko/a1 0.1.8-dev.99e912e → 0.1.8-dev.ade9b77

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
@@ -30,16 +30,20 @@ Pi extension packages install into A1's own profile, so bare `a1` loads them and
30
30
  `a1 pi` and `a1 sandbox` do not. Sources are Pi's: `npm:`, git, or a local path.
31
31
 
32
32
  ```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
33
+ a1 pi install npm:pi-mcp-adapter # install a package into ~/.a1/agent
34
+ a1 pi remove npm:pi-mcp-adapter # remove it again (alias: a1 pi uninstall)
35
+ a1 pi list # list packages installed for a1
36
+ a1 pi update --extensions # update every installed package
37
+ a1 pi update npm:pi-mcp-adapter # update one of them
38
+ a1 update --models # refresh A1's model catalogs
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
 
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "unsupported",
8
- "builtAt": "2026-08-24T07:36:29.445Z",
8
+ "builtAt": "2026-08-25T06:52:40.013Z",
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-24T07:36:31.707Z",
8
+ "builtAt": "2026-08-25T06:52:58.359Z",
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-24T07:36:58.529Z",
8
+ "builtAt": "2026-08-25T06:54:04.992Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "c8491147088aa02f51443ecef1aa9f9e36914f9c899e221668bb3a36d9140941",
11
+ "sha256": "b8b0c81f275bd95b0cf4a63dd19f734a845947e47feded1d85a7a3562cf072db",
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",
@@ -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)));
@@ -143,7 +143,7 @@ export class PiEngineAdapter {
143
143
  if (this.#disposed || updates.length === 0)
144
144
  return;
145
145
  const packages = updates.map(name => `- ${name}`).join("\n");
146
- this.#addDiagnostic("info", "package-updates", `Package updates are available. Run pi update --extensions\nPackages:\n${packages}`, true);
146
+ this.#addDiagnostic("info", "package-updates", `Package updates are available. Run ${PRODUCT_IDENTITY.commandName} pi update --extensions\nPackages:\n${packages}`, true);
147
147
  this.#emitView();
148
148
  }
149
149
  onEvent(listener) {
@@ -373,9 +373,10 @@ export class PiEngineAdapter {
373
373
  }
374
374
  const extensionCommands = this.#session?.extensionRunner?.getRegisteredCommands?.();
375
375
  if (Array.isArray(extensionCommands)) {
376
- for (const command of extensionCommands.filter(isRecord)) {
377
- const name = stringProperty(command, "name");
378
- if (!name || usedNames.has(name))
376
+ const registered = extensionCommands.filter(isRecord);
377
+ for (const command of registered) {
378
+ const name = stringProperty(command, "invocationName") ?? stringProperty(command, "name");
379
+ if (!name || usedNames.has(name) || isPiPrefixedCompatibilityAlias(command, registered))
379
380
  continue;
380
381
  commands.push({ name, description: stringProperty(command, "description") ?? "Extension command", source: "extension" });
381
382
  usedNames.add(name);
@@ -2274,6 +2275,26 @@ function stringProperty(value, key) {
2274
2275
  const item = value[key];
2275
2276
  return typeof item === "string" && item.length > 0 ? item : undefined;
2276
2277
  }
2278
+ /**
2279
+ * Some ecosystem extensions retain a `pi-<name>` slash-command alias beside
2280
+ * their unprefixed command. A1 presents the product-neutral command once while
2281
+ * leaving Pi's runner free to accept the compatibility alias when typed.
2282
+ */
2283
+ function isPiPrefixedCompatibilityAlias(command, commands) {
2284
+ const name = stringProperty(command, "name");
2285
+ if (!name?.startsWith("pi-") || name.length === 3)
2286
+ return false;
2287
+ const canonicalName = name.slice(3);
2288
+ const description = stringProperty(command, "description");
2289
+ const sourcePath = extensionCommandSourcePath(command);
2290
+ return commands.some(candidate => candidate !== command
2291
+ && stringProperty(candidate, "name") === canonicalName
2292
+ && stringProperty(candidate, "description") === description
2293
+ && extensionCommandSourcePath(candidate) === sourcePath);
2294
+ }
2295
+ function extensionCommandSourcePath(command) {
2296
+ return isRecord(command) ? stringProperty(command.sourceInfo, "path") : undefined;
2297
+ }
2277
2298
  function compactResourceLabel(path) {
2278
2299
  const segments = path.replaceAll("\\", "/").split("/").filter(Boolean);
2279
2300
  return segments.at(-1) ?? path;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.1.8-dev.99e912e",
3
+ "version": "0.1.8-dev.ade9b77",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.13.0",