@timurproko/a1 0.1.8-dev.182 → 0.1.8-dev.218

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 (143) hide show
  1. package/bin/cli.js +2 -2
  2. package/bin/sync-pi-tui-proxy.js +1 -1
  3. package/bin/ui.js +1 -1
  4. package/dist/cli/dispatch.js +3 -3
  5. package/dist/cli/packages.js +1 -1
  6. package/dist/cli/version-stats.js +3 -3
  7. package/dist/composition/owned-ui.js +1 -1
  8. package/dist/composition/settings-route-host.js +6 -4
  9. package/dist/features/launch/development-launch.js +1 -1
  10. package/dist/features/owned-ui/customization.d.ts +2 -0
  11. package/dist/features/owned-ui/customization.js +2 -0
  12. package/dist/features/owned-ui/diagnostics.d.ts +1 -0
  13. package/dist/features/owned-ui/diagnostics.js +1 -0
  14. package/dist/features/owned-ui/project-trust-prompt.d.ts +11 -6
  15. package/dist/features/owned-ui/project-trust-prompt.js +134 -17
  16. package/dist/features/owned-ui/settings-app.js +47 -50
  17. package/dist/features/workspace/reducer.d.ts +1 -0
  18. package/dist/features/workspace/reducer.js +1 -0
  19. package/dist/features/workspace/router.d.ts +1 -0
  20. package/dist/features/workspace/router.js +1 -0
  21. package/dist/features/workspace/store.d.ts +1 -0
  22. package/dist/features/workspace/store.js +1 -0
  23. package/dist/foundation/launch-guardian/main.js +1 -1
  24. package/dist/foundation/native-host-protocol/codec.d.ts +2 -0
  25. package/dist/foundation/native-host-protocol/codec.js +2 -0
  26. package/dist/foundation/process-containment/linux-process-inspector.d.ts +1 -0
  27. package/dist/foundation/process-containment/linux-process-inspector.js +1 -0
  28. package/dist/foundation/process-containment/native-guardian-containment.d.ts +1 -0
  29. package/dist/foundation/process-containment/native-guardian-containment.js +1 -0
  30. package/dist/foundation/process-containment/windows-process-inspector.d.ts +1 -0
  31. package/dist/foundation/process-containment/windows-process-inspector.js +1 -0
  32. package/dist/foundation/protocol/client.d.ts +1 -0
  33. package/dist/foundation/protocol/client.js +1 -0
  34. package/dist/foundation/protocol/messages.d.ts +1 -0
  35. package/dist/foundation/protocol/messages.js +1 -0
  36. package/dist/foundation/release/bootstrap.js +6 -6
  37. package/dist/foundation/release/cohort-state.d.ts +1 -0
  38. package/dist/foundation/release/cohort-state.js +1 -0
  39. package/dist/foundation/release/release-gc.js +1 -1
  40. package/dist/foundation/release/release.js +1 -1
  41. package/dist/foundation/release/update-transaction.d.ts +1 -0
  42. package/dist/foundation/release/update-transaction.js +1 -0
  43. package/dist/foundation/release/update.js +12 -12
  44. package/dist/foundation/storage/control-store.d.ts +1 -0
  45. package/dist/foundation/storage/control-store.js +1 -0
  46. package/dist/foundation/structured-agent-runtime/backpressure.d.ts +1 -0
  47. package/dist/foundation/structured-agent-runtime/backpressure.js +1 -0
  48. package/dist/foundation/structured-agent-runtime/commands.d.ts +1 -0
  49. package/dist/foundation/structured-agent-runtime/commands.js +1 -0
  50. package/dist/foundation/structured-agent-runtime/reconnection.d.ts +1 -0
  51. package/dist/foundation/structured-agent-runtime/reconnection.js +1 -0
  52. package/dist/foundation/structured-agent-runtime/state.d.ts +1 -0
  53. package/dist/foundation/structured-agent-runtime/state.js +1 -0
  54. package/dist/foundation/supervision/main.js +1 -1
  55. package/dist/foundation/supervision/server.d.ts +1 -0
  56. package/dist/foundation/supervision/server.js +4 -6
  57. package/dist/integrations/pi/components/conformance.d.ts +1 -0
  58. package/dist/integrations/pi/components/conformance.js +1 -0
  59. package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
  60. package/dist/integrations/pi/components/shell-footer-status.js +8 -7
  61. package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
  62. package/dist/integrations/pi/engine/adapter.d.ts +7 -0
  63. package/dist/integrations/pi/engine/adapter.js +37 -35
  64. package/dist/integrations/pi/engine/conformance.d.ts +2 -0
  65. package/dist/integrations/pi/engine/conformance.js +2 -0
  66. package/dist/integrations/pi/engine/http-dispatcher.js +1 -1
  67. package/dist/integrations/pi/engine/model-auth-integration.d.ts +1 -0
  68. package/dist/integrations/pi/engine/model-auth-integration.js +1 -0
  69. package/dist/integrations/pi/engine/package-integration.js +1 -1
  70. package/dist/integrations/pi/engine/project-trust-preflight.js +1 -1
  71. package/dist/integrations/pi/engine/resource-extension-integration.d.ts +1 -0
  72. package/dist/integrations/pi/engine/resource-extension-integration.js +1 -0
  73. package/dist/integrations/pi/engine/session-integration.d.ts +1 -0
  74. package/dist/integrations/pi/engine/session-integration.js +2 -1
  75. package/dist/integrations/pi/engine/settings-effects.d.ts +13 -2
  76. package/dist/integrations/pi/engine/settings-effects.js +62 -42
  77. package/dist/integrations/pi/engine/settings-integration.js +4 -4
  78. package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -0
  79. package/dist/integrations/pi/engine/workflow-controllers.js +1 -0
  80. package/dist/integrations/pi/session-ui/index.d.ts +1 -0
  81. package/dist/integrations/pi/session-ui/index.js +1 -0
  82. package/dist/integrations/pi/session-ui/prompt-chips.js +1 -1
  83. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +30 -2
  84. package/dist/integrations/pi/session-ui/session-shell-root.js +133 -72
  85. package/dist/integrations/pi/session-ui/session-shell.d.ts +9 -2
  86. package/dist/integrations/pi/session-ui/session-shell.js +112 -37
  87. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
  88. package/dist/integrations/pi/session-ui/session-viewport-controller.js +44 -11
  89. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.d.ts +29 -0
  90. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.js +79 -0
  91. package/dist/integrations/pi/session-ui/system-clipboard.js +6 -6
  92. package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
  93. package/dist/integrations/pi/tui-runtime/adapter.js +138 -8
  94. package/dist/integrations/pi/tui-runtime/contracts.d.ts +25 -0
  95. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +63 -0
  96. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +240 -0
  97. package/dist/integrations/pi/tui-runtime/index.d.ts +2 -0
  98. package/dist/integrations/pi/tui-runtime/index.js +2 -0
  99. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +39 -0
  100. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.js +139 -0
  101. package/dist/native/darwin-arm64/manifest.json +3 -3
  102. package/dist/native/darwin-arm64/process-guardian +0 -0
  103. package/dist/native/linux-x64/manifest.json +1 -1
  104. package/dist/native/win32-x64/manifest.json +2 -2
  105. package/dist/native/win32-x64/process-guardian.exe +0 -0
  106. package/dist/ui/apps/host.js +5 -9
  107. package/dist/ui/apps/registry.d.ts +1 -0
  108. package/dist/ui/apps/registry.js +1 -0
  109. package/dist/ui/components/dialog-panel.js +5 -3
  110. package/dist/ui/components/frame.d.ts +1 -0
  111. package/dist/ui/components/frame.js +1 -0
  112. package/dist/ui/components/index.d.ts +1 -0
  113. package/dist/ui/components/index.js +1 -0
  114. package/dist/ui/components/line-input.d.ts +1 -6
  115. package/dist/ui/components/line-input.js +4 -9
  116. package/dist/ui/components/list-view.d.ts +0 -6
  117. package/dist/ui/components/list-view.js +14 -7
  118. package/dist/ui/components/progress-status.d.ts +2 -0
  119. package/dist/ui/components/progress-status.js +4 -0
  120. package/dist/ui/components/scrollbar.js +1 -1
  121. package/dist/ui/components/shortcuts.d.ts +1 -0
  122. package/dist/ui/components/shortcuts.js +1 -0
  123. package/dist/ui/components/spans.d.ts +0 -5
  124. package/dist/ui/components/spans.js +3 -8
  125. package/dist/ui/components/surface.d.ts +0 -5
  126. package/dist/ui/components/surface.js +1 -5
  127. package/dist/ui/components/text-selection.d.ts +10 -1
  128. package/dist/ui/components/text-selection.js +96 -29
  129. package/dist/ui/components/text.js +3 -3
  130. package/dist/ui/components/transcript-viewport.d.ts +42 -0
  131. package/dist/ui/components/transcript-viewport.js +287 -39
  132. package/dist/ui/settings/sections.d.ts +0 -2
  133. package/dist/ui/settings/sections.js +10 -16
  134. package/dist/ui/settings/session.js +5 -4
  135. package/dist/ui/settings/store.d.ts +1 -0
  136. package/dist/ui/settings/store.js +2 -1
  137. package/docs/architecture/code-documentation.md +77 -0
  138. package/docs/architecture/project-structure.md +1 -1
  139. package/docs/architecture/ui-reference-provenance.md +23 -3
  140. package/docs/ci-release-runbook.md +48 -4
  141. package/docs/manual-owned-ui-checkpoint.md +28 -1
  142. package/docs/manual-terminal-colour-check.md +4 -3
  143. package/package.json +7 -2
