initial skeleton

This commit is contained in:
stoorps 2026-03-19 18:46:50 +00:00
parent dc79fa2448
commit 71f89dde9c
Signed by: stoorps
SSH key fingerprint: SHA256:AZlPfu9hTu042EGtZElmDQoy+KvMOeShLDan/fYLoNI
60 changed files with 3480 additions and 0 deletions

View file

@ -0,0 +1,469 @@
# AppImage Manager Design
## Goal
Build AppImage Manager as a Rust workspace where `aim-core` contains the business logic and reusable APIs, and `aim-cli` is a thin terminal client. The first shipped application is the CLI, but the architecture must leave a clean path for a later GUI client to consume the same core install, update, registry, and adapter logic.
## Agreed Product Shape
### Command surface
- `aim {QUERY}`: search and add from a query source
- `aim`: review-first update flow; aliases `aim update`
- `aim update`: explicit update flow
- `aim remove {QUERY}`: remove by registered app name
- `aim list`: list installed AppImages
### Supported source types
1. GitHub Releases
2. Direct URL / generic website downloads
3. GitLab Releases
4. zsync / embedded AppImage update info
5. SourceForge
6. Custom JSON feed adapters
### Install behavior
- Default installation mode is auto-detected by effective privileges
- `--system` and `--user` override the auto-detected scope
- The tool supports both user and system installations
- The tool performs full desktop-style integration for installed apps
### Identity and update behavior
- The system should infer app identity and version when possible
- If confidence is low, the client should prompt interactively for confirmation or edits
- If identity still cannot be stabilized, the registry should fall back to the raw URL as the last-resort key
- Running `aim` with no query should discover updates, present a review list, and then apply only selected updates
- Architecture handling should remain generic: `aim-core` manages whatever AppImage artifact is resolved, while validating obvious mismatches at install time
## Recommended Architecture
Use typed source adapters behind a common update engine, packaged in `aim-core` and consumed by thin frontend clients.
This architecture fits the source diversity without forcing a plugin runtime into v1. Each upstream source gets an explicit Rust adapter that implements a shared contract for identity resolution, release discovery, artifact selection, and update metadata extraction. The shared update engine operates on normalized internal types rather than source-specific details.
This approach was selected over:
- a registry-centric-first design, which risks smearing source-specific logic across storage and service layers
- a plugin-first design, which adds packaging, security, and testing complexity too early
## Workspace Architecture
The project should be a Cargo workspace with frontend clients over a shared core crate.
### Workspace crates
- `crates/aim-core`: all business logic and reusable APIs
- `crates/aim-cli`: thin terminal frontend for the initial shipped application
- `crates/aim-gui`: deferred future GUI client, planned but not implemented in v1
The critical rule is that `aim-cli` must not become the home for install, update, registry, or source logic. If behavior should be reusable by a future GUI, it belongs in `aim-core`.
## Architecture Layers
The system should be organized into four layers, with the bottom three living in `aim-core`.
### 1. Client layer
- Implemented first in `aim-cli`
- Parses commands, flags, and defaults
- Owns presentation only: prompts, colors, spinners, progress bars, and terminal summaries
- Uses:
- `clap` for CLI parsing
- `dialoguer` for interactive prompts and multi-select review flows
- `console` for styled output and readable summaries
- `indicatif` for progress bars and spinners
This layer should translate user intent into calls into `aim-core` and render responses. It should not contain source-specific business logic, registry mutation logic, or install/update decision logic.
### 2. Application/service layer
- Lives in `aim-core`
- Coordinates workflows like add, remove, list, and update
- Applies product rules such as scope selection, update review behavior, and low-confidence identity confirmation
- Suggested services:
- `AddService`
- `UpdateService`
- `RegistryService`
- `IntegrationService`
### 3. Domain model layer
- Lives in `aim-core`
- Holds the canonical source-agnostic types used across the system
Suggested domain types:
- `AppRecord`
- `InstallScope`
- `SourceRef`
- `SourceKind`
- `ResolvedRelease`
- `InstalledArtifact`
- `UpdatePlan`
- `DesktopIntegration`
- `InteractionRequest`
- `InteractionResponse`
### 4. Infrastructure layer
- Lives in `aim-core`
- Source adapters
- Filesystem and install location management
- Registry persistence
- Desktop integration helpers
- Download and HTTP client behavior
- Optional subprocess wrappers for system integration tasks
## Suggested Module Layout
Suggested workspace layout:
- `Cargo.toml`
- `crates/aim-core/Cargo.toml`
- `crates/aim-core/src/lib.rs`
- `crates/aim-core/src/app/`
- `crates/aim-core/src/domain/`
- `crates/aim-core/src/adapters/`
- `crates/aim-core/src/integration/`
- `crates/aim-core/src/registry/`
- `crates/aim-core/src/platform/`
- `crates/aim-cli/Cargo.toml`
- `crates/aim-cli/src/lib.rs`
- `crates/aim-cli/src/main.rs`
- `crates/aim-cli/src/cli/`
- `crates/aim-cli/src/ui/`
Future-facing placeholder:
- `crates/aim-gui/`
This keeps terminal UX separate from the install/update engine and ensures the later GUI can reuse the same core APIs.
## Core Components
### Query resolver
Lives in `aim-core` and turns user input into a normalized `SourceRef`.
Accepted input forms:
- URL
- `user_or_org/project`
- file URI
- bare `aim` with no query
Behavior:
- Resolve GitHub URLs and `owner/repo` forms to GitHub when unambiguous
- Resolve GitLab URLs and explicit `gitlab:` references to GitLab
- Resolve direct URLs and generic web pages to the direct URL / web adapter
- Resolve `file://` inputs into local import flow
The query resolver should not perform install logic.
### Source adapter layer
Lives in `aim-core`, with one typed adapter per source:
- GitHub Releases adapter
- GitLab Releases adapter
- Direct URL / generic web adapter
- zsync / embedded update info adapter
- SourceForge adapter
- Custom JSON feed adapter
Each adapter should expose a shared capability shape:
- identify app
- enumerate candidate releases
- choose preferred artifact
- expose update metadata
- download or resolve the artifact for download
Not every source needs to support true search. Some only support exact resolution. The contract should represent those differences honestly.
### Registry
Lives in `aim-core` and stores normalized installed app records across user and system scopes.
It should track:
- canonical app identity
- display name
- install scope
- source type
- source locator and source-specific update hints
- installed version
- installed artifact path
- artifact fingerprint or hash
- release metadata
- integration artifact paths
- timestamps
The registry is the bridge between one-time install and repeatable updates, so it must be migration-friendly.
### Installer and integrator
Live in `aim-core`.
Installer responsibilities:
- staging downloads
- validating artifacts
- moving binaries into managed locations
- setting permissions
- replacing installed artifacts atomically where possible
Integrator responsibilities:
- `.desktop` entry generation
- icon extraction or acquisition
- symlink creation
- MIME and related registration where feasible
- correct handling of user vs system targets
Installer and integration concerns should remain separate so updates can replace binaries without always rebuilding every integration artifact.
### Update planner and executor
Live in `aim-core`.
Planner responsibilities:
- load registry entries
- ask adapters for update candidates
- compare installed state to available state
- build a reviewable `UpdatePlan`
Executor responsibilities:
- apply selected updates
- download and validate updated artifacts
- replace existing artifacts safely
- refresh integration artifacts only when needed
- update registry state
- surface typed results and events for clients
### Client interaction boundary
Terminal-specific UI belongs in `aim-cli`, not `aim-core`.
`aim-core` should expose operation APIs and typed interaction or progress models that clients can render however they want. `aim-cli` should wrap all usage of `dialoguer`, `console`, and `indicatif`.
This keeps business logic testable without terminal coupling and makes a GUI frontend viable later.
### Custom JSON feed support
Custom JSON feeds should be declarative in v1, not arbitrary executable plugins.
The adapter should support field mapping and release selection rules against a constrained schema family, rather than loading arbitrary code. This delivers flexibility without turning the CLI into a plugin host.
## End-to-End Data Flow
### `aim {QUERY}` add flow
1. `aim-cli` parses CLI input and scope override flags
2. `aim-cli` calls `aim-core` with a normalized request
3. `aim-core` resolves the query into a `SourceRef`
4. `aim-core` selects the appropriate adapter
5. `aim-core` identifies the app and candidate releases
6. `aim-core` returns an interaction state if confidence is low
7. `aim-cli` prompts, then sends the decision back to `aim-core`
8. `aim-core` falls back to raw URL identity if needed
9. `aim-core` downloads to staging
10. `aim-core` validates the artifact as an AppImage and inspects update metadata
11. `aim-core` installs into the correct managed location
12. `aim-core` generates integration artifacts and persists a normalized registry entry
### `aim` and `aim update` flow
1. `aim-cli` invokes update discovery in `aim-core`
2. `aim-core` loads relevant registry entries
3. `aim-core` asks each adapter for update candidates
4. `aim-core` builds an `UpdatePlan`
5. `aim-cli` renders the review list and collects selection
6. `aim-core` applies selected updates
7. `aim-core` refreshes registry state and integration artifacts as needed
8. `aim-cli` prints a final success/failure summary
### `aim list` flow
- `aim-cli` requests installed app state from `aim-core` and renders the result grouped by scope, source, and version
### `aim remove {QUERY}` flow
1. `aim-cli` forwards the query to `aim-core`
2. `aim-core` resolves the query against registered app names
3. `aim-core` emits an interaction request if ambiguity must be resolved
4. `aim-cli` prompts if needed and returns the selection
5. `aim-core` removes artifacts and integration files in the correct order
6. `aim-core` removes registry state while preserving uncertain shared resources conservatively
## Registry Data Shape
Each registry record should contain enough source-specific state to make updates reliable without re-deriving identity from filenames.
Recommended fields:
- stable app id
- display name
- install scope
- source kind
- source locator
- installed version
- installed file path
- file hash or fingerprint
- release metadata
- updater metadata
- integration artifact paths
- created and updated timestamps
Examples of source-specific metadata:
- GitHub/GitLab: owner, repo, release/tag, asset selection hints
- Direct URL: original URL, resolved URL, etag, last-modified when available
- zsync: zsync URL or embedded update info extracted from the AppImage
- SourceForge: project and file path hints
- Custom JSON feed: feed URL plus mapping profile
## Error Handling Model
Error handling should be structured internally and concise externally.
`aim-core` should own structured error types and machine-readable outcomes. `aim-cli` should map those into concise terminal messages. A future GUI should be able to present the same failures without reparsing CLI text.
Suggested error categories:
- query resolution error
- source adapter error
- network/download error
- artifact validation error
- install permission or scope error
- desktop integration error
- registry persistence error
- update planning error
Behavioral expectations:
- prompt on low-confidence identity rather than silently guessing
- fail clearly on insufficient privileges for system install unless explicit elevation behavior is designed later
- continue update processing across apps when one app fails
- fail-fast within a single app transaction unless a step is intentionally non-fatal
- either roll back on integration failure or explicitly record the app as installed-but-needing-repair
For v1, prefer:
- best-effort continuation across apps during update runs
- fail-fast inside a single app update or install transaction
- atomic replacement where possible
- future room for an `aim repair` command, even if not implemented in v1
## Testing Strategy
Testing should map directly to the architecture layers.
### `aim-core` unit tests
- query parsing and source resolution
- identity normalization and fallback logic
- version comparison and update selection logic
- install scope resolution
- registry serialization and migrations
- adapter-specific parsing helpers
### Shared adapter contract tests
Every adapter should pass a common behavior suite where applicable:
- can identify app
- can resolve latest candidate
- reports unsupported capabilities honestly
- produces normalized release metadata
This is the primary protection against drift across heterogeneous source implementations.
### `aim-core` integration tests
- add flow per source type using fixtures or mocked HTTP
- update planning across mixed registry entries
- remove flow cleaning registry and integration artifacts
- user vs system path resolution
- registry migration compatibility
### Filesystem tests
Use temp directories to simulate:
- user install roots
- system install roots
- desktop entry locations
- icon and symlink generation
### `aim-cli` client behavior tests
- snapshot or golden tests for key terminal flows
- update review list interaction
- low-confidence identity prompt
- success and failure summaries
Most behavioral coverage should target `aim-core`, with only thin client verification in `aim-cli`.
Avoid relying on live network tests in the main suite. Keep those as optional smoke coverage.
### Main risks the test plan must cover
1. Incorrect identity causing duplicate or non-updatable entries
2. Source-specific regressions hidden behind a shared API surface
3. Incomplete rollback leaving broken installs
4. Scope confusion causing files to land in the wrong locations
5. Business logic leaking into `aim-cli` and diverging from future GUI needs
## Recommended Persisted Formats And Key Decisions
### Persisted formats
- Use a structured registry file or registry store that is easy to migrate and inspect
- Keep source-specific update metadata embedded in each app record rather than scattered across auxiliary files
- Store integration artifact paths explicitly so removal and repair remain deterministic
### Key design decisions
- Use a Cargo workspace with `aim-core` and `aim-cli`
- Put all business logic in `aim-core`
- Keep `aim-cli` as a thin terminal adapter over `aim-core`
- Design `aim-core` to be reusable by a future `aim-gui`
- Use typed Rust adapters behind a common update engine
- Normalize identity early and once
- Separate update planning from update execution
- Treat custom JSON feeds as declarative adapters, not executable plugins
- Auto-detect scope by effective privileges, with `--system` and `--user` overrides
- Make bare `aim` a review-first update path
## Explicit v1 Boundaries
Included in v1:
- Cargo workspace with `aim-core` and `aim-cli`
- multi-source AppImage add flow
- user and system scope support
- update planning and selected update execution
- desktop-style integration
- typed adapters for the agreed source list
- declarative custom JSON feed support
Deferred from v1:
- `aim-gui` implementation
- general plugin runtime
- arbitrary executable custom adapters
- broad distro-specific deep integration beyond the agreed desktop registration model
- live network-dependent test suite as the main verification strategy
- repair and doctor commands, though the design should leave room for them
## Open Implementation Notes
- Because the current workspace is not a git repository, the design document can be saved but not committed yet
- The next step should be an implementation plan that breaks this design into small TDD-oriented tasks