@timurproko/a1 0.1.8-dev.563 → 0.1.8-dev.572

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.
Files changed (34) hide show
  1. package/README.md +13 -11
  2. package/dist/app/session-shell/session-shell-root.js +6 -3
  3. package/dist/app/session-shell/session-shell.js +7 -1
  4. package/dist/app/session-shell/session-viewport-controller.js +48 -30
  5. package/dist/cli/dispatch.d.ts +3 -1
  6. package/dist/cli/dispatch.js +56 -56
  7. package/dist/cli/package-messages.d.ts +4 -3
  8. package/dist/cli/package-messages.js +23 -11
  9. package/dist/integrations/pi/components/shell-footer-status.d.ts +4 -3
  10. package/dist/integrations/pi/components/shell-footer-status.js +53 -15
  11. package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -1
  12. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +2 -2
  13. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +3 -2
  14. package/dist/integrations/pi/engine/compaction-progress.d.ts +1 -1
  15. package/dist/integrations/pi/engine/compaction-progress.js +1 -0
  16. package/dist/integrations/pi/engine/repository-pr.d.ts +3 -3
  17. package/dist/integrations/pi/engine/repository-pr.js +6 -6
  18. package/dist/integrations/pi/engine/session-runtime.js +5 -4
  19. package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
  20. package/dist/integrations/pi/tui-runtime/adapter.js +5 -0
  21. package/dist/native/darwin-arm64/manifest.json +1 -1
  22. package/dist/native/linux-x64/manifest.json +1 -1
  23. package/dist/native/win32-x64/manifest.json +3 -3
  24. package/dist/native/win32-x64/process-guardian.exe +0 -0
  25. package/dist/ui/components/index.d.ts +2 -1
  26. package/dist/ui/components/index.js +1 -1
  27. package/dist/ui/components/progress-status.d.ts +9 -2
  28. package/dist/ui/components/progress-status.js +24 -3
  29. package/dist/ui/components/transcript-viewport.d.ts +9 -3
  30. package/dist/ui/components/transcript-viewport.js +90 -132
  31. package/docs/architecture/ui-reference-provenance.md +8 -5
  32. package/docs/openspec-archive-automation.md +2 -2
  33. package/docs/validation.md +1 -1
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -24,13 +24,15 @@ npm install -g @timurproko/a1@0.1.8-dev.107
24
24
 
25
25
  ```sh
26
26
  a1 # launch A1 (profile: ~/.a1/agent)
27
- a1 --help # show all commands (short form: a1 -h)
28
- a1 --version # show the version (short form: a1 -v)
27
+ a1 help # show all commands
28
+ a1 version # show the version
29
29
  a1 update # install the newest stable release
30
30
  a1 update --develop # install the current development preview
31
31
  a1 update --develop 107 # install numbered preview 107
32
32
  a1 update --develop 0.1.8-dev.107 # install that exact preview
33
33
  a1 update --models # refresh A1's model catalogs
34
+ a1 update --extensions # update every installed package
35
+ a1 update npm:pi-mcp-adapter # update one package
34
36
  ```
35
37
 
36
38
  Stable builds print only their installed version. Development builds also show the
@@ -47,17 +49,17 @@ Unsupported commands exit quietly without launching anything. The removed
47
49
 
48
50
  ## Extensions
49
51
 
50
- Pi extension packages install into A1's own profile (`~/.a1/agent`), so bare `a1`
51
- loads them and `a1 pi` does not. Sources are Pi's: `npm:`, git, or a
52
- local path.
52
+ Pi-compatible extension packages install into A1's own profile (`~/.a1/agent`), so
53
+ bare `a1` loads them and the `a1 pi` comparison profile does not. Sources use Pi's
54
+ `npm:`, git, or local-path grammar.
53
55
 
54
56
  ```sh
55
- a1 pi install npm:pi-mcp-adapter # install a package
56
- a1 pi remove npm:pi-mcp-adapter # remove it (alias: a1 pi uninstall)
57
- a1 pi list # list installed packages
58
- a1 pi update --extensions # update every installed package
59
- a1 pi update npm:pi-mcp-adapter # update one
60
- a1 pi update --models # refresh A1's model catalogs
57
+ a1 install npm:pi-mcp-adapter # install a package
58
+ a1 remove npm:pi-mcp-adapter # remove it (alias: a1 uninstall)
59
+ a1 list # list installed packages
60
+ a1 update --extensions # update every installed package
61
+ a1 update npm:pi-mcp-adapter # update one
62
+ a1 update --models # refresh A1's model catalogs
61
63
  ```
62
64
 
63
65
  A1 pins the Pi runtime carried by each release, so Pi self-update forms are refused.
@@ -3,7 +3,7 @@ import { caretCell } from "../../ui/components/line-input.js";
3
3
  import { PromptInput, promptArrow } from "../../ui/components/prompt-input.js";
4
4
  import { composeSubmittedPromptRows, formatSubmittedPromptTime, submittedPromptLayout, } from "../../ui/components/submitted-prompt.js";
