@iseoane/agent-depot 0.5.2 → 0.6.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.es.md +51 -29
  3. package/README.md +44 -25
  4. package/dist/src/app-flow.js +56 -24
  5. package/dist/src/profile-export.js +72 -9
  6. package/dist/src/profile-import.js +3 -2
  7. package/dist/src/profile.js +71 -7
  8. package/dist/src/tui/app-actions.js +82 -33
  9. package/dist/src/tui/app.js +19 -14
  10. package/dist/src/tui/catalog-browse.js +6 -1
  11. package/dist/src/tui/catalog-panel.js +11 -11
  12. package/dist/src/tui/catalog-state.js +28 -0
  13. package/dist/src/tui/catalog-tree.js +22 -5
  14. package/dist/src/tui/catalog-view.js +59 -16
  15. package/dist/src/tui/header-layout.js +33 -0
  16. package/dist/src/tui/installations-panel.js +3 -3
  17. package/dist/src/tui/installations-rows.js +20 -5
  18. package/dist/src/tui/installations-tree.js +4 -7
  19. package/dist/src/tui/installations-view.js +15 -6
  20. package/dist/src/tui/installations.js +9 -7
  21. package/dist/src/tui/manage-panel.js +7 -6
  22. package/dist/src/tui/mode-keys.js +9 -0
  23. package/dist/src/tui/package-rows.js +17 -5
  24. package/dist/src/tui/panel-parts.js +38 -6
  25. package/dist/src/tui/preview-window.js +48 -0
  26. package/dist/src/tui/profile-view.js +261 -42
  27. package/dist/src/tui/sources-mode.js +3 -1
  28. package/dist/src/tui/sources-recipes.js +50 -19
  29. package/dist/src/tui/sources-view.js +50 -14
  30. package/dist/src/tui/theme.js +14 -7
  31. package/dist/src/tui/updates-flow.js +2 -2
  32. package/dist/src/tui/updates-keys.js +34 -5
  33. package/dist/src/tui/updates-model.js +2 -0
  34. package/dist/src/tui/updates-panel.js +40 -15
  35. package/dist/src/tui/updates-preview.js +65 -0
  36. package/dist/src/tui/updates-view.js +17 -2
  37. package/dist/src/tui/updates.js +14 -5
  38. package/dist/src/tui/window.js +6 -4
  39. package/package.json +4 -3
package/CHANGELOG.md CHANGED
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.0] - 2026-10-12
11
+
12
+ ### Added
13
+
14
+ - The TUI fills the terminal and shows five numbered views. Switch with `1` through `5` or Tab/Shift+Tab. Resizing updates the layout. Xeo colors distinguish resource roles and status; `y` confirmations use bold green.
15
+ - Sources lists Git Sources and App recipes together. Approve recipes there; Catalog offers installation only for approved recipes verified absent. Installations retains installed Apps when approval expires and separates installation status from version evidence.
16
+ - Profile v2 can include identity-only replica intent for installed Skills, Apps and Packages. Export carries individual Skill selections and complete Package origin declarations, not installed state or approvals. The parser accepts Profile v1; v1-only readers reject v2. Import does not transfer approval or automatically install Apps or Packages.
17
+
18
+ ### Changed
19
+
20
+ - App recipe, refresh and update previews require review of every physical row before confirmation. After a resize, review any rows not yet displayed. Arrow keys and PageUp/PageDown move by visible pages.
21
+ - `u` in Updates opens the selected batch preview. It does not apply updates until the user reviews the preview and confirms.
22
+ - The Import/Export view groups Source choices, Skills, Apps and Packages under a collapsed Sources parent. Installed replicas and export notes have separate groups. Replica entries carry identity only and do not claim that the destination has installed those resources.
23
+
24
+ ### Fixed
25
+
26
+ - The TUI fills the terminal from startup and recalculates its layout on resize. Unmanaged Skill symlinks are marked distinctly in Installations.
27
+ - The real-terminal test harness waits for fully rendered frames and isolates test state from inherited user paths. Cleanup waits for views to unmount before removing temporary state.
28
+
10
29
  ## [0.5.2] - 2026-10-11
11
30
 
12
31
  ### Added
package/README.es.md CHANGED
@@ -20,8 +20,9 @@ Qué hace:
20
20
  - **Instalaciones existentes**: informa de los Skills no gestionados que ya hay en
21
21
  disco, adopta los que coinciden exactamente con una Source o los elimina mediante
22
22
  una ruta explícita.
23
- - **Apps**: instala, actualiza y desinstala aplicaciones globales de usuario mediante
24
- App recipes escritas por el usuario que deben aprobarse antes de ejecutarse.
23
+ - **Apps**: gestiona aplicaciones globales de usuario mediante App recipes escritas
24
+ por el usuario. Aprobar una receta autoriza sus comandos, pero no instala la App;
25
+ la instalación tiene su propia previsualización y confirmación.
25
26
  - **Packages**: instala, actualiza y desinstala bundles nativos del Host (paquetes de
26
27
  Pi y plugins de Claude Code) delegando el ciclo de vida en la CLI de cada Host y
27
28
  leyendo evidencia de instalación y versión propiedad del Host. Un bundle
@@ -508,29 +509,36 @@ La TUI es un front end de [Ink](https://github.com/vadimdemedes/ink) (React para
508
509
  terminales) sobre las mismas operaciones que la CLI. Necesita Node.js >= 24 y un
509
510
  terminal interactivo; en caso contrario, `tui` falla con un error. Usa la pantalla
510
511
  alternativa del terminal y vuelve a la pantalla anterior al salir. Se adapta al
511
- redimensionado del terminal. Cambia de vista con `1`-`5` y sal con `q` (no mientras
512
- haya un aviso abierto). Las listas se desplazan cuando superan la altura del
513
- terminal. En Sources, Catalog, Installations y Updates, `j`/`k` o las flechas mueven
514
- una fila; `PageUp` y `PageDown` avanzan una página visible.
512
+ redimensionado del terminal. El encabezado con bordes muestra la versión activa del
513
+ paquete en ejecución y las cinco vistas numeradas. Cambia de vista con `1`-`5` o Tab/Shift+Tab,
514
+ y sal con `q` (no mientras haya un aviso abierto). La paleta Xeo distingue roles y
515
+ estados; las confirmaciones con `y` se muestran en negrita y verde. Las listas se
516
+ desplazan cuando superan la altura del terminal. En Sources, Catalog, Installations y Updates,
517
+ `j`/`k` o las flechas mueven
518
+ una fila; `PageUp` y `PageDown` avanzan una página visible. Usa un terminal de al
519
+ menos 40 columnas para revisar las notas de exportación de Profile; con 24 columnas,
520
+ se recortan la pestaña del encabezado y el título de la nota.
515
521
 
