@iseoane/agent-depot 0.4.1 → 0.5.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 (56) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.es.md +969 -0
  3. package/README.md +244 -23
  4. package/dist/src/app-flow.js +52 -51
  5. package/dist/src/app-recipe-removal.js +63 -0
  6. package/dist/src/cli.js +122 -11
  7. package/dist/src/executable-resolution.js +54 -0
  8. package/dist/src/git-source.js +150 -6
  9. package/dist/src/package-bundles.js +614 -0
  10. package/dist/src/package-cli.js +412 -0
  11. package/dist/src/package-content.js +158 -0
  12. package/dist/src/package-flow.js +645 -0
  13. package/dist/src/package-host-plans.js +243 -0
  14. package/dist/src/package-host-state.js +473 -0
  15. package/dist/src/package-model.js +33 -0
  16. package/dist/src/package-owned-skills.js +124 -0
  17. package/dist/src/package-state.js +404 -0
  18. package/dist/src/process-runner.js +9 -1
  19. package/dist/src/profile-cli.js +15 -9
  20. package/dist/src/profile-export.js +44 -9
  21. package/dist/src/profile-import.js +80 -17
  22. package/dist/src/profile.js +78 -3
  23. package/dist/src/skill-discovery.js +7 -2
  24. package/dist/src/skill-install.js +16 -0
  25. package/dist/src/sources.js +11 -0
  26. package/dist/src/tui/app-actions.js +1 -1
  27. package/dist/src/tui/app.js +10 -11
  28. package/dist/src/tui/catalog-browse.js +46 -13
  29. package/dist/src/tui/catalog-ownership.js +12 -0
  30. package/dist/src/tui/catalog-panel.js +16 -4
  31. package/dist/src/tui/catalog-state.js +62 -6
  32. package/dist/src/tui/catalog-tree.js +58 -12
  33. package/dist/src/tui/catalog-view.js +53 -7
  34. package/dist/src/tui/environment.js +8 -1
  35. package/dist/src/tui/installations-browse.js +20 -2
  36. package/dist/src/tui/installations-rows.js +2 -1
  37. package/dist/src/tui/installations-tree.js +14 -1
  38. package/dist/src/tui/installations-view.js +8 -2
  39. package/dist/src/tui/installations.js +12 -2
  40. package/dist/src/tui/manage-actions.js +2 -0
  41. package/dist/src/tui/manage-panel.js +5 -0
  42. package/dist/src/tui/package-actions.js +165 -0
  43. package/dist/src/tui/package-rows.js +91 -0
  44. package/dist/src/tui/profile-view.js +36 -14
  45. package/dist/src/tui/render.js +1 -1
  46. package/dist/src/tui/sources-mode.js +2 -0
  47. package/dist/src/tui/sources-recipes.js +116 -53
  48. package/dist/src/tui/sources-view.js +57 -19
  49. package/dist/src/tui/tree.js +3 -4
  50. package/dist/src/tui/updates-flow.js +4 -1
  51. package/dist/src/tui/updates-panel.js +2 -2
  52. package/dist/src/tui/updates.js +72 -4
  53. package/dist/src/tui/window.js +3 -4
  54. package/dist/src/user-global-skill-inventory.js +7 -2
  55. package/package.json +2 -1
  56. package/skills/agent-depot-apprecipe/SKILL.md +29 -5
