/** * `synap market` — discover + install ANY package type. * ===================================================== * * `synap launch` is workspace-first: it turns an empty pod into a company from * BUNDLED workspace templates (offline). `market` is the superset browser — it * reaches EVERY package type the control-plane registry carries * (capability / skill / workflow / view / cell / workspace), public or private, * over the shared catalog door (`assembleCatalog` + the CP `/api/packages` * transport). Suites (`enterprise-os`, `the-arch`) headline via the `suite` tag, * not a hardcoded slug. * * synap market [--search q] [--type t] [--list] [--json] — browse * synap market install — install * synap market update [slug] [--yes] [--json] — check/apply drift * * INSTALL SCOPE: WORKSPACE-type packages install via `/packages/apply` (the * same door `launch` uses), which provisions a workspace from the definition. * Non-workspace kinds (capability / automation / cell / template) install via * the pod's kind-agnostic `market.install` verb on `/capabilities/execute`, * which resolves the definition from `cp_catalog_cache`. Types the verb doesn't * handle fall back to a route-to-browser message. */ import { type CatalogEntry, type TemplateUpdateCheck } from "@synap-core/workspace-templates"; import { type PackageFilters, type TierInfo } from "../lib/cp-packages.js"; import { type InstalledTemplateInfo } from "../lib/installed.js"; export interface MarketCatalog { entries: CatalogEntry[]; tierBySlug: Map; lockedSlugs: Set; installed: Set; /** Bundled slugs (public + private) — the offline-installable workspace set. */ bundledSlugs: Set; loggedIn: boolean; reachedCp: boolean; /** * Slug → CP row's OWN `version`, read straight off `CpBrowseRow`/`CpMineRow` * (NOT off the merged `entries[]`). Needed because `mergeCatalog`'s * squatting-defense makes the BUNDLE win the merged entry for every OFFICIAL * public template that also exists on the CP (list-route rows never carry * `definition`, so `computeTemplateUpdates` sees no remote version for them * via `entries[]` and would silently hide a real CP-side update). `market * update` compares against THIS map directly instead. */ remoteVersionBySlug: Map; } /** * Assemble the market catalog: bundled workspace templates ∪ public CP browse * (all types) ∪ the caller's own packages (when logged in), through the shared * `assembleCatalog` door. Unlike `launch`, PUBLIC CP rows are fetched even when * logged out — the bundle only carries workspaces, so all-types discovery needs * the CP browse route (which needs no auth). */ export declare function buildMarketCatalog(filters?: PackageFilters): Promise; /** The `kind` values the pod's `market.install` verb provisions (see * cp-catalog-sync.ts `CatalogKind`). */ export type MarketInstallKind = "capability" | "automation" | "template" | "cell"; /** * Map a CP package `category` (what {@link typeOf} returns) → the pod * `market.install` verb's `kind` vocabulary, or `null` when the verb can't * install that type. The CP's live PACKAGE_TYPES and the pod's cache `kind` * diverge (see cp-catalog-sync.ts): the CP's `workflow` is the pod's * `automation`; the CP's `workspace` is the pod's `template` — but a workspace * installs through the dedicated `/packages/apply` create path, so it never * routes through here. `automation`/`template` are also accepted verbatim so a * row already speaking the pod's cache vocabulary still resolves. */ export declare function marketInstallKind(type: string): MarketInstallKind | null; interface DependencySeedOutcome { slug: string; status: "no-layers" | "seeded" | "failed"; layers?: string[]; error?: string; } export interface ResolvedDependencyLike { slug: string; seedOutcome?: DependencySeedOutcome; } /** * Top-level rollup the backend now sends alongside `dependencies[]` — same * info, pre-aggregated. Kept local for the same reason as the types above: * a structural match is all `/packages/apply`'s JSON needs, and this CLI * doesn't carry api-types. Optional — older pod builds won't send it. */ export interface SeedSummary { attempted: number; seeded: number; failed: Array<{ slug: string; error: string; }>; } /** The two new honest-outcome fields `/packages/apply` may return. Both optional — a legacy pod build may omit them. */ export interface WorkspaceApplyResult { status?: string; workspaceId?: string; onto?: string; outcome?: "created" | "reconciled" | "unchanged"; } export interface ApplyVerdict { /** Honest coarse status — JSON consumers should branch on this, never guess from `workspace.outcome` alone. */ status: "installed" | "installed-with-failures" | "updated" | "updated-with-failures" | "unchanged" | "composed" | "composed-with-failures" | "stamp-contradiction" | "legacy-unknown" | "no-workspace"; /** Non-fatal warnings — a stamp contradiction or a compose-overlay note. Seed failures are reported separately via `printSeedOutcomes`. */ warnings: string[]; } /** * Classify one `/packages/apply` response into the honest verdict both * `marketInstall` and `applyOnePackage` print (and both put in `--json`). * `wasUnstamped` = the slug was already installed with no recorded version * BEFORE this call (only `marketInstall` can detect this today — see its * `fetchInstalledTemplates` call; `applyOnePackage` only ever re-applies * already-versioned slugs, so it always passes `false`). */ export declare function classifyApplyResult(entry: CatalogEntry, ws: WorkspaceApplyResult | undefined, dependencies: ResolvedDependencyLike[] | undefined, seedSummary: SeedSummary | undefined, ctx: { remoteVersionSent?: string; wasUnstamped: boolean; }): ApplyVerdict; /** Render a `classifyApplyResult` verdict — the ONE honest-text door for both `marketInstall` and `applyOnePackage`. */ export declare function printApplyVerdict(entry: CatalogEntry, verdict: ApplyVerdict): void; export declare function market(opts: { list?: boolean; json?: boolean; search?: string; type?: string; }): Promise; export declare function marketInstall(slug: string, opts: { podUrl?: string; apiKey?: string; json?: boolean; project?: string; timeout?: string; /** Reconcile this template ONTO an existing workspace (additive) instead of creating a new one — passed through to `/packages/apply` as `targetWorkspaceId`. */ onto?: string; /** Preview the create path write-free (`/packages/preflight`) — reports would-create / reuse / conflicts and writes nothing. */ dryRun?: boolean; }): Promise; export interface UpdateCheck extends TemplateUpdateCheck { /** * True when the installed workspace carries no version stamp at all — the * pod can't say whether it's stale. Distinct from `updateAvailable: false` * (checked, and it IS current). Every CLI/Hub-door install is `true` today — * see `InstalledTemplateInfo.version`'s doc in `lib/installed.ts`. */ noVersionInfo: boolean; } /** * Per-installed-slug drift check. Starts from the shared `computeTemplateUpdates` * (handles bundled-only / private-only slugs via `bundleVersion`), then * overrides `latestVersion`/`updateAvailable` with the CP row's OWN version for * any slug in `remoteVersionBySlug` — the merge-winner bypass this command * needs (see `MarketCatalog.remoteVersionBySlug`'s doc). A slug the pod never * version-stamped is reported `noVersionInfo: true`, never silently "outdated". */ export declare function computeUpdates(installedTemplates: InstalledTemplateInfo[], cat: MarketCatalog): UpdateCheck[]; export declare function marketUpdate(slugsArg: string[] | undefined, opts: { yes?: boolean; dryRun?: boolean; json?: boolean; podUrl?: string; apiKey?: string; timeout?: string; }): Promise; export interface TreeNode { slug: string; name: string; relation: "compose" | "require"; depKind?: string; installed: boolean; workspaceId?: string; workspaceName?: string; installedVersion?: string; latestVersion?: string | null; updateAvailable: boolean; noVersionInfo: boolean; /** Names of OTHER top-level packages whose tree also carries this slug. */ shared: string[]; children: TreeNode[]; } export interface TreePackage { slug: string; name: string; installedVersion: string; latestVersion: string | null; updateAvailable: boolean; noVersionInfo: boolean; workspaceCount: number; isSuite: boolean; children: TreeNode[]; } /** * The nested composition-tree view: one tree per installed TOP-LEVEL package * (a suite, or any installed package whose `edgesOf` resolves at least one * dependency — bundled OR remote). Everything else stays a flat leaf * (`leafRows`, rendered like a plain `market installed` row). This is the * SAME "top-level" rule the old walker used (kept verbatim for `--tree`'s * JSON/text backward compatibility — see `marketInstalled`'s before/after * diff check), just evaluated against the fixed `edgesOf` instead of the * bundled-only map. * * Nested `TreeNode`s are built by walking `graph.edges` (both `require` and * `compose` relations — the engine excludes compose SOURCES from `nodes`, * rule 2, but never from `edges`, so the nested view still shows them exactly * where the old walker did) and `shared` reads straight off each node's * `roots` (the engine's true reachability set), replacing the old walker's * bespoke reverse index. */ export declare function buildCompositionTrees(rows: UpdateCheck[], cat: MarketCatalog, installedTemplates: InstalledTemplateInfo[], workspaceCountBySlug: Map): Promise<{ packages: TreePackage[]; leafRows: UpdateCheck[]; }>; export declare function printCompositionTree(pkg: TreePackage): void; /** * Pure-read inventory of what's installed on this pod — reuses * `buildMarketCatalog` + `fetchInstalledTemplates` + `computeUpdates` * (`market update`'s own drift check), never writes anything. Pairs with * `market update` to apply what this surfaces. */ export declare function marketInstalled(opts: { json?: boolean; outdated?: boolean; tree?: boolean; layers?: boolean; }): Promise; export {};