516
522
  | Vista | Propósito |
517
523
  | --- | --- |
518
524
  | 1 Sources | Sources de Git (sus skills y Packages) y recetas App del usuario en una sola lista, con tipos `skills`/`app` y acciones contextuales. |
519
- | 2 Catalog | Skills y Packages disponibles, agrupados por Source. Los Packages que el Host confirma como instalados se ocultan; los que solo están seleccionados siguen visibles. Un Skill propiedad de un Package activo conserva el contraste normal y muestra una insignia breve; no se puede marcar ni instalar por separado. Sitúa el cursor encima para ver el motivo. |
520
- | 3 Installations | Lo que hay en disco: instalaciones gestionadas (globales de usuario y de proyecto), selecciones de Packages y skills globales de usuario no gestionados. |
521
- | 4 Updates | Skills y Packages gestionados con una versión más reciente disponible, para el ámbito global de usuario y de proyecto. |
522
- | 5 Import/Export | Transferir las elecciones portables de Sources globales de usuario, las selecciones de Skills, las App recipes y las selecciones de Packages. |
525
+ | 2 Catalog | Skills y Packages disponibles, además de App recipes aplicables y aprobadas cuya ausencia se ha verificado. La instalación de una App tiene una previsualización y confirmación separadas. Las recetas inválidas o sin aprobar permanecen en Sources; un estado desconocido nunca se trata como ausencia. Los Packages que el Host confirma como instalados se ocultan; los seleccionados o con estado desconocido siguen visibles. Los Skills propiedad de Packages conservan su comportamiento actual. |
526
+ | 3 Installations | Apps cuya instalación está confirmada, instalaciones de Skills gestionadas (globales de usuario y de proyecto), selecciones de Packages con pertenencia al Host y evidencia de versión mostradas por separado, y Skills globales de usuario no gestionados. Un registro de instalación de App permanece visible aunque caduque la aprobación de la receta, pero sus acciones siguen exigiendo aprobación vigente. |
527
+ | 4 Updates | Skills gestionados en el ámbito global de usuario y de proyecto, además de Apps y Packages instalados globales de usuario con una versión más reciente disponible. Las Apps desconocidas o no verificadas no se tratan como instaladas. |
528
+ | 5 Profile | Exportar o importar elecciones portables de Sources globales de usuario, selecciones individuales de Skills, App recipes y selecciones de Packages. Las réplicas solo identifican recursos cuya instalación se confirmó en el origen. |
523
529
 
524
530
  Teclas por vista (los avisos también aceptan `Esc` para cancelar y `y`/`n` para
525
531
  confirmar):
526
532
 
527
533
  | Vista | Teclas |
528
534
  | --- | --- |
529
- | Sources | `j`/`k` recorren Sources de Git y recetas App, sin cambiar de sección con Tab. `Enter` abre el Catálogo de una Source de Git (sus skills y Packages) o previsualiza/aprueba una receta. `n` añade una Source de Git reutilizable o guía la creación de recetas; `r` refresca Sources o recarga el estado de recetas; `d` elimina. `space`/`a` solo marcan Sources de Git. |
530
- | Catalog | `Enter`/flechas expandir y contraer, `space` alternar el Skill disponible resaltado, `a` alternar todos los Skills disponibles de la Source resaltada que coincidan con el filtro actual, `i` instalar los Skills marcados que coincidan con el filtro o el Skill disponible resaltado, o previsualizar el Package resaltado, `/` filtrar, `s` alternar una Source o todas (si se abrió desde una Source). Las marcas de otras Sources se conservan. |
531
- | Installations | `Enter`/flechas expandir y contraer, `u` desinstalar (todos los Hosts o los Hosts elegidos) o eliminar un skill no gestionado, `h` o `i` añadir Hosts a una instalación, `A` o `Enter` adoptar un skill no gestionado, `space` marca una hoja y `a` marca todas las hojas visibles: con marcas, `u` y `h` actúan sobre todas ellas con una única previsualización combinada y una única confirmación (las instalaciones de proyecto se omiten con un motivo). En una fila de Package: `i` previsualiza el plan de instalación congelado y lo ejecuta, aprobando antes el recibo si falta; `Enter` solo aprueba la declaración de instalación; `U` actualiza, `u` desinstala, `f` olvida el registro de selección. Las filas de Package no se pueden marcar para acciones por lotes. |
532
- | Updates | `space` marcar, `a` marcar todo, `t`/`p`/`g` mostrar todo, proyecto o global de usuario, `Enter` previsualizar las marcas, `r` comprobar de nuevo, las flechas pliegan el grupo "cannot be checked". Sus filas son informativas y no se pueden marcar para actualizar. Un Package marcado se une al lote y se aprueba antes de ejecutar su actualización. |
533
- | Import/Export | `e` exportar, `i` importar; introduce una ruta de archivo, `space` alterna opciones, `a` alterna todas, `Enter` previsualiza, `y` aplica; `j`/`k` desplazan las previsualizaciones y los resultados |
535
+ | Sources | `j`/`k` recorren Sources de Git y recetas App. `Enter` abre el Catálogo de una Source de Git o previsualiza/aprueba una receta App. Aprobarla no instala la App. `n` añade una Source reutilizable o guía la creación de recetas; `r` refresca Sources o recarga el estado de recetas; `d` elimina. `space`/`a` solo marcan Sources de Git. La previsualización de refresco muestra todas las Sources seleccionadas; usa `j`/`k` o PgUp/PgDn para revisarlas antes de `y`; `n` cancela. |
536
+ | Catalog | `Enter`/flechas expanden y contraen; `space` marca el Skill disponible resaltado; `a` marca o desmarca los Skills disponibles visibles de su Source; `i` inicia la instalación de los Skills marcados o del Skill, Package o App aprobada cuya ausencia se verificó y que está resaltada. Confirma la instalación de una App por separado con `y`. `/` filtra y `s` amplía la vista de una Source a todas. Las marcas de otras Sources se conservan y no se instalan Skills ocultos por el filtro. |
537
+ | Installations | `Enter`/flechas expanden y contraen. En filas globales de usuario, `u` inicia la desinstalación de una App o Skill gestionada, o elimina un Skill no gestionado; los Skills de proyecto no se pueden eliminar. `h` o `i` añade Hosts a una instalación global de usuario, y `A` o `Enter` adopta un Skill no gestionado. `space` marca una hoja y `a` marca todas las hojas visibles; las acciones por lotes omiten instalaciones de proyecto con un motivo. En una fila de App, `u` previsualiza la desinstalación y `h` selecciona setup/teardown; las recetas modificadas requieren una nueva aprobación. En una fila de Package, `i` previsualiza el plan de instalación congelado y lo ejecuta, aprobando antes el recibo si falta; `Enter` solo aprueba la declaración de instalación; `U` actualiza, `u` desinstala y `f` olvida el registro de selección. Las filas de Package no se pueden marcar para acciones por lotes. |
538
+ | Updates | `space` marca una actualización, `a` marca todas las actualizaciones disponibles y `t`/`p`/`g` selecciona todo, el ámbito de proyecto o el global de usuario. `Enter` previsualiza las filas marcadas; `u` previsualiza el lote seleccionado; ninguna aplica actualizaciones. `r` vuelve a comprobar y las flechas izquierda/derecha pliegan o despliegan el grupo "cannot be checked". En una previsualización, las flechas o PageUp/PageDown avanzan una página visible y `j`/`k` una fila. Revisa todas las filas antes de `y`; `n` cancela. Las filas desconocidas son informativas. Packages y Apps solo se gestionan en el ámbito global de usuario; Packages requieren aprobación de acción antes de ejecutar una actualización. |
539
+ | Profile | `e` exportar, `i` importar; introduce una ruta de archivo, `space` alterna opciones, `a` alterna todas, `Enter` previsualiza, `y` aplica; `j`/`k` desplazan las previsualizaciones y los resultados |
540
+
541
+ Las previsualizaciones desplazables muestran todas las declaraciones y los términos de ejecución. Usa `j`/`k` para avanzar una fila, o las flechas y `PageUp`/`PageDown` para avanzar una página visible. Confirma solo después de mostrar cada fila física. Si cambias el tamaño del terminal, revisa las filas que aún no se han mostrado.
534
542
 