@@ -0,0 +1,165 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describeCoordinates } from "../package-model.js";
3
+ import { errorText } from "./batch.js";
4
+ import { answerYesNo } from "./mode-keys.js";
5
+ import { installedEvidenceLabel, packagePlanLines } from "./package-rows.js";
6
+ import { PreviewConfirm } from "./panel-parts.js";
7
+ /** The per-row keys of a Package; Enter is approval, handled beside these. */
8
+ export const PACKAGE_ACTION_KEYS = new Map([
9
+ ["i", "install"],
10
+ ["U", "update"],
11
+ ["u", "uninstall"],
12
+ ["f", "forget"],
13
+ ]);
14
+ export const PACKAGE_KINDS = ["package-approve", "package-confirm", "package-forget"];
15
+ /** Narrows the view's mode without asserting a partial mode is a Package mode. */
16
+ export function isPackageMode(mode) {
17
+ return PACKAGE_KINDS.some(kind => kind === mode.kind);
18
+ }
19
+ /** `install`, `update`, `uninstall` and `forget` plan a step; `approve` writes the install receipt. */
20
+ export async function startPackageAction(context, target, input) {
21
+ const { manage, packages } = context;
22
+ manage.setMessage(undefined);
23
+ try {
24
+ if (input === "approve") {
25
+ manage.setMode({ kind: "busy", label: `Preparing the declaration of ${target.name}...` });
26
+ const plan = await packages.planLifecycle(target.selection, "install");
27
+ if (manage.isMounted()) {
28
+ manage.setMode({ kind: "package-approve", target, lines: packagePlanLines(plan, "needs approval") });
29
+ }
30
+ return;
31
+ }
32
+ const action = PACKAGE_ACTION_KEYS.get(input);
33
+ if (action === "forget") {
34
+ manage.setMode({ kind: "busy", label: `Preparing to forget ${target.name}...` });
35
+ const inspection = await packages.inspect(target.selection);
36
+ if (manage.isMounted()) {
37
+ manage.setMode({
38
+ kind: "package-forget",
39
+ target,
40
+ lines: [
41
+ `Package: ${target.name} (${target.selection.host})`,
42
+ `Coordinates: ${describeCoordinates(target.selection)}`,
43
+ `Installed: ${installedEvidenceLabel(inspection.installed)}`,
44
+ "Forgetting drops the Agent Depot selection record only; the host install is left as it is and no host command runs.",
45
+ ],
46
+ });
47
+ }
48
+ return;
49
+ }
50
+ if (action !== undefined) {
51
+ manage.setMode({ kind: "busy", label: `Preparing the ${action} of ${target.name}...` });
52
+ const plan = await packages.planLifecycle(target.selection, action);
53
+ const approval = await packages.approvalStatus(target.selection, action);
54
+ if (manage.isMounted())
55
+ manage.setMode({ kind: "package-confirm", plan, approval });
56
+ return;
57
+ }
58
+ await manage.finish({ kind: "error", text: `The action ${JSON.stringify(input)} is not available for Packages` });
59
+ }
60
+ catch (error) {
61
+ await manage.finish({ kind: "error", text: errorText(error) });
62
+ }
63
+ }
64
+ /** The `y` of a lifecycle confirmation whose receipt is missing approves it first, then asks again. */
65
+ async function approveThenConfirm(context, mode) {
66
+ const { manage, packages } = context;
67
+ manage.setMode({ kind: "busy", label: `Approving the ${mode.plan.action} declaration of ${mode.plan.descriptor.name}...` });
68
+ try {
69
+ await packages.approve(mode.plan.selection, mode.plan.action);
70
+ if (manage.isMounted())
71
+ manage.setMode({ ...mode, approval: "approved" });
72
+ }
73
+ catch (error) {
74
+ if (manage.isMounted())
75
+ await manage.finish({ kind: "error", text: errorText(error) });
76
+ }
77
+ }
78
+ function resultLine(plan, result) {
79
+ const name = plan.descriptor.name;
80
+ switch (result.status) {
81
+ case "installed":
82
+ return { ok: true, text: `${name}: installed (${installedEvidenceLabel(result.verified)})` };
83
+ case "updated":
84
+ return { ok: true, text: `${name}: updated (${installedEvidenceLabel(result.previous)} -> ${installedEvidenceLabel(result.verified)})` };
85
+ case "removed":
86
+ return { ok: true, text: `${name}: removed` };
87
+ case "selected":
88
+ return { ok: true, text: `${name}: selected` };
89
+ case "forgotten":
90
+ return { ok: true, text: `${name}: forgotten` };
91
+ case "not confirmed":
92
+ return { ok: true, text: `${name}: not changed` };
93
+ case "stale plan":
94
+ return { ok: false, text: `${name}: ${plan.action} not run: ${result.reason}` };
95
+ case "manual required":
96
+ return { ok: false, text: `${name}: ${plan.action} needs a manual step: ${result.reason}` };
97
+ case "failed":
98
+ return { ok: false, text: `${name}: failed: ${result.reason}${result.output === undefined ? "" : ` (${result.output})`}` };
99
+ }
100
+ }
101
+ async function executePackagePlan(context, plan) {
102
+ const { manage, packages } = context;
103
+ manage.setMode({ kind: "busy", label: `Running the ${plan.action} of ${plan.descriptor.name}...` });
104
+ let result;
105
+ try {
106
+ result = await packages.executeLifecycle(plan, true);
107
+ }
108
+ catch (error) {
109
+ await manage.finish({ kind: "error", text: errorText(error) });
110
+ return;
111
+ }
112
+ const line = resultLine(plan, result);
113
+ await manage.finish({ kind: line.ok ? "ok" : "error", text: line.text });
114
+ }
115
+ async function forgetSelection(context, mode) {
116
+ const { manage, packages } = context;
117
+ manage.setMode({ kind: "busy", label: `Forgetting ${mode.target.name}...` });
118
+ try {
119
+ await packages.forget(mode.target.selection, true);
120
+ await manage.finish({ kind: "ok", text: `Forgot ${mode.target.name}; no host command ran` });
121
+ }
122
+ catch (error) {
123
+ await manage.finish({ kind: "error", text: errorText(error) });
124
+ }
125
+ }
126
+ /** Applies the y/n keys of every Package confirmation; `n` and Esc run nothing. */
127
+ export function handlePackageKey(context, mode, input, key) {
128
+ const { manage, packages } = context;
129
+ const cancel = (text) => {
130
+ manage.setMessage({ kind: "ok", text });
131
+ manage.browse();
132
+ };
133
+ switch (mode.kind) {
134
+ case "package-approve":
135
+ answerYesNo(input, key, () => {
136
+ manage.setMode({ kind: "busy", label: `Approving the install declaration of ${mode.target.name}...` });
137
+ void packages.approve(mode.target.selection, "install").then(() => manage.finish({ kind: "ok", text: `Approved the install declaration of ${mode.target.name}` }), error => manage.finish({ kind: "error", text: errorText(error) }));
138
+ }, () => cancel("Package action cancelled"));
139
+ return;
140
+ case "package-forget":
141
+ answerYesNo(input, key, () => void forgetSelection(context, mode), () => cancel("Package action cancelled"));
142
+ return;
143
+ case "package-confirm":
144
+ answerYesNo(input, key, () => {
145
+ if (mode.approval === "needs approval")
146
+ void approveThenConfirm(context, mode);
147
+ else
148
+ void executePackagePlan(context, mode.plan);
149
+ }, () => cancel("Package action cancelled"));
150
+ return;
151
+ }
152
+ }
153
+ /** Renders a Package prompt with the existing preview panel and y/n confirmation. */
154
+ export function PackagePanel({ mode }) {
155
+ switch (mode.kind) {
156
+ case "package-approve":
157
+ return _jsx(PreviewConfirm, { lines: mode.lines, question: `Approve this install declaration for ${mode.target.name}? y/n` });
158
+ case "package-forget":
159
+ return _jsx(PreviewConfirm, { lines: mode.lines, question: `Forget the selection record of ${mode.target.name}? y/n` });
160
+ case "package-confirm":
161
+ return (_jsx(PreviewConfirm, { lines: packagePlanLines(mode.plan, mode.approval), question: mode.approval === "needs approval"
162
+ ? "Approve this declaration and continue? y/n"
163
+ : `${mode.plan.action} ${mode.plan.descriptor.name}? y/n` }));
164
+ }
165
+ }
@@ -0,0 +1,91 @@
1
+ import { classifyUpdate } from "../package-host-plans.js";
2
+ import { describeCoordinates, } from "../package-model.js";
3
+ /** Version evidence as a short label; `unknown` when the host or the bundle states none. */
4
+ export function packageVersionLabel(evidence) {
5
+ if (evidence === undefined)
6
+ return "unknown";
7
+ return evidence.kind === "manifest-version" ? evidence.version : evidence.commit.slice(0, 7);
8
+ }
9
+ /** What the host reports about an install, as one phrase. Never a guess. */
10
+ export function installedEvidenceLabel(evidence) {
11
+ switch (evidence.kind) {
12
+ case "installed": {
13
+ if (evidence.reason !== undefined) {
14
+ const reason = evidence.reason.includes("local modifications") ? "local changes" : evidence.reason;
15
+ return `installed (version unavailable: ${reason})`;
16
+ }
17
+ if (evidence.version !== undefined)
18
+ return `installed ${evidence.version}`;
19
+ if (evidence.commit !== undefined)
20
+ return `installed at ${evidence.commit.slice(0, 7)}`;
21
+ return "installed with no version evidence";
22
+ }
23
+ case "absent": {
24
+ return "not installed";
25
+ }
26
+ case "unknown": {
27
+ return `unknown (${evidence.reason})`;
28
+ }
29
+ }
30
+ }
31
+ /** The shared update vocabulary; the Updates list prints `up to date`, not `current`. */
32
+ export function packageStatusLabel(status) {
33
+ if (status === "current")
34
+ return "up to date";
35
+ if (status === "unknown")
36
+ return "cannot assess";
37
+ return status;
38
+ }
39
+ /** What a selection record itself remembers, for lists that do not read host evidence. */
40
+ export function recordedVersionLabel(record) {
41
+ if (record.verified !== undefined)
42
+ return packageVersionLabel(record.verified);
43
+ if (record.lastDelegatedCommit !== undefined)
44
+ return record.lastDelegatedCommit.slice(0, 7);
45
+ return "unknown";
46
+ }
47
+ export function packageRow(record, inspection) {
48
+ return {
49
+ selection: record.selection,
50
+ name: inspection.descriptor?.name ?? describeCoordinates(record.selection),
51
+ installed: installedEvidenceLabel(inspection.installed),
52
+ available: packageVersionLabel(inspection.available),
53
+ status: classifyUpdate(inspection.installed, inspection.available, inspection.availableCommit, inspection.installedCommitIsAncestor),
54
+ ...(inspection.drift === undefined ? {} : { drift: inspection.drift }),
55
+ };
56
+ }
57
+ /** Host, evidence and status of one row, as the Installations line prints it. */
58
+ export function describePackageRow(row) {
59
+ const drift = row.drift === undefined ? "" : ` drift: ${row.drift}`;
60
+ return `${row.selection.host} ${row.installed} available ${row.available} ${packageStatusLabel(row.status)}${drift}`;
61
+ }
62
+ /** One component of the inventory; the executable effect and a host-only owner are called out. */
63
+ export function describePackageComponent(component) {
64
+ const risk = component.effect === "executable" ? " runs a host process" : "";
65
+ const ownership = component.ownership === "host-only" ? " host-only: Agent Depot never manages it" : "";
66
+ const paths = component.paths.length === 0 ? "" : ` ${component.paths.join(", ")}`;
67
+ return `${component.kind} ${component.ownership} ${component.effect}${risk}${ownership}${paths}`;
68
+ }
69
+ /** The preview lines of one frozen plan: origin, inventory, argv, blast radius and approval state. */
70
+ export function packagePlanLines(plan, approval) {
71
+ const { descriptor, selection } = plan;
72
+ return [
73
+ `Environment: ${plan.environment}`,
74
+ `Package: ${descriptor.name} (${selection.host}) ${plan.action}`,
75
+ `Coordinates: ${describeCoordinates(selection)}`,
76
+ `Origin: manifest ${plan.origin.manifestDigest.slice(0, 12)}${plan.origin.resolvedCommit === undefined ? "" : ` commit ${plan.origin.resolvedCommit.slice(0, 7)}`}`,
77
+ `Available: ${packageVersionLabel(descriptor.version)}`,
78
+ `Components (${descriptor.components.length}):`,
79
+ ...descriptor.components.map((component) => ` ${describePackageComponent(component)}`),
80
+ ...(plan.commands.length === 0
81
+ ? ["This action runs no host command."]
82
+ : plan.commands.flatMap((command, position) => [
83
+ `Step ${position + 1} (${command.purpose}): ${JSON.stringify([command.executable, ...command.args])}`,
84
+ `Executable: ${command.resolvedPath ?? command.executable}`,
85
+ ])),
86
+ `Blast radius: ${plan.affects.map(describeCoordinates).join(", ")}`,
87
+ `Working directory: ${plan.cwd}`,
88
+ `Approval: ${approval === "approved" ? "approved" : "needs approval"}`,
89
+ ...plan.warnings.map((warning) => `Warning: ${warning}`),
90
+ ];
91
+ }
@@ -3,9 +3,10 @@ import { readFile } from "node:fs/promises";
3
3
  import { Box, Text } from "ink";
