@xingular/harness 1.2.0 → 1.3.1-unstable.1

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 (186) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +26 -17
  3. package/builtin/skills/plugin-authoring/SKILL.md +34 -0
  4. package/dist/cli.js +7362 -6239
  5. package/dist/sdk/agents/builtin.js +3 -3
  6. package/dist/sdk/agents/planResearch.d.ts +23 -1
  7. package/dist/sdk/agents/planResearch.js +71 -8
  8. package/dist/sdk/agents/profiles.d.ts +16 -4
  9. package/dist/sdk/agents/profiles.js +75 -12
  10. package/dist/sdk/annotations/bridge.d.ts +2 -0
  11. package/dist/sdk/annotations/bridge.js +4 -0
  12. package/dist/sdk/annotations/capture.d.ts +44 -2
  13. package/dist/sdk/annotations/capture.js +104 -3
  14. package/dist/sdk/annotations/delivery.d.ts +24 -17
  15. package/dist/sdk/annotations/delivery.js +82 -49
  16. package/dist/sdk/annotations/inspect.js +7 -6
  17. package/dist/sdk/annotations/pageScript.js +4 -4
  18. package/dist/sdk/annotations/part.d.ts +0 -2
  19. package/dist/sdk/annotations/part.js +4 -33
  20. package/dist/sdk/annotations/queue.d.ts +2 -2
  21. package/dist/sdk/annotations/queue.js +12 -7
  22. package/dist/sdk/annotations/serverMethods.js +11 -7
  23. package/dist/sdk/annotations/store.d.ts +13 -21
  24. package/dist/sdk/annotations/store.js +47 -37
  25. package/dist/sdk/capabilities/runSnapshot.js +10 -4
  26. package/dist/sdk/commands/list.js +2 -0
  27. package/dist/sdk/commands/registry.d.ts +11 -3
  28. package/dist/sdk/commands/registry.js +3 -2
  29. package/dist/sdk/core/links.d.ts +51 -0
  30. package/dist/sdk/core/links.js +155 -0
  31. package/dist/sdk/core/shell.d.ts +20 -0
  32. package/dist/sdk/core/shell.js +73 -0
  33. package/dist/sdk/core/workspaceBashTool.d.ts +17 -1
  34. package/dist/sdk/core/workspaceBashTool.js +16 -2
  35. package/dist/sdk/core/workspaceSandbox.d.ts +10 -0
  36. package/dist/sdk/core/workspaceSandbox.js +52 -12
  37. package/dist/sdk/core/workspaceWriteOperations.d.ts +11 -0
  38. package/dist/sdk/core/workspaceWriteOperations.js +26 -8
  39. package/dist/sdk/doctor/checks.js +71 -12
  40. package/dist/sdk/harness.d.ts +5 -0
  41. package/dist/sdk/harness.js +11 -6
  42. package/dist/sdk/hub/hub.js +5 -3
  43. package/dist/sdk/index.d.ts +3 -2
  44. package/dist/sdk/index.js +2 -1
  45. package/dist/sdk/plugins/builtin.js +14 -9
  46. package/dist/sdk/plugins/builtinRegistry.d.ts +3 -1
  47. package/dist/sdk/plugins/builtinRegistry.js +9 -3
  48. package/dist/sdk/plugins/discover.d.ts +7 -1
  49. package/dist/sdk/plugins/discover.js +41 -2
  50. package/dist/sdk/plugins/identity.js +1 -1
  51. package/dist/sdk/plugins/install.d.ts +6 -2
  52. package/dist/sdk/plugins/install.js +29 -5
  53. package/dist/sdk/plugins/inventory.js +3 -3
  54. package/dist/sdk/plugins/manifest.d.ts +2 -0
  55. package/dist/sdk/plugins/manifest.js +21 -4
  56. package/dist/sdk/plugins/plugins.d.ts +2 -0
  57. package/dist/sdk/plugins/plugins.js +1 -0
  58. package/dist/sdk/plugins/proposals.d.ts +17 -0
  59. package/dist/sdk/plugins/proposals.js +147 -0
  60. package/dist/sdk/plugins/types.d.ts +1 -1
  61. package/dist/sdk/plugins/types.js +1 -1
  62. package/dist/sdk/prompt/compile.d.ts +13 -4
  63. package/dist/sdk/prompt/compile.js +46 -20
  64. package/dist/sdk/prompt/standard.d.ts +1 -1
  65. package/dist/sdk/prompt/standard.js +1 -1
  66. package/dist/sdk/server/methods.js +14 -2
  67. package/dist/sdk/sessions/compactionCheckpoint.d.ts +3 -3
  68. package/dist/sdk/sessions/compactionCheckpoint.js +12 -12
  69. package/dist/sdk/sessions/continuation.d.ts +4 -2
  70. package/dist/sdk/sessions/continuation.js +7 -6
  71. package/dist/sdk/sessions/contracts.d.ts +22 -4
  72. package/dist/sdk/sessions/contracts.js +12 -7
  73. package/dist/sdk/sessions/planMode.js +2 -2
  74. package/dist/sdk/sessions/runStore.js +3 -1
  75. package/dist/sdk/sessions/runner.js +2 -2
  76. package/dist/sdk/sessions/sessionState.d.ts +13 -0
  77. package/dist/sdk/sessions/sessionState.js +31 -0
  78. package/dist/sdk/sessions/sessions.d.ts +7 -0
  79. package/dist/sdk/sessions/sessions.js +73 -15
  80. package/dist/sdk/sessions/subagentService.d.ts +15 -0
  81. package/dist/sdk/sessions/subagentService.js +195 -55
  82. package/dist/sdk/sessions/subagents.d.ts +10 -1
  83. package/dist/sdk/sessions/subagents.js +4 -0
  84. package/dist/sdk/sessions/workSlice.d.ts +8 -5
  85. package/dist/sdk/sessions/workSlice.js +77 -45
  86. package/dist/sdk/terminal/controller.d.ts +20 -0
  87. package/dist/sdk/terminal/controller.js +43 -18
  88. package/dist/sdk/terminal/opentui/ansi.d.ts +25 -0
  89. package/dist/sdk/terminal/opentui/ansi.js +192 -0
  90. package/dist/sdk/terminal/opentui/model.d.ts +163 -0
  91. package/dist/sdk/terminal/opentui/model.js +482 -0
  92. package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +3 -3
  93. package/dist/sdk/terminal/opentui/subagentsWidget.js +7 -5
  94. package/dist/sdk/terminal/opentui/theme.d.ts +2 -0
  95. package/dist/sdk/terminal/opentui/theme.js +2 -0
  96. package/dist/sdk/terminal/piExtension.js +98 -15
  97. package/dist/sdk/terminal/subagentMonitor.d.ts +17 -4
  98. package/dist/sdk/terminal/subagentMonitor.js +29 -8
  99. package/dist/sdk/terminal/terminals.d.ts +16 -0
  100. package/dist/sdk/terminal/terminals.js +130 -26
  101. package/dist/sdk/terminal/trajectory.d.ts +6 -1
  102. package/dist/sdk/terminal/trajectory.js +8 -0
  103. package/dist/sdk/terminal/tui/annotateCommand.d.ts +5 -1
  104. package/dist/sdk/terminal/tui/annotateCommand.js +21 -7
  105. package/dist/sdk/terminal/tui/annotateSend.d.ts +30 -0
  106. package/dist/sdk/terminal/tui/annotateSend.js +40 -0
  107. package/dist/sdk/terminal/tui/chrome.d.ts +4 -0
  108. package/dist/sdk/terminal/tui/chrome.js +13 -0
  109. package/dist/sdk/terminal/tui/commandPages.d.ts +1 -1
  110. package/dist/sdk/terminal/tui/commandPages.js +16 -11
  111. package/dist/sdk/terminal/tui/commands.d.ts +4 -0
  112. package/dist/sdk/terminal/tui/commands.js +12 -2
  113. package/dist/sdk/terminal/tui/dialogs.js +1 -0
  114. package/dist/sdk/terminal/tui/pageOverlay.d.ts +3 -0
  115. package/dist/sdk/terminal/tui/pageOverlay.js +25 -7
  116. package/dist/sdk/terminal/tui/pages/agentsPage.js +5 -2
  117. package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +70 -16
  118. package/dist/sdk/terminal/tui/pages/annotatePage.js +349 -115
  119. package/dist/sdk/terminal/tui/pages/browserPage.js +5 -4
  120. package/dist/sdk/terminal/tui/pages/hubPage.d.ts +14 -1
  121. package/dist/sdk/terminal/tui/pages/hubPage.js +192 -28
  122. package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +14 -1
  123. package/dist/sdk/terminal/tui/pages/pluginsPage.js +295 -25
  124. package/dist/sdk/terminal/tui/pages/subagentsPage.js +8 -5
  125. package/dist/sdk/terminal/tui/pages/usagePage.d.ts +4 -2
  126. package/dist/sdk/terminal/tui/pages/usagePage.js +39 -12
  127. package/dist/sdk/terminal/tui/pluginCommands.d.ts +1 -1
  128. package/dist/sdk/terminal/tui/pluginCommands.js +13 -3
  129. package/dist/sdk/tools/defaultSkills.d.ts +4 -0
  130. package/dist/sdk/tools/defaultSkills.js +20 -0
  131. package/dist/sdk/tools/definitions.d.ts +3 -1
  132. package/dist/sdk/tools/definitions.js +17 -1
  133. package/dist/sdk/tools/dispatch.js +12 -0
  134. package/dist/sdk/tools/harnessTools.js +1 -1
  135. package/dist/sdk/tools/sessionName.d.ts +9 -0
  136. package/dist/sdk/tools/sessionName.js +58 -11
  137. package/dist/sdk/tools/spawnSubagent.js +9 -7
  138. package/dist/sdk/tools/todo.d.ts +15 -0
  139. package/dist/sdk/tools/todo.js +64 -0
  140. package/dist/sdk/world/world.d.ts +0 -2
  141. package/dist/terminal-opentui.js +3331 -1584
  142. package/dist/terminal-pi.js +421 -133
  143. package/docs/README.md +3 -0
  144. package/docs/architecture/00-overview.md +1 -1
  145. package/docs/architecture/01-extension-points.md +1 -1
  146. package/docs/architecture/02-sessions-and-runs.md +73 -36
  147. package/docs/architecture/03-variants.md +1 -3
  148. package/docs/architecture/04-prompt.md +24 -13
  149. package/docs/architecture/06-plugins-and-capabilities.md +46 -10
  150. package/docs/architecture/07-tools-and-gate.md +49 -8
  151. package/docs/architecture/09-accounts-and-connectors.md +8 -2
  152. package/docs/architecture/10-server-protocol.md +68 -16
  153. package/docs/architecture/11-terminal.md +94 -45
  154. package/docs/architecture/12-storage.md +1 -0
  155. package/docs/architecture/13-world-tools.md +8 -6
  156. package/docs/architecture/14-surfaces-and-parity.md +4 -1
  157. package/docs/architecture/README.md +20 -18
  158. package/docs/architecture/proposals/PROP-0006-run-checklist.md +2 -0
  159. package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +6 -2
  160. package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +32 -21
  161. package/docs/architecture/proposals/PROP-0044-work-slice-defaults.md +93 -0
  162. package/docs/architecture/proposals/PROP-0045-rename-checklist-to-todo.md +132 -0
  163. package/docs/architecture/proposals/PROP-0046-progress-aware-stall-guards.md +82 -0
  164. package/docs/architecture/proposals/PROP-0047-plugins-hub-separation.md +122 -0
  165. package/docs/architecture/proposals/PROP-0048-simple-annotate.md +85 -0
  166. package/docs/architecture/proposals/README.md +11 -6
  167. package/docs/decisions/ADR-0039-progress-aware-stall-guards.md +39 -0
  168. package/docs/decisions/ADR-0040-architecture-change-workflow.md +31 -0
  169. package/docs/decisions/ADR-0041-plugins-hub-separation.md +41 -0
  170. package/docs/decisions/ADR-0042-windows-shell-without-sandbox.md +40 -0
  171. package/docs/decisions/ADR-0043-parents-continue-stopped-subagents.md +49 -0
  172. package/docs/decisions/ADR-0044-annotations-pending-or-sent.md +24 -0
  173. package/docs/decisions/ADR-0045-default-plugin-authoring-skill.md +9 -0
  174. package/docs/decisions/ADR-0046-hash-bound-plugin-proposals.md +20 -0
  175. package/docs/decisions/ADR-0047-links-open-with-the-os-handler.md +47 -0
  176. package/docs/doctor.md +14 -1
  177. package/docs/parity.md +16 -13
  178. package/docs/releases/1.3.0.md +21 -0
  179. package/docs/releases/1.3.1-unstable.1.md +8 -0
  180. package/docs/releases/README.md +9 -6
  181. package/docs/terminal-tui.md +127 -65
  182. package/package.json +1 -1
  183. package/dist/sdk/terminal/tui/pages/notesPage.d.ts +0 -75
  184. package/dist/sdk/terminal/tui/pages/notesPage.js +0 -201
  185. package/dist/sdk/tools/checklist.d.ts +0 -9
  186. package/dist/sdk/tools/checklist.js +0 -55
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The Hub page: the reviewed catalog of skills, plugins and reusable documents. Installing asks first,
2
+ * The Hub page: the Community catalog and all installed Hub receipts. Installing asks first,
3
3
  * installs exactly the listed version, and leaves code switched off and untrusted.