535
543
  Al eliminar una receta App se comprueban su comando de versión aprobado y su
536
544
  seguimiento. Si la App está instalada o sigue registrada, la TUI ofrece el flujo
@@ -777,8 +785,10 @@ variable no está definida): la raíz del checkout, `origin`, el commit `HEAD` y
777
785
  paquete sigue instalado, pero su versión no está disponible. Si los cambios locales
778
786
  hacen que el checkout esté modificado, Agent Depot muestra la versión de `HEAD`, pero
779
787
  no usa el commit para evaluar actualizaciones. Installations y `package inspect`
780
- indican `(HEAD; local changes)` en la etiqueta. Tanto los cambios en archivos
781
- controlados como los archivos sin seguimiento cuentan; Agent Depot no ignora archivos
788
+ indican `(HEAD; local changes)` en la etiqueta. La fila de Installations muestra
789
+ `update not checked · local changes`; Updates sigue indicando `cannot assess` y no
790
+ ofrece el Package para actualizar. Tanto los cambios en archivos controlados como
791
+ los archivos sin seguimiento cuentan; Agent Depot no ignora archivos
782
792
  de bloqueo ni otros archivos locales. La versión del manifiesto sirve para mostrar
783
793
  información, pero no demuestra que el checkout coincida con la Source seleccionada.
784
794
  La evaluación de actualizaciones compara el commit de un checkout limpio con el
@@ -931,16 +941,22 @@ combinar el filtro de inclusión de un bloque con su indicador `--no-*` son erro
931
941
 
932
942
  La salida estándar contiene solo JSON; stderr muestra la previsualización y los
933
943
  motivos de exclusión. `--out` requiere un archivo nuevo (los archivos existentes y
934
- los enlaces simbólicos no se reemplazan). Los Profiles contienen URL de Git
935
- canónicas con su inclusión en el descubrimiento, las políticas de versión/Hosts/
936
- métodos de usuario registrados de los Skills, recipes de App completas, incluidas
937
- las recipes de otras plataformas, y selecciones de Package solo como declaraciones:
938
- su Source, coordenadas de Host y política de versión. La exportación por CLI siempre
939
- escribe
940
- `included: true`; las elecciones de descubrimiento son transitorias, mientras que la
944
+ los enlaces simbólicos no se reemplazan). Los Profiles contienen URL de Git canónicas con su inclusión en el descubrimiento,
945
+ selecciones individuales de Skills con sus políticas de versión, Hosts y métodos de
946
+ usuario registrados, recipes de App completas (incluidas las de otras plataformas) y
947
+ selecciones de Package como declaraciones, con su origen Source completo,
948
+ coordenadas de Host y política de versión. Un Profile v2 también puede incluir
949
+ intención de réplica, solo con la identidad de Skills, Apps y Packages cuya
950
+ instalación se confirmó en la máquina de origen. No incluye estado de instalación,
951
+ recibos, aprobaciones ni rutas locales. El parser actual lee los
952
+ Profiles v1 anteriores; los lectores antiguos que solo admiten v1 rechazan los v2.
953
+ La exportación por CLI siempre escribe `included: true`; las elecciones de descubrimiento son transitorias, mientras que la
941
954
  TUI puede pasar elecciones de inclusión explícitas al núcleo de exportación
942
- compartido. La exportación no refresca Sources ni ejecuta comandos de Apps, y un
943
- recibo de Package nunca viaja en un Profile.
955
+ compartido. La exportación no refresca Sources ni ejecuta comandos del ciclo de vida.
956
+ Puede ejecutar la sonda existente de versión de App aprobada e inspeccionar Packages
957
+ sin escritura para determinar la intención de réplica actual; un estado desconocido no
958
+ se considera instalado y la inspección no reconcilia registros. Los recibos de Package
959
+ nunca viajan en un Profile.
944
960
 
945
961
  Las rutas locales y las credenciales evidentes no son portables. Los elementos no
946
962
  portables o inválidos se excluyen con sus motivos, al igual que los Skills no
@@ -1004,9 +1020,15 @@ registro; la previsualización nombra las Sources no registradas que se obtendr
1004
1020
  registrarse. Las filas de Sources existentes explican cuándo la inclusión en el
1005
1021
  descubrimiento difiere pero no se importa.
1006
1022
 
1007
- La pestaña **5 Import/Export** de la TUI ofrece listas de verificación agrupadas y
1008
- las mismas previsualizaciones del núcleo compartido que la CLI. Las exclusiones y los
1009
- conflictos no pueden seleccionarse. La exportación solo crea un archivo nuevo; las
1023
+ La pestaña **5 Import/Export** de la TUI empieza con el grupo padre **Sources**
1024
+ contraído. Dentro contiene grupos anidados para elecciones de Source, Skills, Apps y
1025
+ Packages; cada recurso muestra la identidad de su Source. **Installed replicas** y las
1026
+ notas de exportación son grupos separados. Usa las mismas previsualizaciones del núcleo
1027
+ compartido que la CLI. Las réplicas identifican recursos cuya instalación se confirmó
1028
+ en la máquina de origen; no incluyen el estado de instalación, las aprobaciones ni el
1029
+ contenido completo de la máquina. La importación solo añade; nunca transfiere
1030
+ aprobaciones ni instala automáticamente Apps o Packages. Las exclusiones y los conflictos
1031
+ no pueden seleccionarse. La exportación solo crea un archivo nuevo; las
1010
1032
  exportaciones vacías no escriben nada (la CLI informa de un error). La importación
