-- allium: 1
-- Workspace CLI tool (RuDS issue #19; distilled from bDS2 issue #25)
-- Scope: extension (Bucket G — MCP + Automation)
-- Distilled from: lib/bds/cli.ex, lib/bds/cli/commands.ex, lib/bds/cli/install.ex,
--   rel/overlays/cli/bin/bds-cli

entity CliInvocation {
    command: rebuild | repair | render | upload | push | pull | post | media
        | gallery | config | project | server | tui | lua | install
    incremental: Boolean
    force: Boolean
    airplane: Boolean
    json_output: Boolean
    exit_code: Integer
    -- Parsed by the native Clap binary: subcommands, options, flags, and auto-generated
    -- help/version output. Unknown commands and invalid options exit 1
    -- with formatted errors on stderr.
}

surface CliSurface {
    facing _: CliRuntime

    provides:
        CliCommandExecuted(command)
        CliInstallRequested()
}

invariant SharedDatabase {
    -- The native CLI resolves the same OS application data path, settings,
    -- project registry, and cache database as the GUI/TUI app — but with no
    -- HTTP listener, no SSH daemon, no window, and no sync watcher.
    -- Console logging is redirected to the rotating log file so stdout
    -- carries only command output.
}

invariant CliWritesNotify {
    -- Every CLI mutation (post/media/gallery creation, config set,
    -- project add/switch, bulk rebuild/repair) inserts DbNotification
    -- rows (from_cli: true) so a concurrently running app picks the
    -- change up through its sync watcher (see cli_sync.allium). Bulk
    -- maintenance uses wildcard entity ids per entity type.
}

rule ExitCode {
    when: CliCommandExecuted(command)
    -- Success prints the result (stdout) and exits 0; any error prints
    -- to stderr and exits 1.
    requires: CliInvocation.command = command
    ensures: CliInvocation.exit_code.updated()
}

rule MachineOutput {
    when: CliCommandExecuted(command)
    requires: CliInvocation.json_output
    -- Successful results use one stable JSON envelope on stdout; execution
    -- errors use an error envelope on stderr and still exit 1.
    ensures: CliInvocation.exit_code.updated()
}

rule AirplaneGate {
    when: CliCommandExecuted(command)
    requires: CliInvocation.airplane
    -- --airplane blocks upload and Git network commands and routes all
    -- automatic AI work exclusively to the configured local endpoint. If no
    -- local endpoint exists, deterministic offline fallbacks or an explicit
    -- notice are used; the online endpoint is never contacted.
    ensures: OfflineAiGated()
}

rule RebuildFull {
    when: CliCommandExecuted(command: rebuild)
    requires: not CliInvocation.incremental
    -- The same step sequence as the GUI "Rebuild Database"
    -- (BDS.Maintenance.full_rebuild_steps — posts, media, scripts,
    -- templates, post links, thumbnails, embedding index), run
    -- synchronously with progress on stdout.
    ensures: CacheDatabaseRebuilt()
}

rule RebuildIncremental {
    when: CliCommandExecuted(command: rebuild)
    requires: CliInvocation.incremental
    -- Metadata diff, then auto-apply file→db for every difference and
    -- import every orphan file.
    ensures: CacheDatabaseRebuilt()
}

rule Repair {
    when: CliCommandExecuted(command: repair)
    -- Subcommand argument selects the repair part: post-links,
    -- media-links, thumbnails, embeddings, or search — the standard
    -- rebuild tasks outside the full rebuild.
    ensures: RepairTaskCompleted()
}

rule Render {
    when: CliCommandExecuted(command: render)
    -- Default: render all site sections plus the search index.
    -- --incremental: validate the generated output and apply only the
    -- differences (targeted render + extra-file deletion + calendar).
    -- --force: full re-render ignoring (but updating) content hashes.
    requires: not (CliInvocation.incremental and CliInvocation.force)
    ensures: SiteRendered()
}

rule Upload {
    when: CliCommandExecuted(command: upload)
    -- Uses the project publishing preferences (ssh host/user/path/mode)
    -- exactly like the app's upload, and waits for the publish job.
    ensures: SiteUploaded()
}

rule GitSync {
    when: CliCommandExecuted(command: push | pull)
    -- push: git push of the project repository to origin.
    -- pull: git pull --ff-only, then the incremental cache update
    -- (metadata diff auto-apply + orphan import) so the database
    -- reflects the pulled files.
    ensures: RepositorySynchronized()
}

rule CreatePost {
    when: CliCommandExecuted(command: post)
    -- Post data from parameters or JSON on stdin (LLM-friendly).
    -- Language is auto-detected when missing: the configured AI
    -- endpoint (airplane mode routes to the local model), falling back
    -- to the offline heuristic with a printed notice — the CLI
    -- equivalent of the airplane-mode toast. After creation the same
    -- auto-translation as the GUI is scheduled and awaited; when
    -- nothing can be scheduled the user is told.
    ensures: PostCreated()
}

rule CreateMedia {
    when: CliCommandExecuted(command: media)
    -- Imports the image, then best-effort AI enrichment: generated
    -- title, alt text, caption, and translations to all configured
    -- blog languages (the gallery pipeline without post linking).
    ensures: MediaImported()
}

rule CreateGallery {
    when: CliCommandExecuted(command: gallery)
    -- Creates the post, then runs the shared gallery import pipeline
    -- for every referenced image: import, mandatory post link, AI
    -- enrichment, translations; finally the post auto-translation.
    ensures: PostCreated()
    ensures: MediaImported()
}

rule Preferences {
    when: CliCommandExecuted(command: config)
    -- get/list expose effective global preferences shared with the app:
    -- canonical defaults overlaid by persisted values. Project-file settings and
    -- internal maintenance state are outside this list. Secret-backed keys remain
    -- visible, but text and JSON output reveal only whether each secret is set;
    -- get, list, and set confirmation never print secret values.
    ensures: PreferencesAccessed()
}

rule Projects {
    when: CliCommandExecuted(command: project)
    -- list, add <path> (register a folder in the cache database), and
    -- switch <id|slug|name> (change the active project).
    ensures: ProjectRegistryUpdated()
}

rule Tui {
    when: CliCommandExecuted(command: tui)
    -- The native CLI enters TUI boot mode so the interactive terminal UI owns
    -- the terminal.
    ensures: TuiStarted()
}

rule Server {
    when: CliCommandExecuted(command: server)
    -- Starts the dedicated headless SSH host with optional bind address, port,
    -- database path, and data-directory overrides. The standalone bds-server
    -- executable is not distributed.
    ensures: SshDaemonStarted()
}

rule RunLuaTask {
    when: CliCommandExecuted(command: lua)
    -- Runs a utility ("task", long-running) script from the database by
    -- slug in the active project, with the managed-job execution budget
    -- (unlimited time/reductions) but synchronously. Macro and
    -- transform scripts are rejected.
    ensures: ScriptExecuted()
}

rule InstallLauncher {
    when: CliInstallRequested() or CliCommandExecuted(command: install)
    -- Settings/Data, the future TUI settings action, and `bds-cli install`
    -- share one guarded installer. A forwarding launcher written to
    -- ~/.local/bin/bds-cli executes the packaged native CLI in place, so it
    -- uses the same packaged runtime as the desktop app. Existing unrelated
    -- files are never overwritten; outside a packaged release the action
    -- reports that the packaged CLI executable is required.
    ensures: LauncherInstalled()
}

invariant NativeArgv {
    -- The forwarding launcher preserves operating-system argv for the native
    -- bds-cli executable without the bDS2 release-eval environment shim.
}