4
4
  import { useEffect, useMemo, useRef, useState } from "react";
5
5
  import { createAppOperations } from "../app-flow.js";
6
- import { buildProfileExport, profileExportPreview, writeProfileExport, } from "../profile-export.js";
6
+ import { buildProfileExport, describeProfilePackage, profileExportPreview, writeProfileExport, } from "../profile-export.js";
7
7
  import { applyProfileImport, buildProfileImport, } from "../profile-import.js";
8
8
  import { PROFILE_FORMAT } from "../profile.js";
9
+ import { packageOperationsOf } from "./environment.js";
9
10
  import { useKeys } from "./keys.js";
10
11
  import { answerYesNo, moveCursor, toggleChoice } from "./mode-keys.js";
11
12
  import { ChecklistLines, PreviewConfirm, PreviewLines } from "./panel-parts.js";
@@ -21,6 +22,9 @@ function rowsFromExport(plan) {
21
22
  ...plan.profile.apps.map((value, index) => ({
22
23
  block: "apps", index, label: `Apps: ${value.name} (${value.platform ?? "all platforms"})`, selectable: true,
23
24
  })),
25
+ ...plan.profile.packages.map((value, index) => ({
26
+ block: "packages", index, label: `Packages: ${describeProfilePackage(value)}`, selectable: true,
27
+ })),
24
28
  ...plan.exclusions.map((item, index) => ({
25
29
  block: "excluded", index, label: `Excluded ${item.label}: ${item.reason}`, selectable: false,
26
30
  })),
@@ -29,21 +33,35 @@ function rowsFromExport(plan) {
29
33
  })),