1011
1033
  añade las elecciones que faltan sin reemplazar las existentes. Las recipes importadas
1012
1034
  permanecen sin aprobar y la importación nunca instala Apps. Las selecciones de Package
package/README.md CHANGED
@@ -19,8 +19,9 @@ What it does:
19
19
  needs explicit confirmation.
20
20
  - **Existing installations**: report unmanaged Skills already on disk, adopt the
21
21
  ones that exactly match a Source, or remove them by explicit path.
22
- - **Apps**: install, update and uninstall user-global applications through
23
- user-written App recipes that must be approved before they run.
22
+ - **Apps**: manage user-global applications through user-written App recipes.
23
+ Approval authorizes recipe commands but does not install an App; installation
24
+ has its own preview and confirmation.
24
25
  - **Packages**: install, update and uninstall host-native bundles (Pi packages and
25
26
  Claude Code plugins) by delegating the lifecycle to each Host's own CLI and
26
27
  reading Host-owned installation and version evidence. A bundle reserves
@@ -461,28 +462,33 @@ The TUI is an [Ink](https://github.com/vadimdemedes/ink) (React for terminals)
461
462
  front end over the same operations as the CLI. It needs Node.js >= 24 and an
462
463
  interactive terminal; otherwise `tui` fails with an error. It uses the terminal's
463
464
  alternate screen and returns to the previous screen when you quit. It adjusts to
464
- terminal resizing. Switch views with `1`-`5`, quit with `q` (not while a prompt is
465
- open). Lists scroll when they are taller than the terminal. In Sources, Catalog,
465
+ terminal resizing. The bordered header shows the running package version and all five numbered views.
466
+ Switch views with `1`-`5` or Tab/Shift+Tab, and quit with `q` (not while a prompt
467
+ is open). The Xeo palette distinguishes resource roles and status; `y` confirmations
468
+ are bold green. Lists scroll when they are taller than the terminal. In Sources, Catalog,
466
469
  Installations, and Updates, `j`/`k` or the arrows move one row; `PageUp` and
467
- `PageDown` move by one visible page.
470
+ `PageDown` move by one visible page. Use a terminal at least 40 columns wide to
471
+ review Profile export notes; at 24 columns, the app header tab and note title clip.
468
472
 
469
473
  | View | Purpose |
470
474
  | --- | --- |
471
475
  | 1 Sources | Git Sources (their skills and Packages) and user-owned App recipes in one list, with `skills`/`app` kinds and contextual actions. |
472
- | 2 Catalog | Available Skills and Packages, grouped by Source. Host-confirmed installed Packages are hidden; selected-only Packages remain visible. An active Package-owned Skill stays at normal contrast with a compact owner badge; it cannot be marked or installed separately. Move the cursor to it to see why. |
473
- | 3 Installations | What is on disk: managed installations (user-global and project), Package selections and unmanaged user-global skills. |
474
- | 4 Updates | Managed skills and Packages with a newer version available, for user-global and project scope. |
475
- | 5 Import/Export | Transfer portable user-global Source choices, Skill selections, App recipes and Package selections. |
476
+ | 2 Catalog | Available Skills and Packages, plus applicable approved App recipes verified as not installed. App installation has a separate preview and confirmation. Invalid or unapproved recipes remain in Sources; unknown App state is never treated as absence. Host-confirmed installed Packages are hidden; selected-only or unknown-state Packages remain visible. Package-owned Skills keep their existing behavior. |
477
+ | 3 Installations | Confirmed installed Apps, managed Skill installations (user-global and project), Package selections with Host membership and version evidence shown separately, and unmanaged user-global Skills. App installation records remain visible if recipe approval later expires, but recipe actions still require current approval. |
478
+ | 4 Updates | Managed Skills in project and user-global scope, plus user-global installed Apps and Packages with a newer version available. Unknown or unverified Apps are not treated as installed. |
479
+ | 5 Profile | Export or import portable user-global Source choices, individual Skill selections, App recipes and Package selections. Replica entries report confirmed origin-machine identities only. |
476
480
 
477
481
  Keys per view (prompts also accept `Esc` to cancel and `y`/`n` to confirm):
478
482
 
479
483
  | View | Keys |
480
484
  | --- | --- |
481
- | Sources | `j`/`k` move through Git Sources and App recipes (no Tab section switch). `Enter` opens the Catalog for a Git Source (its skills and Packages) or previews/approves an App recipe. `n` adds a reusable Git Source or guides recipe authoring; `r` refreshes Sources or reloads recipe status; `d` removes. `space`/`a` mark Git Sources only. |
482
- | Catalog | `Enter`/arrows expand and collapse, `space` toggle the highlighted loose Skill, `a` toggle all loose Skills in the highlighted Source that match the current filter, `i` install marked Skills that match the filter or the highlighted loose Skill, or preview the highlighted Package, `/` filter, `s` toggle one Source or all (when opened from a Source). Marks in other Sources remain unchanged. |
483
- | Installations | `Enter`/arrows expand and collapse, `u` uninstall (all Hosts or chosen Hosts) or remove an unmanaged skill, `h` or `i` add Hosts to an installation, `A` or `Enter` adopt an unmanaged skill, `space` marks a leaf and `a` marks all visible leaves: with marks, `u` and `h` act on all of them with one combined preview and one confirmation (project installations are skipped with a reason). On a Package row: `i` previews the frozen install plan and runs it, approving the receipt first when it is missing, `Enter` approves the install declaration only, `U` update, `u` uninstall, `f` forget the selection record. Package rows are not markable for bulk actions. |
484
- | Updates | `space` mark, `a` mark all, `t`/`p`/`g` show all, project or user-global, `Enter` preview the marks, `r` check again, arrows fold the "cannot be checked" group. Rows in that group are informational and cannot be marked for an update. A marked Package joins the batch and is approved before its update runs. |
485
- | Import/Export | `e` export, `i` import; enter a file path, `space` toggle choices, `a` toggle all, `Enter` preview, `y` apply; `j`/`k` scroll previews and results |
485
+ | Sources | `j`/`k` move through Git Sources and App recipes. `Enter` opens the Catalog for a Git Source or previews/approves an App recipe. Approval does not install the App. `n` adds a Git Source or guides recipe authoring; `r` refreshes Sources or reloads recipe status; `d` removes. `space`/`a` mark Git Sources only. Refresh previews show every selected Source; review every physical row before `y`. `n` cancels. |
486
+ | Catalog | `Enter`/arrows expand and collapse, `space` marks the highlighted loose Skill, `a` marks or clears the visible loose Skills in its Source, and `i` starts installation for marked Skills or the highlighted Skill, Package, or verified-absent approved App. Confirm an App install separately with `y`. `/` filters and `s` widens a Source view to all Sources. Marks in other Sources remain unchanged; Skills hidden by the filter are not installed. |
487
+ | Installations | `Enter`/arrows expand and collapse. On user-global rows, `u` starts an App or managed Skill uninstall, or removes an unmanaged Skill; project Skills cannot be removed. `h` or `i` adds Hosts to a user-global Skill installation, and `A` or `Enter` adopts an unmanaged Skill. `space` marks a leaf and `a` marks all visible leaves; bulk actions skip project installations with a reason. On an App row, `u` previews uninstall and `h` selects setup/teardown; changed recipes must be approved again before commands run. On a Package row, `i` previews the frozen install plan and runs it, approving the receipt first when it is missing; `Enter` approves the install declaration only, `U` updates, `u` uninstalls, and `f` forgets the selection record. Package rows are not markable for bulk actions. |
488
+ | Updates | `space` marks an update, `a` marks all available updates, and `t`/`p`/`g` selects all, project, or user-global scope. `Enter` previews the marked rows; `u` previews the selected batch; neither applies updates. `r` checks again, and left/right arrows fold or unfold the "cannot be checked" group. In a preview, arrows or PageUp/PageDown move by a visible page and `j`/`k` move one row. Review every row before `y`; `n` cancels. Unknown rows are informational. Packages and Apps are user-global only; Packages need action approval before update execution. |
489
+ | Profile | `e` export, `i` import; enter a file path, `space` toggle choices, `a` toggle all, `Enter` preview, `y` apply; `j`/`k` scroll previews and results |
490
+
491
+ Scrollable previews show every declaration and execution term. Use `j`/`k` to move one row, or the arrows and `PageUp`/`PageDown` to move by a visible page. Confirm only after every physical row has been displayed. Resizing a preview requires reviewing any rows that have not yet been displayed.
486
492
 
487
493
  Removing an App recipe checks its approved version command and tracked state.
488
494
  If the App is installed or tracked, the TUI offers the existing uninstall flow;
@@ -707,9 +713,10 @@ configured package. Agent Depot then verifies the checkout under
707
713
  `HEAD` cannot be verified, the package remains installed but its version is
708
714
  unavailable. If local changes make the checkout dirty, Agent Depot still displays
709
715
  the version from `HEAD` but withholds the commit, so Updates reports `cannot assess`.
710
- Installations and `package inspect` mark that label as `(HEAD; local changes)`.
711
- Tracked and untracked changes both count; Agent Depot does not ignore lockfiles or
712
- other local files. A manifest version is display evidence, not proof that the
716
+ Installations and `package inspect` mark that label as `(HEAD; local changes)`. The
717
+ Installations row says `update not checked · local changes`; Updates still reports
718
+ `cannot assess` and does not offer the Package for update. Tracked and untracked
719
+ changes both count; Agent Depot does not ignore lockfiles or other local files. A manifest version is display evidence, not proof that the
713
720
  checkout matches the selected Source. Update assessment compares a clean checkout's
714
721
  commit with the available Source commit using ancestry in the cached Source history.
715
722
  For Claude, Agent Depot reads the plugin and marketplace state files. The Host owns
@@ -857,13 +864,20 @@ independently. Unknown selections and combining a block's inclusion filter with
857
864
 
858
865
  Stdout contains JSON only; stderr shows the preview and exclusion reasons.
859
866
  `--out` requires a new file (existing files and symlinks are not replaced).
860
- Profiles contain canonical Git URLs with discovery inclusion, recorded Skill
861
- version policies/Hosts/user methods, complete App recipes, including recipes for
862
- other platforms, and Package selections as declarations only: their Source, host
863
- coordinates and version policy. CLI export always writes `included: true`; discovery choices are transient, while
867
+ Profiles contain canonical Git URLs with discovery inclusion, individual Skill
868
+ selections with recorded version policies, Hosts and user methods, complete App
869
+ recipes (including recipes for other platforms), and Package selections as
870
+ declarations only, with their full Source origin, Host coordinates and version
871
+ policy. A v2 Profile may also contain identity-only replica intent for Skills, Apps
872
+ and Packages confirmed installed on the exporting machine. These entries do not
873
+ carry installed state, receipts, approvals or local paths.
874
+ The current parser reads legacy v1 files; v1-only older readers reject v2 files.
875
+ CLI export always writes `included: true`; discovery choices are transient, while
864
876
  the TUI may pass explicit inclusion choices to the shared export core.
865
- Export neither refreshes Sources nor runs App commands, and a Package receipt never
866
- travels in a Profile.
877
+ Export neither refreshes Sources nor runs lifecycle commands. It may run the existing
878
+ approved App version probe and read-only Package inspection to determine current
879
+ replica intent; unknown state is not treated as installed, and inspection does not
880
+ reconcile records. Package receipts never travel in a Profile.
867
881
 
868
882
  Local paths and obvious credentials are not portable. Nonportable/invalid items
869
883
  are excluded with reasons, as are unmanaged/App-owned and project Skills,
@@ -917,8 +931,13 @@ so omitting their Source block does not force registration; the preview names
917
931
  unregistered Sources that will be fetched without registering. Existing Source
918
932
  rows explain when discovery inclusion differs but is not imported.
919
933
 
920
- The **5 Import/Export** TUI tab offers grouped checklists and the same shared-core
921
- previews as the CLI. Exclusions and conflicts cannot be selected. Export creates
934
+ The **5 Import/Export** TUI tab starts with a collapsed **Sources** parent. It
935
+ contains nested Source choices, Skills, Apps, and Packages groups; resource rows show
936
+ their Source identities. **Installed replicas** and export notes are separate groups.
937
+ Replica entries identify resources confirmed installed on the exporting machine.
938
+ They do not contain installed state, approvals, or full machine contents. Import is
939
+ add-only; it never transfers approval or automatically installs Apps or Packages.
940
+ Exclusions and conflicts cannot be selected. Export creates
922
941
  a new file only; empty exports write nothing (CLI reports an error). Import adds missing choices without replacing existing ones.
923
942
  Imported recipes remain unapproved and Apps are never installed by import.
924
943
  Imported Package selections are recorded without installing a bundle and without
@@ -29,18 +29,23 @@ export function createAppOperations(environment = {}) {
29
29
  });