5
5
  import { backgroundSgrSpan, heldNativeHyperlinkStyle, hyperlinkSgrSpan, nativeHyperlinkStyle, overlaySpan, } from "../../ui/components/spans.js";
6
- import { progressStatusText } from "../../ui/components/progress-status.js";
6
+ import { progressStatusFrame, progressStatusText } from "../../ui/components/progress-status.js";
7
7
  import { displayWidth, faint, stripAnsi } from "../../ui/components/text.js";
8
8
  import { PINNED_PI_HIDDEN_COMMAND_NAMES, PINNED_PI_WORKFLOW_COMMAND_NAMES } from "../../integrations/pi/engine/workflows.js";
9
9
  import { createPiExtensionUiBridge } from "../../integrations/pi/components/shell-extension-ui.js";
@@ -106,10 +106,12 @@ export class OwnedUiSessionShellRoot {
106
106
  ...(this.#customViewport ? { getKeybindings: () => this.editor.keybindingConfig() } : {}),
107
107
  });
108
108
  this.resources = createPiShellLoadedResources(startup.resources ?? [], startup.expanded ?? false);
109
- this.#status = createPiShellStatus(view, progressStatusText, handlers);
109
+ this.#status = createPiShellStatus(view, {
110
+ text: (message, mode) => progressStatusText(message, mode === "custom-viewport" ? "…" : "..."),
111
+ frame: progressStatusFrame,
112
+ }, handlers);
110
113
  this.#status.setProgressPresentation(this.#customViewport ? "custom-viewport" : "pinned");
111
114
  this.#footer = createPiShellFooter(this.#viewWithExtensionStatuses(view), cwd, this.#customViewport ? "a1" : "pi", () => this.#footerLevel);
112
- this.#queued = createPiQueuedInputStatus(view.editor.queuedSubmissions, this.#customViewport ? "custom-viewport" : "pinned");
113
115
  this.editor = createPiShellEditor({
114
116
  ...handlers,
115
117
  keybindingProfile: this.#customViewport ? "a1" : "pi",
@@ -183,6 +185,7 @@ export class OwnedUiSessionShellRoot {
183
185
  },
184
186
  ...(handlers.onPromptSuggestionAccepted === undefined ? {} : { onPromptSuggestionAccepted: handlers.onPromptSuggestionAccepted }),
185
187
  });
188
+ this.#queued = createPiQueuedInputStatus(view.editor.queuedSubmissions, this.#customViewport ? "custom-viewport" : "pinned", this.#customViewport ? () => this.editor.keybindingConfig() : undefined);
186
189
  this.#viewportController = new SessionViewportController({
187
190
  enabled: this.#customViewport,
188
191
  editor: this.editor,
@@ -356,7 +356,13 @@ export class OwnedUiSessionShell {
356
356
  }
357
357
  const routed = this.root.handleViewportPreInput(data, true, Date.now(), this.root.usesDefaultInputSurface() && !this.runtime.hasFocusedOverlay());
358
358
  if (routed.copySelection !== undefined) {
359
- void this.#responseCopy?.submit(routed.copySelection, pendingClipboardWrite);
359
+ const snapshot = routed.copySelection;
360
+ void this.#responseCopy?.submit(snapshot, pendingClipboardWrite).then(result => {
361
+ if (this.#disposed || !runtime.active
362
+ || result.outcome !== "delivered" && result.outcome !== "submitted-unverified")
363
+ return;
364
+ runtime.showFlash(`Copied ${snapshot.sourceUnits} character${snapshot.sourceUnits === 1 ? "" : "s"} to clipboard`);
365
+ });
360
366
  }
361
367
  if (!routed.consumed)
362
368
  return routed.data === data ? undefined : { data: routed.data };