30
34
  ];
31
35
  }
36
+ /** The four import blocks in one indexed list, so the checklist and the confirmation share positions. */
37
+ function planEntries(plan) {
38
+ return [...plan.items, ...plan.packages];
39
+ }
32
40
  function rowsFromPlan(plan) {
33
- return plan.items.map((item, index) => ({
41
+ return planEntries(plan).map((item, index) => ({
34
42
  block: item.block, index,
35
43
  label: `${item.status}: ${item.label}${item.difference ? `; ${item.difference}` : ""}`,
36
44
  selectable: item.status === "add",
37
45
  }));
38
46
  }
39
- function selectedPlanItems(plan, selected) {
40
- return plan.items.filter((item, index) => item.status !== "add" || selected.some(row => row.index === index));
47
+ function selectedPlanEntries(plan, selected) {
48
+ return planEntries(plan).filter((item, index) => item.status !== "add" || selected.some(row => row.index === index));
49
+ }
50
+ /** A plan whose items are the chosen subset, split back into the block the writer consumes. */
51
+ function chosenPlan(plan, selected) {
52
+ const entries = selectedPlanEntries(plan, selected);
53
+ return {
54
+ ...plan,
55
+ items: entries.filter((item) => item.block !== "packages"),
56
+ packages: entries.filter((item) => item.block === "packages"),
57
+ };
41
58
  }
42
59
  /** Collect choices, display shared-core previews and confirm portable Profile transfers. */
43
60
  export function ProfileView({ operations, environment = {}, onCapturingChange, listHeight }) {
44
61
  const apps = useMemo(() => createAppOperations({
45
62
  homeDirectory: environment.homeDirectory, ...environment.appEnvironment,
46
63
  }), [environment]);
64
+ const packages = useMemo(() => packageOperationsOf(environment), [environment]);
47
65
  const [mode, setModeState] = useState("browse");
48
66
  const modeRef = useRef("browse");
49
67
  const mounted = useRef(true);
@@ -113,6 +131,7 @@ export function ProfileView({ operations, environment = {}, onCapturingChange, l
113
131
  sources: profile.sources.filter((_, index) => isChosen("sources", index)),
114
132
  skills: profile.skills.filter((_, index) => isChosen("skills", index)),
115
133
  apps: profile.apps.filter((_, index) => isChosen("apps", index)),
134
+ packages: profile.packages.filter((_, index) => isChosen("packages", index)),
116
135
  };
117
136
  };
118
137
  useKeys((input, key) => {
@@ -123,7 +142,9 @@ export function ProfileView({ operations, environment = {}, onCapturingChange, l
123
142
  if (input === "e") {
124
143
  action.current = "export";
125
144
  void run(async () => {
126
- const exported = await buildProfileExport(operations, apps, { homeDirectory: environment.homeDirectory });
145
+ const exported = await buildProfileExport(operations, apps, {
146
+ homeDirectory: environment.homeDirectory, packageOperations: packages,
147
+ });
127
148
  if (!mounted.current)
128
149
  return;
129
150
  setProfile(exported.profile);
@@ -156,7 +177,7 @@ export function ProfileView({ operations, environment = {}, onCapturingChange, l
156
177
  else
157
178
  void run(async () => {
158
179
  const raw = JSON.parse(await readFile(typed.current, "utf8"));
159
- const imported = await buildProfileImport(raw, operations, apps);
180
+ const imported = await buildProfileImport(raw, operations, apps, {}, packages);
160
181
  profileVersion.current = raw.agentDepotVersion;
161
182
  if (!mounted.current)
162
183
  return;
@@ -183,8 +204,8 @@ export function ProfileView({ operations, environment = {}, onCapturingChange, l
183
204
  result = [`Exported profile to ${typed.current}`];
184
205
  }
185
206
  else {
186
- const confirmed = { ...plan, items: selectedPlanItems(plan, selectedRef.current) };
187
- const outcomes = await applyProfileImport(confirmed, operations, apps, true, () => undefined, { homeDirectory: environment.homeDirectory, ...environment.installationOptions });
207
+ const confirmed = chosenPlan(plan, selectedRef.current);
208
+ const outcomes = await applyProfileImport(confirmed, operations, apps, true, () => undefined, { homeDirectory: environment.homeDirectory, packageOperations: packages, ...environment.installationOptions });
188
209
  result = outcomes.map(outcome => `${outcome.status}: ${outcome.item.label}${outcome.detail ? `; ${outcome.detail}` : ""}`);
189
210
  }
190
211
  if (mounted.current) {
@@ -225,13 +246,14 @@ export function ProfileView({ operations, environment = {}, onCapturingChange, l
225
246
  else
226
247
  void run(async () => {
227
248
  // Reuse the CLI's core preview for the exact selected subset, including argv and Source notes.
228
- const items = selectedPlanItems(plan, selectedRef.current);
249
+ const entries = selectedPlanEntries(plan, selectedRef.current);
229
250
  const preview = await buildProfileImport({
230
251
  format: PROFILE_FORMAT, agentDepotVersion: profileVersion.current,
231
- sources: items.filter(item => item.block === "sources").map(item => item.value),
232
- skills: items.filter(item => item.block === "skills").map(item => item.value),
233
- apps: items.filter(item => item.block === "apps").map(item => item.value),
234
- }, operations, apps);
252
+ sources: entries.filter(item => item.block === "sources").map(item => item.value),
253
+ skills: entries.filter(item => item.block === "skills").map(item => item.value),
254
+ apps: entries.filter(item => item.block === "apps").map(item => item.value),
255
+ packages: entries.filter(item => item.block === "packages").map(item => item.value),
256
+ }, operations, apps, {}, packages);
235
257
  if (mounted.current)
236
258
  showLines(preview.preview, "confirm");
237
259
  });
@@ -239,7 +261,7 @@ export function ProfileView({ operations, environment = {}, onCapturingChange, l
239
261
  });
240
262
  const listing = mode === "export-list" || mode === "import-list";
241
263
  const window = computeWindow(listing ? rows.length : lines.length, cursor, height);
242
- return _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Portable user-global Profile \u00B7 e export \u00B7 i import" }), mode === "busy" ? _jsx(Text, { children: "Loading Profile\u2026" }) : null, mode.endsWith("-path") ? _jsxs(Text, { children: ["Profile path: ", text, "_"] }) : null, listing ? ["sources", "skills", "apps", "excluded"].map(block => {
264
+ return _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Portable user-global Profile \u00B7 e export \u00B7 i import" }), mode === "busy" ? _jsx(Text, { children: "Loading Profile\u2026" }) : null, mode.endsWith("-path") ? _jsxs(Text, { children: ["Profile path: ", text, "_"] }) : null, listing ? ["sources", "skills", "apps", "packages", "excluded"].map(block => {
243
265
  const choices = rows.slice(window.start, window.end).filter(row => row.block === block);
244
266
  if (!choices.length)
245
267
  return null;
@@ -3,6 +3,6 @@ import { render } from "ink";
3
3
  import { App } from "./app.js";
4
4
  /** Renders the TUI and resolves once the user exits it. */
5
5
  export async function renderTui(operations, environment) {
6
- const instance = render(_jsx(App, { operations: operations, environment: environment }));
6
+ const instance = render(_jsx(App, { operations: operations, environment: environment }), { alternateScreen: true });
7
7
  await instance.waitUntilExit();
8
8
  }
@@ -1,6 +1,8 @@
1
1
  export const initialMode = { kind: "browse" };
2
2
  export function modeReducer(mode, event) {
3
3
  switch (event.type) {
4
+ case "add-kind":
5
+ return { kind: "add-kind" };
4
6
  case "input":
5
7
  return { kind: "input", value: "" };
6
8
  case "edit":