package/bin/cli.js CHANGED
@@ -5,7 +5,7 @@ const { fileURLToPath } = await import("node:url");
5
5
  const { readFile } = await import("node:fs/promises");
6
6
  const { cliCapabilities, dispatchCli } = await import("../dist/cli/index.js");
7
7
 
8
- // Which commands this build exposes follows from the build's own version, so a
8
+ // Compatibility: which commands this build exposes follows from the build's own version, so a
9
9
  // released a1 cannot be argued into offering the development profiles.
10
10
  const capabilities = cliCapabilities(JSON.parse(await readFile(new URL("package.json", packageRoot), "utf8")).version);
11
11
 
@@ -16,7 +16,7 @@ process.exitCode = await dispatchCli(process.argv.slice(2), {
16
16
  import("../dist/foundation/release/index.js"),
17
17
  import("./module-identity.js"),
18
18
  ]);
19
- // Self-heal the installed tree before the release store copies it: npm 12
19
+ // Compatibility: self-heal the installed tree before the release store copies it: npm 12
20
20
  // blocks install scripts by default, so the postinstall that normally
21
21
  // repairs module identity may never have run (see bin/module-identity.js).
22
22
  const packageRootPath = fileURLToPath(packageRoot);
@@ -35,7 +35,7 @@ const RE_EXPORT_LINE = /^export \* from "[^"]+";$/m;
35
35
  * pinned Pi resolves. Returns a discriminated outcome; never throws.