@@ -28,12 +28,14 @@ export class SessionViewportController {
28
28
  #inputSurfacePending = false;
29
29
  #overlays = [];
30
30
  #pointerOwner;
31
- // Invariant: a left-button sequence begun in dock chrome remains owned by the dock.
31
+ // Invariant: a right-click editor gesture remains owned by the dock.
32
32
  #dockPointerSuppressed = false;
33
- // Invariant: a left-button sequence begun in non-selectable transcript tail chrome is held there.
33
+ // Invariant: a gesture begun on an explicit control remains owned by that control.
34
34
  #tailPointerSuppressed = false;
35
35
  // Invariant: editor selection remains true only while its left button is held.
36
36
  #editorPointerSelecting = false;
37
+ // Invariant: no-drag editor presses replay on release; distinct motion promotes frame selection.
38
+ #pendingEditorClick;
37
39
  // Invariant: pointer routing uses the most recently composed editor rows.
38
40
  #editorPointerFrame;
39
41
  #selectionAutoScrollTimer;
@@ -112,9 +114,19 @@ export class SessionViewportController {
112
114
  this.#cancelGesture();
113
115
  this.#viewport.setConfig(this.#config);
114
116
  const selectionRevision = this.#viewport.selectionRevision;
117
+ const coveredSurfaces = [
118
+ ...(this.#overlays ?? []),
119
+ ...(this.#inputSurface === undefined ? [] : [this.#inputSurface]),
120
+ ];
121
+ const coveredCells = coveredSurfaces.flatMap(surface => Array.from({ length: Math.max(0, surface.rowEnd - surface.rowStart + 1) }, (_value, index) => ({
122
+ row: surface.rowStart + index,
123
+ columnStart: surface.columnStart,
124
+ columnEnd: surface.columnEnd,
125
+ })));
115
126
  const frame = this.#viewport.compose({
116
127
  ...input,
117
128
  ...(this.#pointerPosition === undefined ? {} : { pointerPosition: this.#pointerPosition }),
129
+ ...(coveredCells.length === 0 ? {} : { coveredCells }),
118
130
  });
119
131
  // Concurrency: if selection changed during composition, this frame truthfully carries
120
132
  // the older revision and exactly one ordinary follow-up render publishes the latest.
@@ -167,6 +179,7 @@ export class SessionViewportController {
167
179
  this.#clearActivityTimer();
168
180
  this.#stopSelectionAutoScroll();
169
181
  this.#editorPointerSelecting = false;
182
+ this.#pendingEditorClick = undefined;
170
183
  this.#editorPointerFrame = undefined;
171
184
  this.#pointerPosition = undefined;
172
185
  this.#hoveredHyperlinkKey = undefined;
@@ -183,6 +196,7 @@ export class SessionViewportController {
183
196
  this.#dockPointerSuppressed = false;
184
197
  this.#tailPointerSuppressed = false;
185
198
  this.#editorPointerSelecting = false;
199
+ this.#pendingEditorClick = undefined;
186
200
  this.#editorPointerFrame = undefined;
187
201
  this.#pointerPosition = undefined;
188
202
  this.#hoveredHyperlinkKey = undefined;
@@ -302,6 +316,7 @@ export class SessionViewportController {
302
316
  let repaint = false;
303
317
  let forceRepaint = false;
304
318
  let activity = false;
319
+ let completedCopy;
305
320
  const routed = routeMouseInput(data, (event, report) => {
306
321
  const hits = frame.hits;
307
322
  const overModal = this.#modalAt(event.column, event.row);
@@ -375,6 +390,10 @@ export class SessionViewportController {
375
390
  return true;
376
391
  if (this.#viewport.selectionActive) {
377
392
  this.#viewport.extendSelection(event.column, event.row, now, false);
393
+ if (this.#pendingEditorClick !== undefined
394
+ && (event.column !== this.#pendingEditorClick.column || event.row !== this.#pendingEditorClick.row)) {
395
+ this.#pendingEditorClick = undefined;
396
+ }
378
397
  this.#updateSelectionAutoScroll(event.column, event.row, hits.viewportHeight);
379
398
  activity = true;
380
399
  return true;
@@ -415,17 +434,20 @@ export class SessionViewportController {
415
434
  return false;
416
435
  this.#dockPointerSuppressed = false;
417
436
  this.#tailPointerSuppressed = false;
437
+ this.#pendingEditorClick = undefined;
418
438
  this.#stopSelectionAutoScroll();
419
439
  if (this.#viewport.clearSelection())
420
440
  repaint = true;
421
441
  if (overBottom) {
422
442
  this.#viewport.scrollToEnd(now);
443
+ this.#tailPointerSuppressed = true;
423
444
  activity = true;
424
445
  repaint = true;
425
446
  return true;
426
447
  }
427
448
  if (overSticky && hits.sticky !== null) {
428
449
  this.#viewport.scrollTo(hits.sticky.target, now);
450
+ this.#tailPointerSuppressed = true;
429
451
  activity = true;
430
452
  repaint = true;
431
453
  return true;
@@ -439,35 +461,19 @@ export class SessionViewportController {
439
461
  }
440
462
  else {
441
463
  this.#viewport.scrollTo(scrollForTrackPage(geometry, trackRow, frame.scrollTop, hits.viewportHeight), now);
464
+ this.#tailPointerSuppressed = true;
442
465
  }
443
466
  activity = true;
444
467
  repaint = true;
445
468
  return true;
446
469
  }
447
- if (editorFrame !== undefined && event.row >= editorFrame.rowStart && event.row <= editorFrame.rowEnd) {
448
- const handled = this.#editor.handlePointer({
449
- kind: "press",
450
- button: event.button,
451
- column: event.column,
452
- row: event.row - editorFrame.rowStart + 1,
453
- });
454
- if (!handled)
455
- this.#dockPointerSuppressed = true;
456
- this.#editorPointerSelecting = handled;
457
- repaint = true;
458
- return true;
459
- }
460
- if (event.row > hits.viewportHeight) {
461
- this.#dockPointerSuppressed = true;
462
- return true;
463
- }
464
- if (event.row >= 1 && event.row <= hits.viewportHeight && event.column <= frame.contentWidth) {
465
- if (!this.#viewport.pressSelection(event.column, event.row, now)) {
466
- // Invariant: empty rows and transient tail rows must never start Pi selection.
467
- this.#tailPointerSuppressed = true;
468
- repaint = true;
469
- return true;
470
+ if (event.row >= 1 && event.row <= frame.rows.length && event.column <= frame.descriptor.width) {
471
+ if (editorFrame !== undefined && event.row >= editorFrame.rowStart && event.row <= editorFrame.rowEnd) {
472
+ this.#pendingEditorClick = { column: event.column, row: event.row };
470
473
  }
474
+ // Invariant: frame selection includes transient and dock rows without changing
475
+ // semantic ownership; no-drag editor presses replay on release.
476
+ this.#viewport.pressSelection(event.column, event.row, now);
471
477
  repaint = true;
472
478
  return true;
473
479
  }
@@ -490,6 +496,17 @@ export class SessionViewportController {
490
496
  }
491
497
  if (this.#viewport.releaseSelection()) {
492
498
  this.#stopSelectionAutoScroll();
499
+ const pendingEditorClick = this.#pendingEditorClick;
500
+ this.#pendingEditorClick = undefined;
501
+ if (pendingEditorClick !== undefined && this.#editorPointerFrame !== undefined) {
502
+ this.#viewport.clearSelection();
503
+ const editorRow = pendingEditorClick.row - this.#editorPointerFrame.rowStart + 1;
504
+ this.#editor.handlePointer({ kind: "press", button: 0, column: pendingEditorClick.column, row: editorRow });
505
+ this.#editor.handlePointer({ kind: "release", button: 0, column: pendingEditorClick.column, row: editorRow });
506
+ }
507
+ else {
508
+ completedCopy = this.#viewport.captureSelectedText() ?? undefined;
509
+ }
493
510
  // Platform: restore OSC 8 links only after the held-button selection paint has
494
511
  // ended, then overwrite any terminal-cached hover cells immediately.
495
512
  forceRepaint = true;
@@ -519,7 +536,7 @@ export class SessionViewportController {
519
536
  this.#lastRequestedSelectionRevision = this.#viewport.selectionRevision;
520
537
  this.#requestRender(forceRepaint);
521
538
  }
522
- return routed;
539
+ return completedCopy === undefined ? routed : { ...routed, copySelection: completedCopy };
523
540
  }
524
541
  #modalAt(column, row) {
525
542
  const contains = (surface) => column >= surface.columnStart && column <= surface.columnEnd
@@ -534,13 +551,13 @@ export class SessionViewportController {
534
551
  .find(candidate => column - 1 >= candidate.from && column - 1 < candidate.to);
535
552
  return range === undefined ? undefined : JSON.stringify([row, range]);
536
553
  }
537
- #updateSelectionAutoScroll(column, row, viewportHeight) {
538
- const beyondEdge = row <= 1 || row > viewportHeight;
554
+ #updateSelectionAutoScroll(column, row, frameHeight) {
555
+ const beyondEdge = row <= 1 || row >= frameHeight;
539
556
  if (!beyondEdge) {
540
557
  this.#stopSelectionAutoScroll();
541
558
  return;
542
559
  }
543
- this.#selectionAutoScrollPointer = { column, row };
560
+ this.#selectionAutoScrollPointer = { column, row, direction: row <= 1 ? -1 : 1 };
544
561
  if (this.#selectionAutoScrollTimer !== undefined)
545
562
  return;
546
563
  this.#selectionAutoScrollTimer = setTimeout(() => this.#selectionAutoScrollTick(), SELECTION_AUTO_SCROLL_INTERVAL_MS);
@@ -557,7 +574,8 @@ export class SessionViewportController {
557
574
  const rowsPerTick = scrollbarSelectionRows(this.#config.scrollbarSpeed);
558
575
  for (let row = 0; row < rowsPerTick; row += 1) {
559
576
  const previous = this.#viewport.scrollTop;
560
- this.#viewport.extendSelection(pointer.column, pointer.row);
577
+ this.#viewport.scrollBy(pointer.direction);
578
+ this.#viewport.extendSelection(pointer.column, pointer.row, undefined, false);
561
579
  if (this.#viewport.scrollTop === previous)
562
580
  break;
563
581
  }
@@ -2,7 +2,7 @@ import { type InteractiveLaunchIntent, type LaunchProfileId, type SessionSelecti
2
2
  import type { CliCapabilities } from "./capabilities.js";
3
3
  import type { PackageCommandRequest } from "./packages.js";
4
4
  import type { SessionContextRequest } from "./session-context.js";
5
- import { type PackageCliVerb, type PackageSyntaxDiagnostic } from "./package-messages.js";
5
+ import { type PackageCliVerb, type PackageCommandNamespace, type PackageSyntaxDiagnostic } from "./package-messages.js";
6
6
  export type UpdateChannel = "stable" | "next";
7
7
  export interface CliHandlers {
8
8
  readonly launch: (intent: InteractiveLaunchIntent) => Promise<number>;
@@ -25,9 +25,11 @@ export type CliCommand = {
25
25
  } | {
26
26
  readonly kind: "package-help";
27
27
  readonly verb: PackageCliVerb;
28
+ readonly namespace: PackageCommandNamespace;
28
29
  } | {
29
30
  readonly kind: "package-error";
30
31
  readonly diagnostic: PackageSyntaxDiagnostic;
32
+ readonly namespace: PackageCommandNamespace;
31
33
  } | {
32
34
  readonly kind: "launch";
33
35
  readonly profileId: LaunchProfileId;
@@ -1,5 +1,5 @@
1
1
  import { interactiveLaunchIntent, parseSessionSelection } from "../features/launch/index.js";
2
- import { packageCommandHelp, renderPackageSyntax } from "./package-messages.js";
2
+ import { packageCommandHelp, renderPackageSyntax, } from "./package-messages.js";
3
3
  import { PRODUCT_TEXT } from "../product-identity.js";
4
4
  export function cliUsage(capabilities) {
5
5
  return PRODUCT_TEXT.usage([
@@ -7,22 +7,19 @@ export function cliUsage(capabilities) {
7
7
  "--session <path|id>",
8
8
  "--session-dir <dir> --session <path|id>",
9
9
  ...(capabilities.developmentComparison ? ["pi"] : []),
10
- "--help",
11
- "-h",
12
- "--version",
13
- "-v",
10
+ "help",
11
+ "version",
14
12
  "update",
15
13
  "update --develop [preview-or-version]",
16
14
  "update --models",
15
+ "update --extensions",
16
+ "update <source>",
17
+ "install <source>",
18
+ "remove <source>",
19
+ "uninstall <source>",
20
+ "list",
17
21
  "session link-worktree <path>",
18
22
  "session unlink-worktree",
19
- "pi install <source>",
20
- "pi remove <source>",
21
- "pi uninstall <source>",
22
- "pi list",
23
- "pi update --extensions",
24
- "pi update --models",
25
- "pi update <source>",
26
23
  ]);
27
24
  }
28
25
  export function cliHelp(capabilities) {
@@ -33,29 +30,26 @@ export function cliHelp(capabilities) {
33
30
  ` ${command} --session <path|id>`,
34
31
  ` ${command} --session-dir <dir> --session <path|id>`,
35
32
  ...(capabilities.developmentComparison ? [` ${command} pi`] : []),
36
- ` ${command} --help`,
37
- ` ${command} -h`,
38
- ` ${command} --version`,
39
- ` ${command} -v`,
33
+ ` ${command} help`,
34
+ ` ${command} version`,
40
35
  "",
41
36
  "Update:",
42
37
  ` ${command} update`,
43
38
  ` ${command} update --develop [preview-or-version]`,
44
39
  ` ${command} update --models`,
40
+ ` ${command} update --extensions`,
41
+ ` ${command} update <source>`,
42
+ "",
43
+ "Packages:",
44
+ ` ${command} install <source>`,
45
+ ` ${command} remove <source>`,
46
+ ` ${command} uninstall <source>`,
47
+ ` ${command} list`,
45
48
  "",
46
49
  "Session context:",
47
50
  ` ${command} session link-worktree <path>`,
48
51
  ` ${command} session unlink-worktree`,
49
52
  "",
50
- "Pi-compatible packages:",
51
- ` ${command} pi install <source>`,
52
- ` ${command} pi remove <source>`,
53
- ` ${command} pi uninstall <source>`,
54
- ` ${command} pi list`,
55
- ` ${command} pi update --extensions`,
56
- ` ${command} pi update --models`,
57
- ` ${command} pi update <source>`,
58
- "",
59
53
  ].join("\n");
60
54
  }
61
55
  const PROFILE_WORDS = new Set(["pi"]);
@@ -68,11 +62,11 @@ export async function dispatchCli(arguments_, handlers, output, capabilities) {
68
62
  return 0;
69
63
  }
70
64
  if (command.kind === "package-help") {
71
- output.stdout(packageCommandHelp(command.verb));
65
+ output.stdout(packageCommandHelp(command.verb, command.namespace));
72
66
  return 0;
73
67
  }
74
68
  if (command.kind === "package-error") {
75
- output.stderr(renderPackageSyntax(command.diagnostic));
69
+ output.stderr(renderPackageSyntax(command.diagnostic, command.namespace));
76
70
  return 2;
77
71
  }
78
72
  if (command.kind === "error") {
@@ -102,13 +96,16 @@ export function parseCliCommand(arguments_, capabilities) {
102
96
  return { kind: "error", message: PRODUCT_TEXT.diagnostic(`could not parse session launch: ${error instanceof Error ? error.message : String(error)}`) };
103
97
  }
104
98
  }
105
- if (command === "--help" || command === "-h")
99
+ if (command === "help" || command === "--help" || command === "-h")
106
100
  return withoutArguments(rest, { kind: "help" });
107
- if (command === "--version" || command === "-v")
101
+ if (command === "version" || command === "--version" || command === "-v")
108
102
  return withoutArguments(rest, { kind: "version" });
103
+ if (command === "install" || command === "remove" || command === "uninstall" || command === "list") {
104
+ return parsePackageCommand(arguments_, "direct");
105
+ }
109
106
  if (command === "pi") {
110
107
  if (rest.length > 0)
111
- return parsePiPackageCommand(rest);
108
+ return parsePackageCommand(rest, "pi");
112
109
  return capabilities.developmentComparison ? { kind: "launch", profileId: "pi" } : { kind: "noop" };
113
110
  }
114
111
  if (command === "update")
@@ -143,6 +140,9 @@ function parseSessionContext(rest) {
143
140
  * operation that cannot be combined with either update channel.
144
141
  */
145
142
  function parseUpdate(rest) {
143
+ if (rest.some(value => value === "--help" || value === "-h")) {
144
+ return { kind: "package-help", verb: "update", namespace: "direct" };
145
+ }
146
146
  if (rest.length === 0)
147
147
  return { kind: "update", channel: "stable" };
148
148
  const [selector, ...values] = rest;
@@ -167,10 +167,9 @@ function parseUpdate(rest) {
167
167
  }
168
168
  if (selector === "pi")
169
169
  return pinnedPiUpdateError();
170
- if (selector !== undefined && selector.startsWith("-"))
170
+ if (selector === "--self" || selector === "--all")
171
171
  return unknownOption(selector, "update");
172
- // Rationale: positional update targets are reserved for a future A1-native plugin model.
173
- return { kind: "noop" };
172
+ return parsePackageUpdate(rest, "direct");
174
173
  }
175
174
  function isDevelopmentTarget(target) {
176
175
  return /^[1-9]\d*$/.test(target) || /^\d+\.\d+\.\d+-dev\.[1-9]\d*$/.test(target);
@@ -178,48 +177,49 @@ function isDevelopmentTarget(target) {
178
177
  function updateGrammarError(detail) {
179
178
  return { kind: "error", message: PRODUCT_TEXT.diagnostic(`could not parse update: ${detail}`) };
180
179
  }
181
- function parsePiPackageCommand(arguments_) {
180
+ function parsePackageCommand(arguments_, namespace) {
182
181
  const [verb, ...rest] = arguments_;
183
182
  if ((verb === "install" || verb === "remove" || verb === "uninstall" || verb === "list" || verb === "update")
184
183
  && rest.some(value => value === "--help" || value === "-h")) {
185
- return { kind: "package-help", verb: verb === "uninstall" ? "remove" : verb };
184
+ return { kind: "package-help", verb: verb === "uninstall" ? "remove" : verb, namespace };
186
185
  }
187
186
  if (verb === "install" || verb === "remove" || verb === "uninstall") {
188
- return parseSourceCommand(verb === "install" ? "install" : "remove", rest);
187
+ return parseSourceCommand(verb === "install" ? "install" : "remove", rest, namespace);
189
188
  }
190
189
  if (verb === "list") {
191
190
  const flag = rest.find(argument => argument.startsWith("-"));
192
191
  if (flag !== undefined)
193
- return packageOptionError(flag, "list");
192
+ return packageOptionError(flag, "list", namespace);
194
193
  if (rest[0] !== undefined)
195
- return packageSyntax({ verb: "list", kind: "unexpected-argument", value: rest[0] });
194
+ return packageSyntax({ verb: "list", kind: "unexpected-argument", value: rest[0] }, namespace);
196
195
  return { kind: "packages", request: { verb: "list", source: null } };
197
196
  }
198
197
  if (verb === "update")
199
- return parsePiPackageUpdate(rest);
198
+ return parsePackageUpdate(rest, namespace);
200
199
  return { kind: "noop" };
201
200
  }
202
- function parsePiPackageUpdate(rest) {
201
+ function parsePackageUpdate(rest, namespace) {
203
202
  if (rest.length === 0)
204
203
  return pinnedPiUpdateError();
205
204
  if (rest.length > 1) {
206
205
  const unknown = rest.find(value => value.startsWith("-") && !["--extensions", "--models", "--self", "--all"].includes(value));
207
206
  if (unknown !== undefined)
208
- return packageOptionError(unknown, "update");
207
+ return packageOptionError(unknown, "update", namespace);
209
208
  if (rest.includes("--self") || rest.includes("--all") || rest.includes("self") || rest.includes("pi"))
210
209
  return pinnedPiUpdateError();
211
210
  if (rest.every(value => value === rest[0]) && rest[0]?.startsWith("-")) {
212
- return { kind: "error", message: PRODUCT_TEXT.diagnostic("pi update accepts one target.") };
211
+ const command = namespace === "direct" ? "update" : "pi update";
212
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic(`${command} accepts one target.`) };
213
213
  }
214
214
  const sources = rest.filter(value => !value.startsWith("-"));
215
215
  if (sources[1] !== undefined)
216
- return packageSyntax({ verb: "update", kind: "unexpected-argument", value: sources[1] });
216
+ return packageSyntax({ verb: "update", kind: "unexpected-argument", value: sources[1] }, namespace);
217
217
  const message = rest.includes("--models")
218
218
  ? rest.includes("--extensions")
219
219
  ? "--models cannot be combined with --self, --extensions, --all, or --extension"
220
220
  : "--models cannot be combined with a positional source"
221
221
  : "positional update targets cannot be combined with --self, --extensions, or --all";
222
- return packageSyntax({ verb: "update", kind: "conflict", message });
222
+ return packageSyntax({ verb: "update", kind: "conflict", message }, namespace);
223
223
  }
224
224
  const [target] = rest;
225
225
  if (target === "--extensions")
@@ -229,7 +229,7 @@ function parsePiPackageUpdate(rest) {
229
229
  if (target === "--self" || target === "--all" || target === "self" || target === "pi")
230
230
  return pinnedPiUpdateError();
231
231
  if (target === undefined || target.startsWith("-"))
232
- return packageOptionError(target ?? "", "update");
232
+ return packageOptionError(target ?? "", "update", namespace);
233
233
  return { kind: "packages", request: { verb: "update", source: target } };
234
234
  }
235
235
  function pinnedPiUpdateError() {
@@ -239,35 +239,35 @@ function pinnedPiUpdateError() {
239
239
  PRODUCT_TEXT.diagnostic("pins its certified Pi runtime and cannot update it independently."),
240
240
  "Use:",
241
241
  ` ${PRODUCT_TEXT.commandName} update Update a1`,
242
- ` ${PRODUCT_TEXT.commandName} pi update --extensions Update Pi-compatible packages`,
243
- ` ${PRODUCT_TEXT.commandName} pi update --models Refresh model catalogs`,
242
+ ` ${PRODUCT_TEXT.commandName} update --extensions Update Pi-compatible packages`,
243
+ ` ${PRODUCT_TEXT.commandName} update --models Refresh model catalogs`,
244
244
  ].join("\n"),
245
245
  };
246
246
  }
247
- function parseSourceCommand(verb, rest) {
247
+ function parseSourceCommand(verb, rest, namespace) {
248
248
  const flag = rest.find(argument => argument.startsWith("-"));
249
249
  if (flag !== undefined)
250
- return packageOptionError(flag, verb);
250
+ return packageOptionError(flag, verb, namespace);
251
251
  const [source, extra] = rest;
252
252
  if (extra !== undefined)
253
- return packageSyntax({ verb, kind: "unexpected-argument", value: extra });
253
+ return packageSyntax({ verb, kind: "unexpected-argument", value: extra }, namespace);
254
254
  if (source === undefined)
255
- return packageSyntax({ verb, kind: "missing-source" });
255
+ return packageSyntax({ verb, kind: "missing-source" }, namespace);
256
256
  if (PROFILE_WORDS.has(source))
257
257
  return profileRejection(verb);
258
258
  return { kind: "packages", request: { verb, source } };
259
259
  }
260
- function packageSyntax(diagnostic) {
261
- return { kind: "package-error", diagnostic };
260
+ function packageSyntax(diagnostic, namespace) {
261
+ return { kind: "package-error", diagnostic, namespace };
262
262
  }
263
- function packageOptionError(option, verb) {
263
+ function packageOptionError(option, verb, namespace) {
264
264
  if (option.startsWith("--profile") || option === "-l" || option === "--local")
265
265
  return profileRejection(verb);
266
266
  // Compatibility: these recognized Pi flags remain outside A1's supported subset.
267
267
  if (["--extension", "--approve", "--no-approve", "-a", "-na", "--force"].includes(option)) {
268
- return unknownOption(option, `pi ${verb}`);
268
+ return unknownOption(option, namespace === "direct" ? verb : `pi ${verb}`);
269
269
  }
270
- return packageSyntax({ verb, kind: "unknown-option", value: option });
270
+ return packageSyntax({ verb, kind: "unknown-option", value: option }, namespace);
271
271
  }
272
272
  function withoutArguments(rest, command) {
273
273
  if (rest.length === 0)
@@ -1,6 +1,7 @@
1
1
  import chalk from "chalk";
2
2
  /** Supported subset of pinned Pi's package-manager CLI presentation. */
3
3
  export type PackageCliVerb = "install" | "remove" | "list" | "update";
4
+ export type PackageCommandNamespace = "direct" | "pi";
4
5
  export type PackageSyntaxDiagnostic = {
5
6
  readonly verb: PackageCliVerb;
6
7
  readonly kind: "missing-source";
@@ -18,9 +19,9 @@ export type PackageSyntaxDiagnostic = {
18
19
  readonly message: string;
19
20
  };
20
21
  type MessageStyle = Pick<typeof chalk, "red" | "dim" | "bold">;
21
- export declare function packageCommandUsage(verb: PackageCliVerb): string;
22
+ export declare function packageCommandUsage(verb: PackageCliVerb, namespace?: PackageCommandNamespace): string;
22
23
  /** Diagnostic facts stay unstyled until the terminal boundary decides color support. */
23
- export declare function renderPackageSyntax(diagnostic: PackageSyntaxDiagnostic, style?: MessageStyle): string;
24
+ export declare function renderPackageSyntax(diagnostic: PackageSyntaxDiagnostic, namespace?: PackageCommandNamespace, style?: MessageStyle): string;
24
25
  /** Pi's help layout, with unsupported options and examples deliberately absent. */
25
- export declare function packageCommandHelp(verb: PackageCliVerb, style?: MessageStyle): string;
26
+ export declare function packageCommandHelp(verb: PackageCliVerb, namespace?: PackageCommandNamespace, style?: MessageStyle): string;
26
27
  export {};
@@ -2,21 +2,26 @@
2
2
  // at 914cf1472e715297caa30db4b9535d534a9eb718 (MIT); grammar is restricted to A1's supported subset.
3
3
  import chalk from "chalk";
4
4
  import { PRODUCT_TEXT } from "../product-identity.js";
5
- export function packageCommandUsage(verb) {
6
- const command = `${PRODUCT_TEXT.commandName} pi ${verb}`;
5
+ export function packageCommandUsage(verb, namespace = "pi") {
6
+ const command = namespace === "direct"
7
+ ? `${PRODUCT_TEXT.commandName} ${verb}`
8
+ : `${PRODUCT_TEXT.commandName} pi ${verb}`;
7
9
  if (verb === "install" || verb === "remove")
8
10
  return `${command} <source>`;
11
+ if (verb === "update" && namespace === "direct") {
12
+ return `${command} [--develop [preview-or-version]|--models|--extensions|source]`;
13
+ }
9
14
  if (verb === "update")
10
15
  return `${command} [source|--extensions|--models]`;
11
16
  return command;
12
17
  }
13
18
  /** Diagnostic facts stay unstyled until the terminal boundary decides color support. */
14
- export function renderPackageSyntax(diagnostic, style = chalk) {
15
- const usage = packageCommandUsage(diagnostic.verb);
19
+ export function renderPackageSyntax(diagnostic, namespace = "pi", style = chalk) {
20
+ const usage = packageCommandUsage(diagnostic.verb, namespace);
16
21
  let message;
17
22
  if (diagnostic.kind === "unknown-option") {
18
23
  return `${style.red(`Unknown option ${diagnostic.value} for "${diagnostic.verb}".`)}\n`
19
- + `${style.dim(`Use "${PRODUCT_TEXT.commandName} --help" or "${usage}".`)}\n`;
24
+ + `${style.dim(`Use "${PRODUCT_TEXT.commandName} help" or "${usage}".`)}\n`;
20
25
  }
21
26
  if (diagnostic.kind === "missing-source")
22
27
  message = `Missing ${diagnostic.verb} source.`;
@@ -27,9 +32,16 @@ export function renderPackageSyntax(diagnostic, style = chalk) {
27
32
  return `${style.red(message)}\n${style.dim(`Usage: ${usage}`)}\n`;
28
33
  }
29
34
  /** Pi's help layout, with unsupported options and examples deliberately absent. */
30
- export function packageCommandHelp(verb, style = chalk) {
31
- const command = `${PRODUCT_TEXT.commandName} pi`;
32
- const header = `${style.bold("Usage:")}\n ${packageCommandUsage(verb)}\n\n`;
35
+ export function packageCommandHelp(verb, namespace = "pi", style = chalk) {
36
+ const command = namespace === "direct" ? PRODUCT_TEXT.commandName : `${PRODUCT_TEXT.commandName} pi`;
37
+ const header = verb === "update" && namespace === "direct"
38
+ ? `${style.bold("Usage:")}\n`
39
+ + ` ${command} update\n`
40
+ + ` ${command} update --develop [preview-or-version]\n`
41
+ + ` ${command} update --models\n`
42
+ + ` ${command} update --extensions\n`
43
+ + ` ${command} update <source>\n\n`
44
+ : `${style.bold("Usage:")}\n ${packageCommandUsage(verb, namespace)}\n\n`;
33
45
  switch (verb) {
34
46
  case "install":
35
47
  return header + `Install a package and add it to settings.
@@ -55,14 +67,14 @@ Examples:
55
67
  case "list":
56
68
  return header + "List installed packages from user settings.\n\n";
57
69
  case "update":
58
- return header + `Update installed packages or model catalogs.
70
+ return header + `${namespace === "direct" ? "Update A1, installed packages, or model catalogs." : "Update installed packages or model catalogs."}
59
71
 
60
72
  Options:
61
- --extensions Update installed packages only
73
+ ${namespace === "direct" ? " --develop [target] Update to the development channel or one preview\n" : ""} --extensions Update installed packages only
62
74
  --models Refresh model catalogs only
63
75
 
64
76
  Short forms:
65
- ${command} update --extensions Update installed packages only
77
+ ${namespace === "direct" ? ` ${command} update Update A1 to the stable release\n ${command} update --develop Update A1 to the development preview\n` : ""} ${command} update --extensions Update installed packages only
66
78
  ${command} update --models Refresh model catalogs only
67
79
  ${command} update <source> Update one package
68
80