30
30
  const receiptPath = (entry) => path.join(receipts, createHash("sha256").update(entry.canonicalFile).digest("hex") + ".json");
31
31
  async function read(file) {
32
+ let canonicalFile;
33
+ let content;
34
+ try {
35
+ canonicalFile = await realpath(file);
36
+ content = await readFile(canonicalFile);
37
+ }
38
+ catch (error) {
39
+ return { file, applicable: true, error: error instanceof Error ? error.message : String(error) };
40
+ }
41
+ const hash = createHash("sha256").update(content).digest("hex");
32
42
  try {
33
- const canonicalFile = await realpath(file);
34
- const content = await readFile(canonicalFile);
35
43
  const recipe = parseAppRecipe(JSON.parse(content.toString("utf8")));
36
44
  const currentPlatform = platform === "win32" ? "windows" : platform;
37
- return {
38
- file, canonicalFile, recipe, hash: createHash("sha256").update(content).digest("hex"),
39
- applicable: recipe.platform === undefined || recipe.platform === currentPlatform,
40
- };
45
+ return { file, canonicalFile, recipe, hash, applicable: recipe.platform === undefined || recipe.platform === currentPlatform };
41
46
  }
42
47
  catch (error) {
43
- return { file, applicable: true, error: error instanceof Error ? error.message : String(error) };
48
+ return { file, canonicalFile, hash, applicable: true, error: error instanceof Error ? error.message : String(error) };
44
49
  }
45
50
  }