4
4
  */
5
5
  import type { Theme } from '@earendil-works/pi-coding-agent';
@@ -15,12 +15,20 @@ export interface HubPageOptions {
15
15
  }
16
16
  export declare class HubPage implements TerminalPage {
17
17
  private readonly options;
18
+ readonly presentation = "fullscreen";
18
19
  private catalog;
19
20
  private section;
20
21
  private error;
22
+ private refreshError;
23
+ private refreshing;
24
+ private previewTitle;
25
+ private previewText;
26
+ private previewOffset;
27
+ private previewRows;
21
28
  private busy;
22
29
  private disposed;
23
30
  private readonly list;
31
+ private pointerLayout?;
24
32
  constructor(options: HubPageOptions);
25
33
  get focused(): boolean;
26
34
  set focused(value: boolean);
@@ -29,11 +37,16 @@ export declare class HubPage implements TerminalPage {
29
37
  tabs(): readonly TabInfo[];
30
38
  activeTab(): string;
31
39
  hint(): string;
40
+ private refreshOnOpen;
32
41
  private load;
33
42
  private fill;
34
43
  private rows;
35
44
  private activateRow;
45
+ private previewEntry;
46
+ private manageReceipt;
47
+ private status;
36
48
  handleMouse(event: TerminalMouseInput): import("@earendil-works/pi-tui").TuiMouseEventResult | undefined;
37
49
  handleInput(data: string): boolean;
50
+ private entryInspector;
38
51
  render(width: number, rows: number): string[];
39
52
  }
@@ -1,47 +1,87 @@
1
1
  import { matchesKey, truncateToWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
2
2
  import { installHubEntry } from "../../hubMenu.js";
3
- import { safeText } from "../chrome.js";
3
+ import { inspectorListWidth, safeText, splitColumns } from "../chrome.js";
4
4
  import { FilterList } from "../list.js";
5
5
  const SECTIONS = [
6
6
  { id: 'catalog', title: 'Catalog' },
7
7
  { id: 'installed', title: 'Installed' }
8
8
  ];
9
9
  const REFRESH = 'action:refresh';
10
+ const installedId = (id, version) => `installed:${id}@${version}`;
10
11
  export class HubPage {
11
12
  options;
13
+ presentation = 'fullscreen';
12
14
  catalog;
13
15
  section = 'catalog';
14
16
  error;
17
+ refreshError;
18
+ refreshing = false;
19
+ previewTitle;
20
+ previewText;
21
+ previewOffset = 0;
22
+ previewRows = 1;
15
23
  busy = false;
16
24
  disposed = false;
17
25
  list;
26
+ pointerLayout;
18
27
  constructor(options) {
19
28
  this.options = options;
20
29
  this.list = new FilterList({
21
- theme: options.theme, search: 'always', noun: ['entry', 'entries'], empty: 'The catalog has no entries.',
30
+ theme: options.theme, search: 'always', noun: ['entry', 'entries'], empty: 'No Community entries have been published yet.',
22
31
  onActivate: row => { void this.activateRow(row.id); }
23
32
  });
24
33
  }
25
34
  get focused() { return this.list.focused; }
26
35
  set focused(value) { this.list.focused = value; }
27
- activate() { if (!this.catalog)
28
- void this.load(false); }
36
+ activate() { void this.refreshOnOpen(); }
29
37
  dispose() { this.disposed = true; }
30
38
  tabs() { return SECTIONS; }
31
39
  activeTab() { return this.section; }
32
40
  hint() {
33
- return this.list.query ? 'Enter open · Esc clear' : this.section === 'catalog' ? 'Enter review and install · type to search' : 'Select an installed entry · type to search';
41
+ if (this.previewText !== undefined)
42
+ return '↑↓ scroll · PgUp/PgDn page · Esc back';
43
+ if (this.list.query)
44
+ return 'Enter open · Esc clear';
45
+ if (this.section === 'installed')
46
+ return 'Enter manage receipt · type to search';
47
+ const selectedId = this.list.selected?.id;
48
+ const entry = this.catalog?.entries.find(candidate => candidate.channel === 'community' && candidate.id + '@' + candidate.version === selectedId);
49
+ if (entry?.delivery.kind === 'pi-package')
50
+ return 'Enter install exact version · type to search';
51
+ return entry ? 'Enter preview or install · type to search' : 'Enter refresh catalog · type to search';
52
+ }
53
+ async refreshOnOpen() {
54
+ await this.load(false);
55
+ if (!this.disposed)
56
+ await this.load(true);
34
57
  }
35
58
  async load(refresh) {
59
+ if (refresh) {
60
+ this.refreshing = true;
61
+ this.options.changed();
62
+ }
36
63
  try {
37
64
  const catalog = await this.options.call('hub.list', refresh ? { refresh: true } : {});
38
65
  this.catalog = catalog;
39
66
  this.error = undefined;
40
- if (refresh && catalog.error)
41
- this.options.ui.notify(`${catalog.error} Using ${catalog.origin} content.`, 'warning');
67
+ if (refresh) {
68
+ this.refreshError = catalog.error ?? undefined;
69
+ if (catalog.error)
70
+ this.options.ui.notify(`${catalog.error} Using ${catalog.origin} content.`, 'warning');
71
+ }
72
+ else if (catalog.error) {
73
+ this.refreshError = catalog.error;
74
+ }
42
75
  }
43
76
  catch (error) {
44
- this.error = error instanceof Error ? error.message : String(error);
77
+ const message = error instanceof Error ? error.message : String(error);
78
+ if (refresh)
79
+ this.refreshError = message;
80
+ else if (!this.catalog)
81
+ this.error = message;
82
+ }
83
+ finally {
84
+ this.refreshing = false;
45
85
  }
46
86
  if (this.disposed)
47
87
  return;
@@ -53,22 +93,24 @@ export class HubPage {
53
93
  }
54
94
  rows() {
55
95
  if (this.section === 'installed') {
96
+ const receipts = this.catalog?.installed ?? [];
56
97
  return [
57
- ...(this.catalog?.installed ?? []).map(receipt => ({
58
- id: 'installed:' + receipt.entry.id + '@' + receipt.entry.version,
98
+ ...receipts.map(receipt => ({
99
+ id: installedId(receipt.entry.id, receipt.entry.version),
59
100
  label: safeText(receipt.entry.title),
60
- group: receipt.entry.category,
61
- detail: (receipt.state ?? 'installed') + ' · ' + receipt.entry.version,
101
+ group: safeText(receipt.entry.category),
102
+ detail: `${receipt.entry.channel} · ${receipt.state ?? 'installed'} · ${receipt.entry.version}`,
62
103
  tone: receipt.state === 'installed' ? 'success' : 'warning',
63
- searchText: receipt.entry.title + ' ' + receipt.entry.category + ' ' + receipt.entry.id + ' ' + receipt.entry.publisher + ' ' + (receipt.state ?? '')
64
- }))
104
+ searchText: `${receipt.entry.title} ${receipt.entry.category} ${receipt.entry.id} ${receipt.entry.publisher} ${receipt.entry.channel} ${receipt.state ?? ''}`
105
+ })),
106
+ { id: REFRESH, label: '⟳ Refresh catalog', group: 'Actions', tone: 'accent' }
65
107
  ];
66
108
  }
67
- const entries = [...(this.catalog?.entries ?? [])].sort((left, right) => left.category.localeCompare(right.category));
109
+ const entries = (this.catalog?.entries ?? []).filter(entry => entry.channel === 'community').sort((left, right) => left.category.localeCompare(right.category));
68
110
  return [
69
111
  ...entries.map(entry => ({
70
112
  id: entry.id + '@' + entry.version, label: safeText(entry.title), group: safeText(entry.category),
71
- detail: entry.version, tone: 'muted', searchText: entry.title + ' ' + entry.category + ' ' + entry.id + ' ' + entry.publisher
113
+ detail: entry.version, tone: 'muted', searchText: `${entry.title} ${entry.category} ${entry.id} ${entry.publisher} ${entry.tags.join(' ')}`
72
114
  })),
73
115
  { id: REFRESH, label: '⟳ Refresh catalog', group: 'Actions', tone: 'accent' }
74
116
  ];
@@ -82,20 +124,103 @@ export class HubPage {
82
124
  await this.load(true);
83
125
  return;
84
126
  }
85
- if (id.startsWith('installed:'))
127
+ if (id.startsWith('installed:')) {
128
+ const receipt = this.catalog?.installed.find(candidate => installedId(candidate.entry.id, candidate.entry.version) === id);
129
+ if (receipt)
130
+ await this.manageReceipt(receipt);
86
131
  return;
87
- const entry = this.catalog?.entries.find(candidate => candidate.id + '@' + candidate.version === id);
88
- if (entry)
89
- await installHubEntry(this.options.ui, this.options.call, entry);
132
+ }
133
+ const entry = this.catalog?.entries.find(candidate => candidate.channel === 'community' && candidate.id + '@' + candidate.version === id);
134
+ if (entry) {
135
+ const installAction = `Install exact version ${entry.version}`;
136
+ const actions = entry.delivery.kind === 'pi-package'
137
+ ? [installAction, 'Back']
138
+ : ['Preview reviewed content', installAction, 'Back'];
139
+ const action = await this.options.ui.select(`${safeText(entry.title)} · ${entry.version}`, actions);
140
+ if (action === 'Preview reviewed content')
141
+ await this.previewEntry(entry);
142
+ else if (action === installAction) {
143
+ await installHubEntry(this.options.ui, this.options.call, entry);
144
+ await this.load(false);
145
+ }
146
+ }
90
147
  }
91
148
  finally {
92
149
  this.busy = false;
93
150
  }
94
151
  }
152
+ async previewEntry(entry) {
153
+ if (entry.delivery.kind === 'pi-package')
154
+ return;
155
+ try {
156
+ const page = await this.options.call('terminal.browser.hubPreview', { id: entry.id, version: entry.version });
157
+ this.previewTitle = safeText(entry.title);
158
+ this.previewText = page.content + (page.done ? '' : '\n\n[Preview limited to first page.]');
159
+ this.previewOffset = 0;
160
+ this.pointerLayout = undefined;
161
+ this.options.changed();
162
+ }
163
+ catch (error) {
164
+ this.options.ui.notify(error instanceof Error ? error.message : String(error), 'error');
165
+ }
166
+ }
167
+ async manageReceipt(receipt) {
168
+ const action = await this.options.ui.select(`${receipt.entry.title} · ${receipt.entry.channel} · ${receipt.entry.version}`, ['Uninstall this version', 'Back']);
169
+ if (action !== 'Uninstall this version')
170
+ return;
171
+ const confirmed = await this.options.ui.confirm(`Uninstall ${receipt.entry.title}?`, `This removes only resources recorded by the ${receipt.entry.channel} receipt ${receipt.entry.id}@${receipt.entry.version} and revokes their attachment choices.`);
172
+ if (!confirmed)
173
+ return;
174
+ try {
175
+ await this.options.call('hub.uninstall', { id: receipt.entry.id, version: receipt.entry.version });
176
+ this.options.ui.notify(`Uninstalled ${receipt.entry.title} ${receipt.entry.version}.`, 'info');
177
+ await this.load(false);
178
+ }
179
+ catch (error) {
180
+ this.options.ui.notify(error instanceof Error ? error.message : String(error), 'error');
181
+ }
182
+ }
183
+ status() {
184
+ const catalog = this.catalog;
185
+ const error = this.refreshError ?? catalog.error;
186
+ const lastRefresh = catalog.refreshedAt ? ` · last refreshed ${catalog.refreshedAt}` : '';
187
+ if (this.refreshing)
188
+ return { text: `${catalog.origin} catalog · refreshing…${lastRefresh}`, tone: 'muted' };
189
+ if (error)
190
+ return { text: `${catalog.origin} catalog · refresh failed: ${error}${lastRefresh}`, tone: 'warning' };
191
+ return { text: `${catalog.origin} catalog${catalog.refreshedAt ? ` · refreshed ${catalog.refreshedAt}` : ''}`, tone: 'dim' };
192
+ }
95
193
  handleMouse(event) {
96
- return this.list.handleMouse(event);
194
+ if (this.previewText !== undefined)
195
+ return { handled: true, render: false };
196
+ const layout = this.pointerLayout;
197
+ if (!layout)
198
+ return undefined;
199
+ if (event.x < 0 || event.x >= layout.width || event.y < layout.top || event.y >= layout.top + layout.rows)
200
+ return { handled: true, render: false };
201
+ return this.list.handleMouse({ ...event, y: event.y - layout.top });
97
202
  }
98
203
  handleInput(data) {
204
+ if (this.previewText !== undefined) {
205
+ if (matchesKey(data, 'escape')) {
206
+ this.previewTitle = undefined;
207
+ this.previewText = undefined;
208
+ this.previewOffset = 0;
209
+ this.options.changed();
210
+ return true;
211
+ }
212
+ const page = Math.max(1, this.previewRows);
213
+ const delta = matchesKey(data, 'down') ? 1 : matchesKey(data, 'up') ? -1
214
+ : matchesKey(data, 'pageDown') ? page : matchesKey(data, 'pageUp') ? -page : 0;
215
+ if (matchesKey(data, 'home'))
216
+ this.previewOffset = 0;
217
+ else if (matchesKey(data, 'end'))
218
+ this.previewOffset = Number.MAX_SAFE_INTEGER;
219
+ else
220
+ this.previewOffset += delta;
221
+ this.options.changed();
222
+ return true;
223
+ }
99
224
  if (matchesKey(data, 'tab') || matchesKey(data, 'shift+tab')) {
100
225
  if (!this.list.query) {
101
226
  this.section = this.section === 'catalog' ? 'installed' : 'catalog';
@@ -105,22 +230,61 @@ export class HubPage {
105
230
  }
106
231
  return this.list.handleInput(data);
107
232
  }
233
+ entryInspector(entry, installed, width, rows) {
234
+ const theme = this.options.theme();
235
+ if (width < 1 || rows < 1)
236
+ return [];
237
+ if (!entry)
238
+ return [theme.fg('muted', 'Select an entry to review its publisher, license, and version.')].slice(0, rows);
239
+ const lines = [theme.bold(safeText(entry.title)), '', ...wrapTextWithAnsi(safeText(entry.description), width), ''];
240
+ lines.push(`Category: ${safeText(entry.category)}`, `Publisher: ${safeText(entry.publisher)}`, `License: ${safeText(entry.license)}`, `Version: ${safeText(entry.version)}`, `Channel: ${safeText(entry.channel)}`, `ID: ${safeText(entry.id)}`);
241
+ lines.push('', ...wrapTextWithAnsi(theme.fg('dim', `Source: ${safeText(entry.sourceUrl)}`), width));
242
+ if (installed)
243
+ lines.push('', `Installed: ${safeText(installed.installedAt)}`, `Receipt: ${safeText(installed.state ?? 'installed')} · ${safeText(installed.entry.channel)}`);
244
+ if (entry.delivery.kind === 'pi-package')
245
+ lines.push('', theme.fg('warning', 'Package code stays switched off and untrusted until reviewed.'));
246
+ return lines.slice(0, rows).map(line => truncateToWidth(line, width, ''));
247
+ }
108
248
  render(width, rows) {
109
249
  const theme = this.options.theme();
110
250
  if (width < 1 || rows < 1)
111
251
  return [];
112
- if (!this.catalog)
113
- return [theme.fg(this.error ? 'error' : 'dim', this.error ? 'Could not read the catalog: ' + this.error : 'Reading the catalog…')];
252
+ if (this.previewText !== undefined) {
253
+ const content = this.previewText.split('\n').flatMap(line => wrapTextWithAnsi(safeText(line), width));
254
+ this.previewRows = Math.max(1, rows - 1);
255
+ this.previewOffset = Math.min(this.previewOffset, Math.max(0, content.length - this.previewRows));
256
+ const header = theme.bold(`Preview · ${this.previewTitle ?? 'Hub entry'} · ${this.previewOffset + 1}/${Math.max(1, content.length)}`);
257
+ return [header, ...content.slice(this.previewOffset, this.previewOffset + this.previewRows)]
258
+ .slice(0, rows).map(line => truncateToWidth(line, width, ''));
259
+ }
260
+ if (!this.catalog) {
261
+ this.pointerLayout = undefined;
262
+ return [theme.fg(this.error ? 'error' : 'dim', this.error ? 'Could not read the catalog: ' + safeText(this.error) : 'Reading the catalog…')];
263
+ }
114
264
  const selectedId = this.list.selected?.id;
115
265
  const installed = this.section === 'installed'
116
- ? this.catalog.installed.find(receipt => 'installed:' + receipt.entry.id + '@' + receipt.entry.version === selectedId)
266
+ ? this.catalog.installed.find(receipt => installedId(receipt.entry.id, receipt.entry.version) === selectedId)
117
267
  : undefined;
118
- const entry = installed?.entry ?? this.catalog.entries.find(candidate => candidate.id + '@' + candidate.version === selectedId);
268
+ const entry = installed?.entry ?? this.catalog.entries.find(candidate => candidate.channel === 'community' && candidate.id + '@' + candidate.version === selectedId);
269
+ const status = this.status();
270
+ const statusLine = theme.fg(status.tone, truncateToWidth(safeText(status.text), width, ''));
271
+ const paneWidth = inspectorListWidth(width);
272
+ if (paneWidth !== undefined) {
273
+ const bodyRows = Math.max(0, rows - 1);
274
+ const rightWidth = Math.max(0, width - paneWidth - 3);
275
+ const listLines = this.list.render(paneWidth, bodyRows);
276
+ this.pointerLayout = { width: paneWidth, top: 1, rows: listLines.length };
277
+ return [statusLine, ...splitColumns(listLines, this.entryInspector(entry, installed, rightWidth, bodyRows), paneWidth, width, bodyRows, theme)]
278
+ .slice(0, rows).map(line => truncateToWidth(line, width, ''));
279
+ }
119
280
  const detail = entry && rows >= 9
120
- ? [...wrapTextWithAnsi(safeText(entry.description), width).slice(0, 3), entry.publisher + ' · ' + entry.license + ' · ' + entry.id,
121
- ...(installed ? ['Installed ' + installed.installedAt + ' · ' + (installed.state ?? 'installed')] : [])]
281
+ ? [...wrapTextWithAnsi(safeText(entry.description), width).slice(0, 3), `${entry.publisher} · ${entry.license} · ${entry.id}`,
282
+ ...(installed ? [`Installed ${installed.installedAt} · ${installed.entry.channel} · ${installed.state ?? 'installed'}`] : [])]
122
283
  : [];
123
- const lines = this.list.render(width, Math.max(2, rows - (detail.length ? detail.length + 1 : 0)));
284
+ const detailHeight = detail.length ? detail.length + 1 : 0;
285
+ const listLines = this.list.render(width, Math.max(1, rows - 1 - detailHeight));
286
+ this.pointerLayout = { width, top: 1, rows: Math.min(listLines.length, Math.max(0, rows - 1)) };
287
+ const lines = [statusLine, ...listLines];
124
288
  if (detail.length)
125
289
  lines.push('', ...detail.map(line => theme.fg('muted', safeText(line))));
126
290
  return lines.slice(0, rows).map(line => truncateToWidth(line, width, ''));
@@ -13,14 +13,22 @@ export interface PluginsPageOptions {
13
13
  }
14
14
  export declare class PluginsPage implements TerminalPage {
15
15
  private readonly options;
16
+ readonly presentation = "fullscreen";
16
17
  private plugins;
18
+ private proposals;
19
+ private proposalError;
20
+ private preview;
21
+ private previewOffset;
22
+ private previewRows;
17
23
  private section;
18
24
  private projectId;
25
+ private hubManagedIds;
19
26
  private error;
20
27
  private restart;
21
28
  private busy;
22
29
  private disposed;
23
30
  private readonly list;
31
+ private pointerLayout?;
24
32
  /** The plugin whose settings are open, or null on the list. */
25
33
  private settingsFor;
26
34
  private saved;
@@ -36,17 +44,22 @@ export declare class PluginsPage implements TerminalPage {
36
44
  private settingsPlugin;
37
45
  private reload;
38
46
  private fill;
39
- /** The menu rows, with a Settings row after the last row of each plugin that has settings. */
47
+ /** Switch, trust, settings and package actions stay grouped under their owning plugin. */
40
48
  private rows;
41
49
  private menu;
42
50
  private key;
43
51
  private toRow;
44
52
  private open;
53
+ private decideProposal;
54
+ private installPackage;
55
+ private managePackage;
45
56
  private back;
46
57
  /** Flips a switch, or asks for a number inside the setting's range, and saves it. */
47
58
  private change;
48
59
  handleMouse(event: TerminalMouseInput): import("@earendil-works/pi-tui").TuiMouseEventResult | undefined;
49
60
  handleInput(data: string): boolean;
61
+ private selectedPlugin;
62
+ private pluginInspector;
50
63
  render(width: number, rows: number): string[];
51
64
  private renderSettings;
52
65
  }