@xingular/harness 1.1.1 → 1.3.1-unstable.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +36 -28
- package/dist/cli.js +10837 -6159
- package/dist/sdk/accounts/accounts.d.ts +27 -3
- package/dist/sdk/accounts/accounts.js +78 -24
- package/dist/sdk/accounts/secrets.d.ts +12 -0
- package/dist/sdk/accounts/secrets.js +114 -7
- package/dist/sdk/accounts/store.d.ts +26 -1
- package/dist/sdk/accounts/store.js +1 -1
- package/dist/sdk/agents/builtin.js +18 -4
- package/dist/sdk/agents/planResearch.d.ts +27 -0
- package/dist/sdk/agents/planResearch.js +82 -0
- package/dist/sdk/agents/profiles.d.ts +5 -1
- package/dist/sdk/agents/profiles.js +21 -5
- package/dist/sdk/annotations/pageScript.js +349 -48
- package/dist/sdk/annotations/part.js +34 -23
- package/dist/sdk/annotations/store.d.ts +7 -0
- package/dist/sdk/annotations/store.js +13 -5
- package/dist/sdk/browser/backend.d.ts +16 -0
- package/dist/sdk/browser/backend.js +17 -0
- package/dist/sdk/browser/contracts.d.ts +19 -0
- package/dist/sdk/browser/contracts.js +116 -0
- package/dist/sdk/browser/tools.d.ts +1 -4
- package/dist/sdk/browser/tools.js +14 -91
- package/dist/sdk/capabilities/runSnapshot.js +10 -4
- package/dist/sdk/cli.js +5 -4
- package/dist/sdk/commands/list.d.ts +3 -0
- package/dist/sdk/commands/list.js +75 -0
- package/dist/sdk/commands/registry.d.ts +258 -0
- package/dist/sdk/commands/registry.js +48 -0
- package/dist/sdk/commands/routes.d.ts +16 -0
- package/dist/sdk/commands/routes.js +33 -0
- package/dist/sdk/core/commandBudget.d.ts +23 -0
- package/dist/sdk/core/commandBudget.js +57 -0
- package/dist/sdk/core/index.js +17 -1
- package/dist/sdk/core/interactiveProfile.d.ts +46 -0
- package/dist/sdk/core/interactiveProfile.js +143 -0
- package/dist/sdk/core/shell.d.ts +20 -0
- package/dist/sdk/core/shell.js +73 -0
- package/dist/sdk/core/workspace.d.ts +13 -0
- package/dist/sdk/core/workspace.js +88 -0
- package/dist/sdk/core/workspaceBashTool.d.ts +20 -0
- package/dist/sdk/core/workspaceBashTool.js +50 -0
- package/dist/sdk/core/workspaceSandbox.d.ts +41 -0
- package/dist/sdk/core/workspaceSandbox.js +360 -0
- package/dist/sdk/core/workspaceWriteOperations.d.ts +33 -0
- package/dist/sdk/core/workspaceWriteOperations.js +224 -0
- package/dist/sdk/doctor/checks.js +71 -12
- package/dist/sdk/doctor/types.d.ts +2 -2
- package/dist/sdk/harness.d.ts +7 -0
- package/dist/sdk/harness.js +42 -2
- package/dist/sdk/hub/hub.js +5 -3
- package/dist/sdk/index.d.ts +8 -3
- package/dist/sdk/index.js +4 -1
- package/dist/sdk/pi/runtime.d.ts +2 -1
- package/dist/sdk/pi/runtime.js +2 -2
- package/dist/sdk/plugins/builtin.js +32 -15
- package/dist/sdk/plugins/builtinRegistry.d.ts +10 -3
- package/dist/sdk/plugins/builtinRegistry.js +30 -8
- package/dist/sdk/plugins/catalogue.js +2 -1
- package/dist/sdk/plugins/commandPrefix.d.ts +8 -0
- package/dist/sdk/plugins/commandPrefix.js +21 -0
- package/dist/sdk/plugins/firstParty.d.ts +1 -1
- package/dist/sdk/plugins/firstParty.js +8 -2
- package/dist/sdk/plugins/install.d.ts +6 -2
- package/dist/sdk/plugins/install.js +29 -5
- package/dist/sdk/plugins/inventory.js +6 -5
- package/dist/sdk/plugins/manifest.d.ts +2 -0
- package/dist/sdk/plugins/manifest.js +2 -1
- package/dist/sdk/plugins/surfaces.d.ts +5 -0
- package/dist/sdk/plugins/surfaces.js +11 -0
- package/dist/sdk/plugins/types.d.ts +15 -0
- package/dist/sdk/prompt/compile.d.ts +14 -0
- package/dist/sdk/prompt/compile.js +65 -31
- package/dist/sdk/prompt/standard.d.ts +17 -0
- package/dist/sdk/prompt/standard.js +21 -0
- package/dist/sdk/server/methods.js +22 -2
- package/dist/sdk/server/remote.js +36 -11
- package/dist/sdk/sessions/compactionCheckpoint.d.ts +11 -0
- package/dist/sdk/sessions/compactionCheckpoint.js +79 -0
- package/dist/sdk/sessions/continuation.d.ts +7 -2
- package/dist/sdk/sessions/continuation.js +11 -6
- package/dist/sdk/sessions/contracts.d.ts +190 -4
- package/dist/sdk/sessions/contracts.js +71 -8
- package/dist/sdk/sessions/control.d.ts +72 -0
- package/dist/sdk/sessions/control.js +158 -0
- package/dist/sdk/sessions/initialBash.d.ts +7 -0
- package/dist/sdk/sessions/initialBash.js +7 -0
- package/dist/sdk/sessions/planMode.d.ts +14 -3
- package/dist/sdk/sessions/planMode.js +74 -17
- package/dist/sdk/sessions/registry.d.ts +1 -1
- package/dist/sdk/sessions/registry.js +2 -2
- package/dist/sdk/sessions/runStore.d.ts +14 -0
- package/dist/sdk/sessions/runStore.js +38 -1
- package/dist/sdk/sessions/runner.d.ts +40 -2
- package/dist/sdk/sessions/runner.js +161 -13
- package/dist/sdk/sessions/sessions.d.ts +23 -3
- package/dist/sdk/sessions/sessions.js +225 -22
- package/dist/sdk/sessions/subagentService.d.ts +24 -0
- package/dist/sdk/sessions/subagentService.js +225 -26
- package/dist/sdk/sessions/subagents.d.ts +10 -1
- package/dist/sdk/sessions/subagents.js +4 -0
- package/dist/sdk/sessions/workSlice.d.ts +106 -0
- package/dist/sdk/sessions/workSlice.js +329 -0
- package/dist/sdk/sessions/workspaceBoundary.d.ts +15 -0
- package/dist/sdk/sessions/workspaceBoundary.js +46 -0
- package/dist/sdk/terminal/browserControl.js +2 -1
- package/dist/sdk/terminal/controller.d.ts +31 -3
- package/dist/sdk/terminal/controller.js +81 -29
- package/dist/sdk/terminal/opentui/ansi.d.ts +25 -0
- package/dist/sdk/terminal/opentui/ansi.js +192 -0
- package/dist/sdk/terminal/opentui/model.d.ts +163 -0
- package/dist/sdk/terminal/opentui/model.js +482 -0
- package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +3 -3
- package/dist/sdk/terminal/opentui/subagentsWidget.js +7 -5
- package/dist/sdk/terminal/opentui/theme.d.ts +43 -0
- package/dist/sdk/terminal/opentui/theme.js +88 -0
- package/dist/sdk/terminal/piExtension.d.ts +6 -1
- package/dist/sdk/terminal/piExtension.js +177 -63
- package/dist/sdk/terminal/standalone.d.ts +7 -0
- package/dist/sdk/terminal/standalone.js +72 -13
- package/dist/sdk/terminal/subagentMonitor.d.ts +28 -6
- package/dist/sdk/terminal/subagentMonitor.js +61 -9
- package/dist/sdk/terminal/terminals.d.ts +26 -2
- package/dist/sdk/terminal/terminals.js +325 -43
- package/dist/sdk/terminal/trajectory.d.ts +6 -1
- package/dist/sdk/terminal/trajectory.js +8 -0
- package/dist/sdk/terminal/tui/annotateCommand.d.ts +1 -1
- package/dist/sdk/terminal/tui/annotateCommand.js +3 -3
- package/dist/sdk/terminal/tui/chrome.d.ts +4 -0
- package/dist/sdk/terminal/tui/chrome.js +13 -0
- package/dist/sdk/terminal/tui/commandPages.d.ts +3 -1
- package/dist/sdk/terminal/tui/commandPages.js +17 -4
- package/dist/sdk/terminal/tui/commands.d.ts +6 -0
- package/dist/sdk/terminal/tui/commands.js +213 -124
- package/dist/sdk/terminal/tui/list.d.ts +14 -1
- package/dist/sdk/terminal/tui/list.js +96 -14
- package/dist/sdk/terminal/tui/pageOverlay.js +21 -5
- package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +1 -1
- package/dist/sdk/terminal/tui/pages/browserPage.js +5 -4
- package/dist/sdk/terminal/tui/pages/hubPage.d.ts +14 -1
- package/dist/sdk/terminal/tui/pages/hubPage.js +192 -28
- package/dist/sdk/terminal/tui/pages/notesPage.d.ts +5 -5
- package/dist/sdk/terminal/tui/pages/notesPage.js +18 -18
- package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +8 -6
- package/dist/sdk/terminal/tui/pages/pluginsPage.js +206 -28
- package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +1 -0
- package/dist/sdk/terminal/tui/pages/subagentsPage.js +104 -19
- package/dist/sdk/terminal/tui/pages/usagePage.d.ts +43 -0
- package/dist/sdk/terminal/tui/pages/usagePage.js +370 -0
- package/dist/sdk/terminal/tui/piAdapter.d.ts +1 -2
- package/dist/sdk/terminal/tui/piAdapter.js +3 -5
- package/dist/sdk/terminal/tui/pluginCommands.d.ts +2 -0
- package/dist/sdk/terminal/tui/pluginCommands.js +13 -9
- package/dist/sdk/terminal/tui/runtime.js +23 -3
- package/dist/sdk/terminal/tui/sessionPicker.d.ts +40 -0
- package/dist/sdk/terminal/tui/sessionPicker.js +141 -0
- package/dist/sdk/tools/checkpoint.d.ts +27 -0
- package/dist/sdk/tools/checkpoint.js +154 -0
- package/dist/sdk/tools/getCapabilities.d.ts +31 -0
- package/dist/sdk/tools/getCapabilities.js +34 -0
- package/dist/sdk/tools/sessionName.d.ts +2 -0
- package/dist/sdk/tools/sessionName.js +5 -3
- package/dist/sdk/tools/spawnSubagent.js +11 -8
- package/dist/sdk/tools/todo.d.ts +15 -0
- package/dist/sdk/tools/todo.js +64 -0
- package/dist/sdk/usage/analytics.d.ts +20 -0
- package/dist/sdk/usage/analytics.js +151 -0
- package/dist/sdk/usage/ledger.d.ts +38 -0
- package/dist/sdk/usage/ledger.js +142 -0
- package/dist/sdk/usage/limits/adapters.d.ts +11 -0
- package/dist/sdk/usage/limits/adapters.js +349 -0
- package/dist/sdk/usage/limits/capture.d.ts +8 -0
- package/dist/sdk/usage/limits/capture.js +47 -0
- package/dist/sdk/usage/limits/http.d.ts +12 -0
- package/dist/sdk/usage/limits/http.js +51 -0
- package/dist/sdk/usage/limits/normalize.d.ts +15 -0
- package/dist/sdk/usage/limits/normalize.js +211 -0
- package/dist/sdk/usage/limits/service.d.ts +30 -0
- package/dist/sdk/usage/limits/service.js +176 -0
- package/dist/sdk/usage/limits/types.d.ts +65 -0
- package/dist/sdk/usage/limits/types.js +1 -0
- package/dist/sdk/usage/serverMethods.d.ts +3 -0
- package/dist/sdk/usage/serverMethods.js +22 -0
- package/dist/sdk/usage/service.d.ts +32 -0
- package/dist/sdk/usage/service.js +66 -0
- package/dist/sdk/usage/settings.d.ts +7 -0
- package/dist/sdk/usage/settings.js +28 -0
- package/dist/sdk/usage/types.d.ts +60 -0
- package/dist/sdk/usage/types.js +4 -0
- package/dist/terminal-opentui.js +9204 -4812
- package/dist/terminal-pi.js +1848 -272
- package/docs/architecture/00-overview.md +1 -1
- package/docs/architecture/01-extension-points.md +1 -1
- package/docs/architecture/02-sessions-and-runs.md +169 -36
- package/docs/architecture/04-prompt.md +55 -15
- package/docs/architecture/06-plugins-and-capabilities.md +36 -15
- package/docs/architecture/07-tools-and-gate.md +131 -25
- package/docs/architecture/08-memory-and-history.md +4 -2
- package/docs/architecture/09-accounts-and-connectors.md +39 -3
- package/docs/architecture/10-server-protocol.md +71 -9
- package/docs/architecture/11-terminal.md +148 -60
- package/docs/architecture/12-storage.md +30 -2
- package/docs/architecture/13-world-tools.md +22 -1
- package/docs/architecture/14-surfaces-and-parity.md +58 -8
- package/docs/architecture/README.md +20 -17
- package/docs/architecture/proposals/PROP-0006-run-checklist.md +2 -0
- package/docs/architecture/proposals/PROP-0017-page-annotations.md +6 -2
- package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +1 -1
- package/docs/architecture/proposals/PROP-0031-plugin-command-owner-namespaces.md +54 -0
- package/docs/architecture/proposals/PROP-0032-planning-quality.md +196 -0
- package/docs/architecture/proposals/PROP-0033-compaction-plan-checkpoints.md +75 -0
- package/docs/architecture/proposals/PROP-0034-conversation-fast-mode.md +120 -0
- package/docs/architecture/proposals/PROP-0035-retain-subagent-status.md +47 -0
- package/docs/architecture/proposals/PROP-0036-first-class-harness-commands.md +59 -0
- package/docs/architecture/proposals/PROP-0037-workspace-write-boundary.md +63 -0
- package/docs/architecture/proposals/PROP-0038-annotate-open-shadow-roots.md +73 -0
- package/docs/architecture/proposals/PROP-0039-workspace-boundary-hardening.md +139 -0
- package/docs/architecture/proposals/PROP-0040-one-launch-path.md +87 -0
- package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +258 -0
- package/docs/architecture/proposals/PROP-0042-usage-and-provider-limits.md +127 -0
- package/docs/architecture/proposals/PROP-0043-read-only-plan-bash.md +74 -0
- package/docs/architecture/proposals/PROP-0044-work-slice-defaults.md +93 -0
- package/docs/architecture/proposals/PROP-0045-rename-checklist-to-todo.md +132 -0
- package/docs/architecture/proposals/PROP-0046-progress-aware-stall-guards.md +82 -0
- package/docs/architecture/proposals/PROP-0047-plugins-hub-separation.md +122 -0
- package/docs/architecture/proposals/README.md +24 -6
- package/docs/decisions/ADR-0001-owned-terminal.md +1 -1
- package/docs/decisions/ADR-0021-required-command-namespaces.md +1 -1
- package/docs/decisions/ADR-0031-plugin-command-owner-namespaces.md +37 -0
- package/docs/decisions/ADR-0032-compaction-run-checkpoints.md +33 -0
- package/docs/decisions/ADR-0033-plan-mode-research-delegation.md +47 -0
- package/docs/decisions/ADR-0034-first-class-harness-terminal-commands.md +25 -0
- package/docs/decisions/ADR-0035-one-launch-path.md +40 -0
- package/docs/decisions/ADR-0036-workspace-write-operation-boundary.md +54 -0
- package/docs/decisions/ADR-0037-usage-ledger-and-provider-limits.md +44 -0
- package/docs/decisions/ADR-0038-plan-bash-os-read-only.md +29 -0
- package/docs/decisions/ADR-0039-progress-aware-stall-guards.md +39 -0
- package/docs/decisions/ADR-0040-architecture-change-workflow.md +31 -0
- package/docs/decisions/ADR-0041-plugins-hub-separation.md +41 -0
- package/docs/decisions/ADR-0042-windows-shell-without-sandbox.md +40 -0
- package/docs/decisions/ADR-0043-parents-continue-stopped-subagents.md +49 -0
- package/docs/doctor.md +14 -1
- package/docs/experiments/EXP-0003-performance-audit/data/session-runtime-replication.json +420 -0
- package/docs/experiments/EXP-0003-performance-audit/data/session-runtime.json +420 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-compact.json +376 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-history.json +422 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-question.json +95 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-wide.json +1036 -0
- package/docs/experiments/EXP-0003-performance-audit/plan.md +109 -0
- package/docs/experiments/EXP-0003-performance-audit/results.md +151 -0
- package/docs/handoff-2026-10-25-app-parity-and-solo-tabs.md +202 -0
- package/docs/parity.md +83 -27
- package/docs/releases/1.1.1.md +28 -0
- package/docs/releases/1.2.0.md +49 -0
- package/docs/releases/1.3.0.md +21 -0
- package/docs/releases/4.0.0.md +10 -9
- package/docs/releases/README.md +35 -30
- package/docs/study/00-index.md +3 -0
- package/docs/study/STUDY-0003-long-horizon-runs.md +210 -0
- package/docs/terminal-tui.md +162 -82
- package/package.json +4 -3
- package/dist/sdk/tools/checklist.d.ts +0 -9
- package/dist/sdk/tools/checklist.js +0 -54
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fuzzyFilter, matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
1
|
+
import { fuzzyFilter, matchesKey, parseColor, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
2
2
|
import { safeText, selectedRow, selectionMuted, selectionText } from "./chrome.js";
|
|
3
3
|
import { SEARCH_FIELD_ROWS, SearchField, searchFieldRowsFor } from "./search.js";
|
|
4
4
|
import { isPrimaryClick } from "./mouse.js";
|
|
@@ -25,7 +25,10 @@ export class FilterList {
|
|
|
25
25
|
get total() { return this.rows.length; }
|
|
26
26
|
get shown() { return this.visible.length; }
|
|
27
27
|
/** Rows the list would use if nothing cut it: the search field, headings and rows. */
|
|
28
|
-
get height() {
|
|
28
|
+
get height() {
|
|
29
|
+
return (this.searchVisible() ? this.renderedSearchRows : 0)
|
|
30
|
+
+ this.entries().reduce((height, entry) => height + (entry.row?.subtitle === undefined ? 1 : 2), 0);
|
|
31
|
+
}
|
|
29
32
|
/** Rows at the top of `render` that a frame must keep when it has to cut the list. */
|
|
30
33
|
get pinnedRows() { return this.searchVisible() ? this.renderedSearchRows : 0; }
|
|
31
34
|
setRows(rows) {
|
|
@@ -61,6 +64,10 @@ export class FilterList {
|
|
|
61
64
|
}
|
|
62
65
|
invalidate() { this.search.invalidate(); }
|
|
63
66
|
limit() { return Math.max(1, Math.floor(this.options.limit?.() ?? Number.POSITIVE_INFINITY)); }
|
|
67
|
+
rowGap() {
|
|
68
|
+
const gap = this.options.rowGap ?? 0;
|
|
69
|
+
return Number.isFinite(gap) ? Math.max(0, Math.floor(gap)) : 0;
|
|
70
|
+
}
|
|
64
71
|
mode() { return this.options.search ?? 'always'; }
|
|
65
72
|
canSearch() { return this.mode() === 'always'; }
|
|
66
73
|
searchVisible() { return this.canSearch() || this.query.length > 0; }
|
|
@@ -68,7 +75,7 @@ export class FilterList {
|
|
|
68
75
|
refilter() {
|
|
69
76
|
const query = this.query.trim();
|
|
70
77
|
this.visible = query
|
|
71
|
-
? fuzzyFilter(this.rows, query, row => row.searchText ?? `${row.label} ${row.detail ?? ''} ${row.group ?? ''}`)
|
|
78
|
+
? fuzzyFilter(this.rows, query, row => row.searchText ?? `${row.label} ${row.subtitle ?? ''} ${row.detail ?? ''} ${row.group ?? ''}`)
|
|
72
79
|
: this.rows;
|
|
73
80
|
if (!this.visible.some(row => row.id === this.selectedId && !row.disabled))
|
|
74
81
|
this.selectedId = this.firstSelectable()?.id;
|
|
@@ -136,15 +143,20 @@ export class FilterList {
|
|
|
136
143
|
entries() {
|
|
137
144
|
const entries = [];
|
|
138
145
|
let last;
|
|
139
|
-
|
|
146
|
+
const gap = this.rowGap();
|
|
147
|
+
for (const [index, row] of this.visible.entries()) {
|
|
140
148
|
if (!this.query && row.group && row.group !== last)
|
|
141
149
|
entries.push({ heading: row.group });
|
|
142
150
|
last = row.group;
|
|
143
151
|
entries.push({ row });
|
|
152
|
+
if (index < this.visible.length - 1) {
|
|
153
|
+
for (let line = 0; line < gap; line += 1)
|
|
154
|
+
entries.push({ spacer: true });
|
|
155
|
+
}
|
|
144
156
|
}
|
|
145
157
|
return entries;
|
|
146
158
|
}
|
|
147
|
-
|
|
159
|
+
lines(row, width) {
|
|
148
160
|
const theme = this.options.theme();
|
|
149
161
|
const selected = row.id === this.selectedId;
|
|
150
162
|
const detail = row.detail ? safeText(row.detail) : '';
|
|
@@ -153,10 +165,40 @@ export class FilterList {
|
|
|
153
165
|
const label = truncateToWidth(safeText(row.label), Math.max(0, room - (showDetail ? visibleWidth(detail) + 2 : 0)), '…');
|
|
154
166
|
const gap = ' '.repeat(Math.max(0, room - visibleWidth(label) - (showDetail ? visibleWidth(detail) : 0)));
|
|
155
167
|
const tone = (text) => theme.fg(row.tone ?? 'muted', text);
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
168
|
+
const prefix = selected ? '❯ ' : ' ';
|
|
169
|
+
const labelText = selected || row.disabled || !row.labelParts?.length ? label : this.labelParts(row.labelParts, label, theme);
|
|
170
|
+
const main = prefix + label;
|
|
171
|
+
const primary = selected
|
|
172
|
+
? selectedRow(selectionText(theme, main) + gap + (showDetail ? selectionMuted(theme, detail) : ''), width, theme)
|
|
173
|
+
: (row.disabled ? theme.fg('dim', main) : prefix + labelText) + gap + (showDetail ? tone(detail) : '');
|
|
174
|
+
if (row.subtitle === undefined)
|
|
175
|
+
return [primary];
|
|
176
|
+
const subtitle = truncateToWidth(` ${safeText(row.subtitle)}`, width, '…');
|
|
177
|
+
const secondary = selected
|
|
178
|
+
? selectedRow(selectionMuted(theme, subtitle), width, theme)
|
|
179
|
+
: theme.fg(row.disabled ? 'dim' : 'muted', subtitle);
|
|
180
|
+
return [primary, secondary];
|
|
181
|
+
}
|
|
182
|
+
labelParts(parts, visibleLabel, theme) {
|
|
183
|
+
const source = parts.map(part => safeText(part.text)).join('');
|
|
184
|
+
const truncated = visibleWidth(source) > visibleWidth(visibleLabel);
|
|
185
|
+
let remaining = Math.max(0, visibleWidth(visibleLabel) - (truncated ? 1 : 0));
|
|
186
|
+
const output = [];
|
|
187
|
+
for (const part of parts) {
|
|
188
|
+
if (remaining <= 0)
|
|
189
|
+
break;
|
|
190
|
+
const text = safeText(part.text);
|
|
191
|
+
const fitted = truncateToWidth(text, remaining, '');
|
|
192
|
+
if (fitted) {
|
|
193
|
+
output.push(part.color ? theme.style(fitted, { fg: parseColor(part.color) }) : part.tone ? theme.fg(part.tone, fitted) : fitted);
|
|
194
|
+
}
|
|
195
|
+
remaining -= visibleWidth(fitted);
|
|
196
|
+
if (visibleWidth(fitted) < visibleWidth(text))
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
if (truncated)
|
|
200
|
+
output.push('…');
|
|
201
|
+
return output.join('');
|
|
160
202
|
}
|
|
161
203
|
/** `rows` is the most the list may use, the search row included. */
|
|
162
204
|
render(width, rows) {
|
|
@@ -186,11 +228,51 @@ export class FilterList {
|
|
|
186
228
|
return lines.map(line => truncateToWidth(line, width, ''));
|
|
187
229
|
}
|
|
188
230
|
const at = Math.max(0, entries.findIndex(entry => entry.row?.id === this.selectedId));
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
231
|
+
let start = at;
|
|
232
|
+
let end = at + 1;
|
|
233
|
+
let used = entries[at]?.row?.subtitle === undefined ? 1 : 2;
|
|
234
|
+
let preferBefore = true;
|
|
235
|
+
while (start > 0 || end < entries.length) {
|
|
236
|
+
const before = entries[start - 1];
|
|
237
|
+
const after = entries[end];
|
|
238
|
+
const beforeHeight = before ? (before.row?.subtitle === undefined ? 1 : 2) : Number.POSITIVE_INFINITY;
|
|
239
|
+
const afterHeight = after ? (after.row?.subtitle === undefined ? 1 : 2) : Number.POSITIVE_INFINITY;
|
|
240
|
+
if (preferBefore && before && used + beforeHeight <= budget) {
|
|
241
|
+
start -= 1;
|
|
242
|
+
used += beforeHeight;
|
|
243
|
+
}
|
|
244
|
+
else if (after && used + afterHeight <= budget) {
|
|
245
|
+
end += 1;
|
|
246
|
+
used += afterHeight;
|
|
247
|
+
}
|
|
248
|
+
else if (before && used + beforeHeight <= budget) {
|
|
249
|
+
start -= 1;
|
|
250
|
+
used += beforeHeight;
|
|
251
|
+
}
|
|
252
|
+
else
|
|
253
|
+
break;
|
|
254
|
+
preferBefore = !preferBefore;
|
|
255
|
+
}
|
|
256
|
+
if (entries[start]?.spacer)
|
|
257
|
+
start += 1;
|
|
258
|
+
if (entries[end - 1]?.spacer)
|
|
259
|
+
end -= 1;
|
|
260
|
+
for (const entry of entries.slice(start, end)) {
|
|
261
|
+
if (entry.row) {
|
|
262
|
+
const rendered = this.lines(entry.row, width);
|
|
263
|
+
lines.push(...rendered);
|
|
264
|
+
this.renderedRows.push(...rendered.map(() => entry.row));
|
|
265
|
+
}
|
|
266
|
+
else if (entry.spacer) {
|
|
267
|
+
lines.push('');
|
|
268
|
+
this.renderedRows.push(undefined);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
lines.push(theme.fg('dim', safeText(entry.heading ?? '')));
|
|
272
|
+
this.renderedRows.push(undefined);
|
|
273
|
+
}
|
|
193
274
|
}
|
|
194
|
-
|
|
275
|
+
this.renderedRows = this.renderedRows.slice(0, rowBudget);
|
|
276
|
+
return lines.slice(0, rowBudget).map(line => truncateToWidth(line, width, ''));
|
|
195
277
|
}
|
|
196
278
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { matchesKey } from '@earendil-works/pi-tui';
|
|
1
|
+
import { matchesKey, visibleWidth } from '@earendil-works/pi-tui';
|
|
2
2
|
import { CENTER_DIALOG_PAD, CHROME_PAD, centerDialogBodyRows, centerDialogContentWidth, centerDialogFrame, centerDialogVerticalPad, contentWidth, pageBodyRows, pageFrame, tabIndexAt, tabStrip } from "./chrome.js";
|
|
3
3
|
import { centeredDialogHost, dialogFits, pageRows } from "./dialogGeometry.js";
|
|
4
4
|
export class PageOverlay {
|
|
@@ -132,11 +132,27 @@ export class PageOverlay {
|
|
|
132
132
|
contentWidth: contentWidth(width),
|
|
133
133
|
centered
|
|
134
134
|
};
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
const pageHint = page.hint();
|
|
136
|
+
const returnsToPage = pageHint.split(' · ').includes('Esc back');
|
|
137
|
+
const escapeHint = page.ownsNavigation ? 'Esc back/close' : returnsToPage ? 'Esc back' : 'Esc close';
|
|
138
|
+
const fullFooter = page.ownsNavigation ? `${escapeHint} · ${pageHint}` : [
|
|
139
|
+
pageHint,
|
|
140
|
+
tabs.length > 1 ? 'Tab/Shift+Tab sections' : '',
|
|
141
|
+
returnsToPage ? '' : escapeHint
|
|
139
142
|
].filter(Boolean).join(' · ');
|
|
143
|
+
const footerWidth = Math.max(0, width - CHROME_PAD * 2);
|
|
144
|
+
const primaryHint = pageHint.split(' · ')[0] ?? '';
|
|
145
|
+
const compactFooter = [
|
|
146
|
+
primaryHint,
|
|
147
|
+
!page.ownsNavigation && tabs.length > 1 ? 'Tab/⇧Tab' : '',
|
|
148
|
+
escapeHint
|
|
149
|
+
].filter(Boolean).join(' · ');
|
|
150
|
+
const navigationFooter = [!page.ownsNavigation && tabs.length > 1 ? 'Tab/⇧Tab' : '', escapeHint].filter(Boolean).join(' · ');
|
|
151
|
+
const footer = visibleWidth(fullFooter) <= footerWidth
|
|
152
|
+
? fullFooter
|
|
153
|
+
: visibleWidth(compactFooter) <= footerWidth
|
|
154
|
+
? compactFooter
|
|
155
|
+
: visibleWidth(navigationFooter) <= footerWidth ? navigationFooter : escapeHint;
|
|
140
156
|
return pageFrame({
|
|
141
157
|
title: '',
|
|
142
158
|
headerLine: tabStrip(tabs, page.activeTab(), inner, theme, 'top', this.hoveredTab),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The annotate pane (`/
|
|
2
|
+
* The annotate pane (`/annotate:` and `/annotate:annotate`, PROP-0018): the person works in a browser window or an app's own
|
|
3
3
|
* windows, and this page is the panel beside it. With the inspector on, hovering outlines a
|
|
4
4
|
* component and a click opens a comment popover with three choices: keep it as a note, add it to
|
|
5
5
|
* this session's queue, or send it to the agent now. The page lists the queue, what was sent and
|
|
@@ -194,10 +194,11 @@ export class BrowserPage {
|
|
|
194
194
|
if (row.hub) {
|
|
195
195
|
const entry = row.hub;
|
|
196
196
|
const installed = this.snapshot?.hub?.installed.some(receipt => receipt.entry.id === entry.id && receipt.entry.version === entry.version);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
if (entry.delivery.kind !== 'pi-package')
|
|
198
|
+
actions.push({ id: 'preview', label: 'Preview reviewed content', run: async () => {
|
|
199
|
+
const page = await this.options.call('terminal.browser.hubPreview', { id: entry.id, version: entry.version });
|
|
200
|
+
this.showContent(page.content + (page.done ? '' : '\n\n[Preview limited to first page.]'));
|
|
201
|
+
} });
|
|
201
202
|
if (!installed)
|
|
202
203
|
actions.push({ id: 'install', label: `Install ${entry.version}`, run: async () => {
|
|
203
204
|
if (!await this.options.ui.confirm(`Install ${entry.title}?`, `${entry.description}\nPublisher: ${entry.publisher}\nLicense: ${entry.license}\nSource: ${entry.sourceUrl}\nVersion: ${entry.version}\nInstallation does not grant trust, enablement, credentials or permissions.`))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The Hub page: the
|
|
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: '
|
|
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() {
|
|
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
|
-
|
|
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
|
|
41
|
-
this.
|
|
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
|
-
|
|
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
|
-
...
|
|
58
|
-
id:
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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 (
|
|
113
|
-
|
|
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 =>
|
|
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
|
|
121
|
-
...(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
|
|
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, ''));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The Web annotations page (`/annotate:notes`, `/annotate:annotate [url]`): comments a person keeps about web pages. A new annotation
|
|
3
3
|
* is captured in a visible browser window: the whole page, text the person selects, or an element
|
|
4
|
-
* they click.
|
|
4
|
+
* they click. An annotation is private until it is shared with this session, whose agent can then read it
|
|
5
5
|
* with `page_notes_read` (PROP-0017).
|
|
6
6
|
*/
|
|
7
7
|
import type { Theme } from '@earendil-works/pi-coding-agent';
|
|
@@ -18,7 +18,7 @@ export interface NotesPageOptions {
|
|
|
18
18
|
input(title: string, placeholder?: string): Promise<string | undefined>;
|
|
19
19
|
};
|
|
20
20
|
changed(): void;
|
|
21
|
-
/** Start a new
|
|
21
|
+
/** Start a new annotation as soon as the page opens (`/annotate:annotate [url]`), on this address when given. */
|
|
22
22
|
start?: {
|
|
23
23
|
url?: string;
|
|
24
24
|
};
|
|
@@ -36,7 +36,7 @@ export declare const NOTE_KINDS: {
|
|
|
36
36
|
};
|
|
37
37
|
export declare const NOTE_ACTIONS: {
|
|
38
38
|
readonly reveal: "Show it in the browser window";
|
|
39
|
-
readonly edit: "Edit the
|
|
39
|
+
readonly edit: "Edit the annotation";
|
|
40
40
|
readonly resolve: "Mark resolved";
|
|
41
41
|
readonly reopen: "Reopen";
|
|
42
42
|
readonly share: "Let this session's agent read it";
|
|
@@ -65,7 +65,7 @@ export declare class NotesPage implements TerminalPage {
|
|
|
65
65
|
rows(): ListRow<Row>[];
|
|
66
66
|
private attempt;
|
|
67
67
|
activateRow(row: Row): Promise<void>;
|
|
68
|
-
/** Opens the capture window, takes what the person points at, and saves their
|
|
68
|
+
/** Opens the capture window, takes what the person points at, and saves their annotation. */
|
|
69
69
|
newNote(startUrl?: string): Promise<void>;
|
|
70
70
|
private noteMenu;
|
|
71
71
|
handleMouse(event: TerminalMouseInput): import("@earendil-works/pi-tui").TuiMouseEventResult | undefined;
|