46
51
  function rejectDuplicates(entries) {
@@ -82,14 +87,21 @@ export function createAppOperations(environment = {}) {
82
87
  ...Object.entries({
83
88
  install: recipe.install, update: recipe.update, uninstall: recipe.uninstall, version: recipe.version,
84
89
  }),
85
- ...(recipe.latest && "argv" in recipe.latest ? [["latest", recipe.latest]] : []),
90
+ ...(recipe.latest ? [["latest", recipe.latest]] : []),
86
91
  ...Object.entries(recipe.setup ?? {}).map(([host, step]) => [`setup.${host}`, step]),
87
92
  ...Object.entries(recipe.teardown ?? {}).map(([host, step]) => [`teardown.${host}`, step]),
88
93
  ];
89
94
  return Promise.all(steps.map(async ([step, value]) => {
90
- const resolution = value.argv ? await resolve(value.argv[0]) : undefined;
95
+ const resolution = "argv" in value && value.argv ? await resolve(value.argv[0]) : undefined;
91
96
  return {
92
- step, ...value, executable: resolution?.executable, cwd: home,
97
+ step,
98
+ ...("argv" in value && value.argv ? { argv: value.argv } : {}),
99
+ ...("manual" in value && value.manual ? { manual: value.manual } : {}),
100
+ ...("npm" in value ? { declaration: `Latest npm package: ${value.npm}` } : {}),
101
+ ...("github" in value ? { declaration: `Latest GitHub repository: ${value.github}` } : {}),
102
+ ...("pattern" in value ? { pattern: value.pattern } : {}),
103
+ executable: resolution?.executable, cwd: home,
104
+ environment: isWsl ? "linux (WSL)" : platform,
93
105
  warning: resolution?.blocked ? WINDOWS_EXECUTABLE_WARNING : undefined,
94
106
  };
95
107
  }));
@@ -138,39 +150,59 @@ export function createAppOperations(environment = {}) {
138
150
  return { status: approval.status, reason: approval.reason };
139
151
  }
140
152
  /** A supplied cache checks recipe approval without running version; omit it for a live check. */
141
- async function inspect(entry, cached) {
153
+ async function inspectEntry(entry, cached, reconcileTracking) {
142
154
  if (entry.error || !entry.recipe)
143
155
  return { status: "invalid", reason: entry.error };
144
156
  if (!entry.applicable)
145
157
  return { status: "not applicable here" };
158
+ if (cached?.installedVersion) {
159
+ return { status: "installed", installedVersion: cached.installedVersion, evidence: "recorded" };
160
+ }
146
161
  const approval = await approvalProblem(entry);
147
162
  if (approval)
148
163
  return approval;
149
- if (cached)
150
- return cached.installedVersion
151
- ? { status: "installed", installedVersion: cached.installedVersion, evidence: "recorded" }
152
- : { status: "not installed" };
153
- const { executable, blocked } = await resolve(entry.recipe.version.argv[0]);
154
- if (!executable || blocked) {
155
- return { status: "not installed", executable, reason: blocked ? WINDOWS_EXECUTABLE_WARNING : undefined };
164
+ let resolution;
165
+ try {
166
+ resolution = await resolve(entry.recipe.version.argv[0]);
167
+ }
168
+ catch (error) {
169
+ return { status: "unknown", reason: error instanceof Error ? error.message : String(error) };
156
170
  }
171
+ const { executable, blocked } = resolution;
172
+ if (blocked)
173
+ return { status: "unknown", executable, reason: WINDOWS_EXECUTABLE_WARNING };
174
+ if (!executable)
175
+ return { status: "not installed", reason: "Version executable is unavailable" };
157
176
  let installedVersion;
177
+ let result;
158
178
  try {
159
- const result = await runner(executable, entry.recipe.version.argv.slice(1), {
179
+ result = await runner(executable, entry.recipe.version.argv.slice(1), {
160
180
  cwd: home, captureStdout: true, maxOutputBytes: APP_OUTPUT_LIMIT, maxStderrBytes: APP_OUTPUT_LIMIT,
161
181
  });
162
182
  installedVersion = result.code === 0 && !result.signal && !result.outputTooLarge
163
183
  ? new RegExp(entry.recipe.version.pattern).exec(result.stdout.toString("utf8"))?.[1]
164
184
  : undefined;
165
185
  }
166
- catch {
167
- return { status: "not installed", executable };
186
+ catch (error) {
187
+ return { status: "unknown", executable, reason: error instanceof Error ? error.message : String(error) };
168
188
  }
169
- if (!installedVersion)
189
+ if (result.signal)
190
+ return { status: "unknown", executable, reason: `Version command terminated by signal ${result.signal}` };
191
+ if (result.code !== 0)
170
192
  return { status: "not installed", executable };
171
- await reconcileTracked(entry, installedVersion);
193
+ if (!installedVersion || result.outputTooLarge) {
194
+ return { status: "unknown", executable, reason: result.outputTooLarge ? "Version output exceeded the limit" : "Version output did not match the recipe" };
195
+ }
196
+ if (reconcileTracking)
197
+ await reconcileTracked(entry, installedVersion);
172
198
  return { status: "installed", executable, installedVersion, evidence: "verified" };
173
199
  }
200
+ async function inspect(entry, cached) {
201
+ return await inspectEntry(entry, cached, true);
202
+ }
203
+ async function inspectForExport(entry) {
204
+ return await inspectEntry(entry, undefined, false);
205
+ }
174
206
  async function checkUpdate(entry) {
175
207
  const inspection = await inspect(entry);
176
208
  let latestVersion;
@@ -448,7 +480,7 @@ export function createAppOperations(environment = {}) {
448
480
  throw error;
449
481
  });
450
482
  }
451
- return { directory, approvalStatus, checkUpdate, load, preview, approve, inspect, planLifecycle, executeLifecycle, completeManual, cancelManual, trackedApps, previewForget, forgetApp };
483
+ return { directory, approvalStatus, checkUpdate, load, preview, approve, inspect, inspectForExport, planLifecycle, executeLifecycle, completeManual, cancelManual, trackedApps, previewForget, forgetApp };
452
484
  }
453
485
  /** Concurrent independent checks; one broken App must not hide the rest of a batch. */
454
486
  export async function checkAppUpdates(apps) {
@@ -4,9 +4,10 @@ import { loadAppOwnedSkillFilter } from "./app-owned-skills.js";
4
4
  import { sourceIdForUrl } from "./git-source.js";
5
5
  import { bundleRoot, describeCoordinates } from "./package-model.js";
6
6
  import { AGENT_DEPOT_PACKAGE_VERSION } from "./project-manifest.js";
7
- import { parseProfile, PROFILE_FORMAT, serializeProfile } from "./profile.js";
7
+ import { parseProfile, PROFILE_FORMAT, PROFILE_FORMAT_V2, serializeProfile } from "./profile.js";
8
8
  import { formatVersionPolicy } from "./skill-install.js";
9
9
  import { scanUserGlobalSkillInventory } from "./user-global-skill-inventory.js";
10
+ export const SKILL_EXPORT_EXPLANATION = "Selected Skills are exported with their version policy; destination install paths and Host links are recreated. Local paths and symlink targets do not travel.";
10
11
  export function selectProfileItems(items, requested, disabled, names, block) {
11
12
  if (disabled && requested?.length)
12
13
  throw new Error(`${block}: filters cannot be combined with --no-${block}`);
@@ -32,7 +33,24 @@ export function packageFilterNames(selection) {
32
33
  }
33
34
  /** One Package line, shared by the export preview and the import plan label. */
34
35
  export function describeProfilePackage(selection) {
35
- return `Package ${describeCoordinates(selection)} (${formatVersionPolicy(selection.version)})`;
36
+ return `Package ${describeCoordinates(selection)} from ${profileSourceIdentity(selection.source)} (${formatVersionPolicy(selection.version)}; saved selection)`;
37
+ }
38
+ export function describeProfileSkill(skill) {
39
+ return `Skill ${skill.path} from ${profileSourceIdentity(skill.source)} (${skill.version.policy})`;
40
+ }
41
+ export function describeProfileReplica(replica) {
42
+ switch (replica.kind) {
43
+ case "skill": return `Installed Skill ${replica.path} from ${profileSourceIdentity(replica.source)}`;
44
+ case "app": return `Installed App ${replica.name} (${replica.platform ?? "all platforms"})`;
45
+ case "package": return `Installed Package ${describeCoordinates(replica.selection)} from ${profileSourceIdentity(replica.selection.source)}`;
46
+ }
47
+ }
48
+ function profileSourceIdentity(source) {
49
+ if (source.kind === "builtin")
50
+ return "Agent Depot (built-in Source)";
51
+ if ("url" in source)
52
+ return source.url;
53
+ return source.path;
36
54
  }
37
55
  function validateOne(block, item) {
38
56
  const profile = parseProfile({ format: PROFILE_FORMAT, agentDepotVersion: AGENT_DEPOT_PACKAGE_VERSION,
@@ -77,6 +95,7 @@ export async function buildProfileExport(operations, apps, options = {}) {
77
95
  }
78
96
  }
79
97
  const skills = [];
98
+ const replicas = [];
80
99
  for (const skill of installations) {
81
100
  if (owned(path.posix.basename(skill.installation?.path ?? skill.path))) {
82
101
  exclude("skills", `Skill ${skill.path}`, "App-owned");
@@ -86,19 +105,44 @@ export async function buildProfileExport(operations, apps, options = {}) {
86
105
  const selection = { source, path: skillPath, version, hosts, ...(methods ? { methods } : {}) };
87
106
  try {
88
107
  skills.push(validateOne("skills", selection));
89
- if (skill.installation)
90
- exclude("skills", `installation location and version evidence for Skill ${skill.path}`, "machine-specific");
108
+ replicas.push({ kind: "skill", source: skill.source, path: skill.path });
91
109
  }
92
110
  catch (error) {
93
111
  exclude("skills", `Skill ${skill.path}`, `not portable (${error instanceof Error ? error.message : String(error)})`);
94
112
  }
95
113
  }
96
114
  const recipes = [];
115
+ let appInstallations = [];
116
+ try {
117
+ appInstallations = await apps.trackedApps(warn);
118
+ }
119
+ catch (error) {
120
+ warn(`App installation records could not be read: ${error instanceof Error ? error.message : String(error)}`);
121
+ }
97
122
  for (const entry of entries) {
98
123
  try {
99
124
  if (entry.error)
100
125
  throw new Error(entry.error);
101
126
  recipes.push(validateOne("apps", entry.recipe));
127
+ const tracked = entry.recipe && entry.canonicalFile && appInstallations.find(record => record.name === entry.recipe.name && path.resolve(record.recipeFile) === path.resolve(entry.canonicalFile));
128
+ let inspection;
129
+ try {
130
+ inspection = await apps.inspectForExport(entry);
131
+ }
132
+ catch (error) {
133
+ exclude("replicas", `App ${entry.recipe.name} (${entry.recipe.platform ?? "all platforms"})`, `current installation state could not be read, so it is not marked for replication (${error instanceof Error ? error.message : String(error)})`);
134
+ continue;
135
+ }
136
+ if (inspection.status === "installed" && entry.recipe) {
137
+ replicas.push({ kind: "app", name: entry.recipe.name,
138
+ ...(entry.recipe.platform === undefined ? {} : { platform: entry.recipe.platform }) });
139
+ }
140
+ else if (tracked && inspection.status !== "not installed") {
141
+ exclude("replicas", `App ${entry.recipe.name} (${entry.recipe.platform ?? "all platforms"})`, `a tracked install record exists, but current installation state is ${inspection.status}${inspection.reason ? ` (${inspection.reason})` : ""}; not marked for replication`);
142
+ }
143
+ else if (tracked && inspection.status === "not installed") {
144
+ exclude("replicas", `App ${entry.recipe.name} (${entry.recipe.platform ?? "all platforms"})`, "the current version probe reports not installed; stale tracking does not create replica intent");
145
+ }
102
146
  }
103
147
  catch (error) {
104
148
  exclude("apps", `App recipe ${path.basename(entry.file)}`, error instanceof Error ? error.message : String(error));
@@ -108,6 +152,17 @@ export async function buildProfileExport(operations, apps, options = {}) {
108
152
  for (const record of selectedPackages) {
109
153
  try {
110
154
  packages.push(validateOne("packages", record.selection));
155
+ try {
156
+ const inspection = await options.packageOperations?.inspect(record.selection);
157
+ if (inspection?.installed.kind === "installed")
158
+ replicas.push({ kind: "package", selection: record.selection });
159
+ else if (inspection?.installed.kind === "unknown") {
160
+ exclude("replicas", describeProfilePackage(record.selection), `host installation state is unknown, so it is not marked for replication (${inspection.installed.reason})`);
161
+ }
162
+ }
163
+ catch (error) {
164
+ exclude("replicas", describeProfilePackage(record.selection), `host installation state could not be read, so it is not marked for replication (${error instanceof Error ? error.message : String(error)})`);
165
+ }
111
166
  }
112
167
  catch (error) {
113
168
  exclude("packages", describeProfilePackage(record.selection), `not portable (${error instanceof Error ? error.message : String(error)})`);
@@ -122,8 +177,8 @@ export async function buildProfileExport(operations, apps, options = {}) {
122
177
  exclude("skills", `unmanaged Skill ${name}`, "not tracked");
123
178
  }
124
179
  }
125
- const profile = parseProfile({ format: PROFILE_FORMAT, agentDepotVersion: AGENT_DEPOT_PACKAGE_VERSION,
126
- sources: portableSources, skills, apps: recipes, packages });
180
+ const profile = parseProfile({ format: replicas.length ? PROFILE_FORMAT_V2 : PROFILE_FORMAT, agentDepotVersion: AGENT_DEPOT_PACKAGE_VERSION,
181
+ sources: portableSources, skills, apps: recipes, packages, ...(replicas.length ? { replicas } : {}) });
127
182
  return {
128
183
  profile,
129
184
  exclusions: diagnostics.filter((item) => "block" in item),
@@ -140,14 +195,22 @@ function sourceWarnings(profile) {
140
195
  ...profile.packages.flatMap(selection => missing(selection.source, describeProfilePackage(selection))),
141
196
  ];
142
197
  }
198
+ /** Describes whether an export diagnostic removes a selection or only omits machine-local metadata. */
199
+ export function describeProfileExportExclusion(item) {
200
+ if (item.reason.startsWith("always present"))
201
+ return `Not transferred because it is always present: ${item.label}`;
202
+ return `Excluded ${item.label}: ${item.reason}`;
203
+ }
143
204
  /** Shared preview for a frontend-selected subset; Source warnings are recomputed. */
144
205
  export function profileExportPreview(profile, diagnostics = []) {
145
206
  return ["Preview: export portable user-global profile",
207
+ SKILL_EXPORT_EXPLANATION,
146
208
  ...profile.sources.map(source => `Source ${sourceIdForUrl(source.url)} ${source.url} (included: ${source.included})`),
147
209
  ...profile.skills.map(skill => `Skill ${skill.path} [${skill.hosts.join(", ")}] (${skill.version.policy})`),
148
- ...profile.apps.map(recipe => `App recipe ${recipe.name} (${recipe.platform ?? "all platforms"}; no approval)`),
149
- ...profile.packages.map(selection => `${describeProfilePackage(selection)}; no receipt, no installed state`),
150
- ...diagnostics.flatMap(item => "block" in item ? [`Excluded ${item.label}: ${item.reason}`] : item.kind === "load" ? [item.message] : []),
210
+ ...profile.apps.map(recipe => `App recipe ${recipe.name} (${recipe.platform ?? "all platforms"}; approval not transferred)`),
211
+ ...profile.packages.map(selection => `${describeProfilePackage(selection)}; no receipt or installed evidence`),
212
+ ...profile.replicas.map(replica => `Replica intent ${describeProfileReplica(replica)}; no installed state or approval is transferred`),
213
+ ...diagnostics.flatMap(item => "block" in item ? [describeProfileExportExclusion(item)] : item.kind === "load" ? [item.message] : []),
151
214
  ...sourceWarnings(profile).map(warning => warning.message)];
152
215
  }
153
216
  /** Empty profiles are valid to parse, but are not useful export artifacts. */
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  import { compareAppVersions } from "./app-version.js";
6
6
  import { canonicalizeGitSourceUrl, sourceIdForUrl } from "./git-source.js";
7
7
  import { selectionKey } from "./package-model.js";
8
- import { describeProfilePackage, packageFilterNames, selectProfileItems } from "./profile-export.js";
8
+ import { describeProfilePackage, describeProfileReplica, packageFilterNames, selectProfileItems } from "./profile-export.js";
9
9
  import { parseProfile, PROFILE_FORMAT } from "./profile.js";
10
10
  import { AGENT_DEPOT_PACKAGE_VERSION } from "./project-manifest.js";
11
11
  import { defaultProjectSkillTreeAccess, executeSingleInstall, formatVersionPolicy, outputSingleInstallPreview, planSingleInstall } from "./skill-install.js";
@@ -175,7 +175,8 @@ export async function buildProfileImport(input, operations, apps, filters = {},
175
175
  if (AGENT_DEPOT_PACKAGE_VERSION && (compareAppVersions(profile.agentDepotVersion, AGENT_DEPOT_PACKAGE_VERSION) ?? 0) > 0) {
176
176
  preview.push(`WARNING: profile Agent Depot ${profile.agentDepotVersion} is newer than running ${AGENT_DEPOT_PACKAGE_VERSION}`);
177
177
  }
178
- preview.push("Existing choices are never replaced. Source inclusion is a transient discovery choice.", "Same-name App recipes conflict only when platforms overlap; disjoint platform recipes can both be added.");
178
+ preview.push(...profile.replicas.map(replica => `Origin replica intent: ${describeProfileReplica(replica)}; importing this intent does not approve or install it`));
179
+ preview.push("Existing choices are never replaced. Source inclusion is a transient discovery choice.", "Replica intent never triggers approval or installation. Apps remain unapproved; Package selection does not install.", "Same-name App recipes conflict only when platforms overlap; disjoint platform recipes can both be added.");
179
180
  return { items, packages, preview };
180
181
  }
181
182
  /** Publish complete JSON exclusively: rename would silently overwrite an existing recipe. */