36
36
  */
37
37
  export function syncPiTuiProxy(packageRoot) {
38
- // resolvePinnedPiTui answers with a canonical real path, so the directory the
38
+ // Platform: resolvePinnedPiTui answers with a canonical real path, so the directory the
39
39
  // relative specifier is computed from must be canonical too (Windows short
40
40
  // names, symlinked installs).
41
41
  const canonicalRoot = canonical(packageRoot);
package/bin/ui.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const { assertSinglePiTuiModuleAtLaunch } = await import("./module-identity.js");
4
4
  const { fileURLToPath } = await import("node:url");
5
5
 
6
- // Before the composition loads pinned Pi's terminal stack: confirm A1 and Pi
6
+ // Compatibility: before the composition loads pinned Pi's terminal stack: confirm A1 and Pi
7
7
  // resolve it to the same copy, so extensions and the owned UI share one module
8
8
  // identity (see bin/module-identity.js for the full story). Nothing is repaired
9
9
  // here — A1's package manifest decides which copy wins, and this only reports
@@ -85,7 +85,7 @@ export function parseCliCommand(arguments_, capabilities) {
85
85
  return parseUpdate(rest);
86
86
  if (command?.startsWith("update:"))
87
87
  return { kind: "noop" };
88
- // Unsupported and reserved command spaces are deliberately quiet. Help is
88
+ // Rationale: unsupported and reserved command spaces are deliberately quiet. Help is
89
89
  // explicit, and a typo must never start an interactive or maintenance path.
90
90
  return { kind: "noop" };
91
91
  }
@@ -98,7 +98,7 @@ function parseUpdate(rest) {
98
98
  if (rest.length === 0)
99
99
  return { kind: "update", channel: "stable" };
100
100
  const [selector, ...values] = rest;
101
- // Removed compatibility notation is unsupported rather than deprecated.
101
+ // Compatibility: removed notation is unsupported rather than deprecated.
102
102
  if (selector === "self")
103
103
  return { kind: "noop" };
104
104
  if (selector === "--develop") {
@@ -121,7 +121,7 @@ function parseUpdate(rest) {
121
121
  return pinnedPiUpdateError();
122
122
  if (selector !== undefined && selector.startsWith("-"))
123
123
  return unknownOption(selector, "update");
124
- // Positional update targets are reserved for a future A1-native plugin model.
124
+ // Rationale: positional update targets are reserved for a future A1-native plugin model.
125
125
  return { kind: "noop" };
126
126
  }
127
127
  function isDevelopmentTarget(target) {
@@ -40,7 +40,7 @@ async function runVerb(port, request) {
40
40
  return request.verb === "install" ? await port.install(request.source) : await port.remove(request.source);
41
41
  }
42
42
  export function renderPackageOutcome(outcome, profileRoot, style = chalk) {
43
- // Model refresh remains an A1 top-level command. The `a1 pi` compatibility
43
+ // Compatibility: model refresh remains an A1 top-level command. The `a1 pi` compatibility
44
44
  // transcript applies to package operations only.
45
45
  if (outcome.operation === "refresh-models") {
46
46
  if (outcome.status === "failed") {
@@ -20,7 +20,7 @@ export async function runVersionStats(options) {
20
20
  output.stderr(`${PRODUCT_TEXT.diagnostic(`could not read its installed version: ${message(error)}`)}\n`);
21
21
  return 1;
22
22
  }
23
- // Match Pi's release behavior: stable builds print only their installed version.
23
+ // Compatibility: match Pi's release behavior: stable builds print only their installed version.
24
24
  // Development builds retain channel visibility for preview comparison and updates.
25
25
  if (prerelease(installed) === null) {
26
26
  output.stdout(`${installed}\n`);
@@ -32,7 +32,7 @@ export async function runVersionStats(options) {
32
32
  output.stderr(`${PRODUCT_TEXT.diagnostic(`could not resolve npm dist-tags: ${remote.error}`)}\n`);
33
33
  return 0;
34
34
  }
35
- // npm view respects the user's .npmrc registry and proxy, so it stays primary; the direct
35
+ // Rationale: npm view respects the user's .npmrc registry and proxy, so it stays primary; the direct
36
36
  // registry fetch below covers npm CLI versions whose --json output shape we cannot parse.
37
37
  async function queryDistTags(runner, fetcher) {
38
38
  const fromNpm = await queryDistTagsViaNpm(runner);
@@ -55,7 +55,7 @@ async function queryDistTagsViaNpm(runner) {
55
55
  return unavailable(`npm exited with status ${result.code ?? "unknown"}`);
56
56
  try {
57
57
  const parsed = JSON.parse(result.stdout);
58
- // npm 12 wraps `npm view <pkg> dist-tags --json` output in a one-element array; older npm returns the bare object.
58
+ // Compatibility: npm 12 wraps `npm view <pkg> dist-tags --json` output in a one-element array; older npm returns the bare object.
59
59
  const metadata = Array.isArray(parsed) && parsed.length === 1 ? parsed[0] : parsed;
60
60
  return parseDistTags(metadata, "npm");
61
61
  }
@@ -26,7 +26,7 @@ export async function composeOwnedUi(options = {}) {
26
26
  store: new OwnedUiSettingsStore({ configDir: resolveProductPaths().configDir, profileId: options.profileId }),
27
27
  agentProvider: () => adapter.settingsPort(),
28
28
  });
29
- // Bare A1 intentionally ships one visual target while its UI is being completed:
29
+ // Compatibility: bare A1 intentionally ships one visual target while its UI is being completed:
30
30
  // dark, regardless of terminal detection or a previously stored Pi theme. The
31
31
  // comparison profile keeps Pi's configured theme behavior and settings surface.
32
32
  applyConfiguredPiTheme(ownedSurfaces ? "dark" : adapter.configuredTheme());
@@ -61,13 +61,15 @@ function pinnedTheme() {
61
61
  return {
62
62
  fg: (token, text) => piTheme().fg(token, text),
63
63
  bold: (text) => piTheme().bold(text),
64
- // Unpainted, so a list reads against the terminal the reader actually has
64
+ // Rationale: unpainted, so a list reads against the terminal the reader actually has
65
65
  // rather than against the background the configured theme was built for.
66
66
  plain: (text) => text,
67
- // Quiet, and quieter again: the terminal's own faint attribute over the
67
+ // Rationale: quiet, and quieter again: the terminal's own faint attribute over the
68
68
  // dimmest colour the theme has.
69
69
  disabled: (text) => faint(piTheme().fg("dim", text)),
70
- highlight: (text) => `${text}`,
71
- panel: (text) => `${text}`,
70
+ // Rationale: physical review requires the shared value menu to read as a floating
71
+ // product surface instead of blending into the settings rows behind it.
72
+ highlight: (text) => `\u001b[48;2;82;82;82m\u001b[97m${text}\u001b[39m\u001b[49m`,
73
+ panel: (text) => `\u001b[48;2;55;55;55m${text}\u001b[49m`,
72
74
  };
73
75
  }
@@ -12,7 +12,7 @@ export function resolveDevelopmentLaunchEnvironment(canonicalPackageRoot, releas
12
12
  const selectedDevelopmentRoot = source[PRODUCT_IDENTITY.environment.developmentRoot];
13
13
  const developmentRoot = resolve(selectedDevelopmentRoot
14
14
  ?? join(checkoutRoot, releaseId, "instances", instanceId));
15
- // Runtime/data remain instance-isolated, but product settings are user
15
+ // Rationale: runtime/data remain instance-isolated, but product settings are user
16
16
  // preferences and must survive a fresh local launch (and a rebuilt candidate).
17
17
  // An explicit development root retains its historical self-contained policy.
18
18
  const persistentConfigDir = selectedDevelopmentRoot
@@ -12,6 +12,7 @@ export interface OwnedCommandHandlerContext {
12
12
  readonly correlationId: string;
13
13
  }
14
14
  export type OwnedCommandHandler = (context: OwnedCommandHandlerContext) => OwnedUiCommand | Promise<OwnedUiCommand>;
15
+ /** Registers owned UI commands and assigns a monotonic correlation identifier to each dispatch. */
15
16
  export declare class OwnedCommandSurface {
16
17
  #private;
17
18
  register(name: string, handler: OwnedCommandHandler): () => void;
@@ -22,6 +23,7 @@ export interface ResolvedOwnedUiSlot {
22
23
  readonly customization: OwnedUiCustomization;
23
24
  readonly implementation: OwnedUiSlotImplementation;
24
25
  }
26
+ /** Resolves one customization per slot by precedence and version while preserving registration identity. */
25
27
  export declare class OwnedUiCustomizationRegistry {
26
28
  #private;
27
29
  register(customization: OwnedUiCustomization, implementation: OwnedUiSlotImplementation): () => void;
@@ -1,4 +1,5 @@
1
1
  import { assertOwnedUiCustomization, } from "../../contracts/owned-ui/index.js";
2
+ /** Registers owned UI commands and assigns a monotonic correlation identifier to each dispatch. */
2
3
  export class OwnedCommandSurface {
3
4
  #handlers = new Map();
4
5
  #sequence = 0;
@@ -17,6 +18,7 @@ export class OwnedCommandSurface {
17
18
  return handler({ sessionId, correlationId: `ui-command-${this.#sequence}` });
18
19
  }
19
20
  }
21
+ /** Resolves one customization per slot by precedence and version while preserving registration identity. */
20
22
  export class OwnedUiCustomizationRegistry {
21
23
  #registrations = new Map();
22
24
  register(customization, implementation) {
@@ -17,6 +17,7 @@ export interface OwnedUiDiagnosticsSnapshot {
17
17
  readonly resources: OwnedUiResourceObservation | null;
18
18
  readonly terminalRestorationFailed: boolean;
19
19
  }
20
+ /** Retains bounded, redacted UI diagnostics together with frame and resource observations. */
20
21
  export declare class OwnedUiDiagnosticsRecorder {
21
22
  #private;
22
23
  readonly maximumEntries: number;
@@ -1,3 +1,4 @@
1
+ /** Retains bounded, redacted UI diagnostics together with frame and resource observations. */
1
2
  export class OwnedUiDiagnosticsRecorder {
2
3
  maximumEntries;
3
4
  #entries = [];
@@ -4,17 +4,22 @@ export interface OwnedProjectTrustPromptRequest {
4
4
  readonly defaultDecision: "ask" | "always" | "never";
5
5
  }
6
6
  export type OwnedProjectTrustPrompt = (request: OwnedProjectTrustPromptRequest) => Promise<boolean | null>;
7
+ interface RawTtyInput extends Readable {
8
+ readonly isTTY?: boolean;
9
+ readonly isRaw?: boolean;
10
+ setRawMode?(enabled: boolean): this;
11
+ }
7
12
  export interface ConsoleProjectTrustPromptOptions {
8
- readonly input?: Readable & {
9
- readonly isTTY?: boolean;
10
- };
13
+ readonly input?: RawTtyInput;
11
14
  readonly output?: Writable & {
12
15
  readonly isTTY?: boolean;
16
+ readonly columns?: number;
13
17
  };
14
18
  }
15
19
  /**
16
- * Minimal pre-session surface. It depends only on the parent terminal and fixed
17
- * A1 wording, so no project setting, theme, extension, prompt, or skill can
18
- * execute before the decision.
20
+ * Pre-resource startup selector. It uses only fixed product wording and terminal
21
+ * controls: no project setting, theme, extension, prompt, package, or skill is
22
+ * consulted before the trust result exists.
19
23
  */
20
24
  export declare function createConsoleProjectTrustPrompt(options?: ConsoleProjectTrustPromptOptions): OwnedProjectTrustPrompt;
25
+ export {};
@@ -1,8 +1,18 @@
1
- import { createInterface } from "node:readline/promises";
1
+ const ENTER_ALTERNATE_SCREEN = "\u001b[?1049h";
2
+ const LEAVE_ALTERNATE_SCREEN = "\u001b[?1049l";
3
+ const HIDE_CURSOR = "\u001b[?25l";
4
+ const SHOW_CURSOR = "\u001b[?25h";
5
+ const CLEAR_HOME = "\u001b[2J\u001b[H";
6
+ const ACCENT = "\u001b[38;2;138;190;183m";
7
+ const MUTED = "\u001b[38;2;128;128;128m";
8
+ const DIM = "\u001b[38;2;102;102;102m";
9
+ const RESET_FG = "\u001b[39m";
10
+ const BOLD = "\u001b[1m";
11
+ const RESET_BOLD = "\u001b[22m";
2
12
  /**
3
- * Minimal pre-session surface. It depends only on the parent terminal and fixed
4
- * A1 wording, so no project setting, theme, extension, prompt, or skill can
5
- * execute before the decision.
13
+ * Pre-resource startup selector. It uses only fixed product wording and terminal
14
+ * controls: no project setting, theme, extension, prompt, package, or skill is
15
+ * consulted before the trust result exists.
6
16
  */
7
17
  export function createConsoleProjectTrustPrompt(options = {}) {
8
18
  const input = options.input ?? process.stdin;
@@ -11,22 +21,129 @@ export function createConsoleProjectTrustPrompt(options = {}) {
11
21
  if (input.isTTY !== true || output.isTTY !== true) {
12
22
  throw new Error("an interactive terminal is unavailable");
13
23
  }
14
- const reader = createInterface({ input, output, terminal: true });
24
+ const wasRaw = input.isRaw === true;
25
+ let selected = 0;
26
+ let restored = false;
27
+ const restore = () => {
28
+ if (restored)
29
+ return;
30
+ restored = true;
31
+ input.setRawMode?.(wasRaw);
32
+ output.write(`${CLEAR_HOME}${SHOW_CURSOR}${LEAVE_ALTERNATE_SCREEN}`);
33
+ };
34
+ const render = () => {
35
+ const width = Math.max(20, output.columns ?? 80);
36
+ const lines = [
37
+ `${BOLD}${ACCENT}Trust project folder?${RESET_FG}${RESET_BOLD}`,
38
+ cwd,
39
+ "",
40
+ "This allows A1 to load project settings and resources, install missing project packages, and execute project extensions.",
41
+ "",
42
+ optionRow("Trust", selected === 0),
43
+ optionRow("Do not trust", selected === 1),
44
+ "",
45
+ `${DIM} ↑/↓ to navigate · Enter to select · Esc to cancel${RESET_FG}`,
46
+ ];
47
+ output.write(`${CLEAR_HOME}${lines.map(line => clipAnsiSafe(line, width)).join("\n")}`);
48
+ };
15
49
  try {
16
- output.write(`\nA1 found project-local settings or executable resources in:\n${cwd}\n`);
17
- output.write("Trusting permits project settings, skills, prompts, packages, themes, and extensions to load.\n");
18
- for (let attempt = 0; attempt < 3; attempt += 1) {
19
- const answer = (await reader.question("Trust this project for this and future launches? [y/N] ")).trim().toLowerCase();
20
- if (answer === "y" || answer === "yes")
21
- return true;
22
- if (answer === "" || answer === "n" || answer === "no")
23
- return false;
24
- output.write("Enter y or n.\n");
25
- }
26
- return null;
50
+ output.write(`${ENTER_ALTERNATE_SCREEN}${HIDE_CURSOR}`);
51
+ input.setRawMode?.(true);
52
+ render();
53
+ return await new Promise((resolve, reject) => {
54
+ let settled = false;
55
+ const finish = (value) => {
56
+ if (settled)
57
+ return;
58
+ settled = true;
59
+ cleanup();
60
+ restore();
61
+ resolve(value);
62
+ };
63
+ const fail = (error) => {
64
+ if (settled)
65
+ return;
66
+ settled = true;
67
+ cleanup();
68
+ restore();
69
+ reject(error);
70
+ };
71
+ const onData = (chunk) => {
72
+ const data = chunk.toString();
73
+ for (let index = 0; index < data.length;) {
74
+ if (data.startsWith("\u001b[A", index)) {
75
+ selected = 0;
76
+ index += 3;
77
+ render();
78
+ continue;
79
+ }
80
+ if (data.startsWith("\u001b[B", index) || data[index] === "\t") {
81
+ selected = selected === 0 ? 1 : 0;
82
+ index += data[index] === "\t" ? 1 : 3;
83
+ render();
84
+ continue;
85
+ }
86
+ const key = data[index] ?? "";
87
+ index += 1;
88
+ if (key === "\r" || key === "\n") {
89
+ finish(selected === 0);
90
+ return;
91
+ }
92
+ if (key === "\u001b" || key === "\u0003") {
93
+ finish(null);
94
+ return;
95
+ }
96
+ // Compatibility: retain y/n aliases for terminals or automation that cannot send
97
+ // navigation keys; the visible interaction remains selector-first.
98
+ if (key === "y" || key === "Y") {
99
+ finish(true);
100
+ return;
101
+ }
102
+ if (key === "n" || key === "N") {
103
+ finish(false);
104
+ return;
105
+ }
106
+ }
107
+ };
108
+ const onEnd = () => finish(null);
109
+ const onError = (error) => fail(error);
110
+ const cleanup = () => {
111
+ input.off("data", onData);
112
+ input.off("end", onEnd);
113
+ input.off("error", onError);
114
+ };
115
+ input.on("data", onData);
116
+ input.once("end", onEnd);
117
+ input.once("error", onError);
118
+ input.resume();
119
+ });
27
120
  }
28
121
  finally {
29
- reader.close();
122
+ restore();
30
123
  }
31
124
  };
32
125
  }
126
+ function optionRow(label, selected) {
127
+ return selected
128
+ ? `${ACCENT}→ ${label}${RESET_FG}`
129
+ : ` ${label}`;
130
+ }
131
+ /** Avoid replaying or manufacturing control payloads while keeping fixed SGR. */
132
+ function clipAnsiSafe(line, width) {
133
+ let visible = 0;
134
+ let output = "";
135
+ for (let index = 0; index < line.length && visible < width;) {
136
+ if (line[index] === "\u001b") {
137
+ const match = line.slice(index).match(/^\u001b\[[0-9;:]*m/);
138
+ if (match !== null) {
139
+ output += match[0];
140
+ index += match[0].length;
141
+ continue;
142
+ }
143
+ }
144
+ output += line[index];
145
+ index += 1;
146
+ visible += 1;
147
+ }
148
+ return `${output}${RESET_FG}${RESET_BOLD}`;
149
+ }