Archipelago — open-source initial import

This commit is contained in:
Archipelago
2026-08-12 10:55:50 +00:00
commit b67e1527a2
2068 changed files with 472303 additions and 0 deletions
+176
View File
@@ -0,0 +1,176 @@
/**
* AIUI ↔ Archy postMessage Protocol
*
* AIUI (iframe) communicates with Archy (host) via structured messages.
* Archy acts as a context broker — AIUI never directly accesses node data.
*/
import type { ArchyContentBundle } from '@/composables/archyContentAdapter'
/** Data categories that AIUI can request access to */
export type AIContextCategory =
| 'apps'
| 'system'
| 'network'
| 'wallet'
| 'files'
| 'media'
| 'search'
| 'ai-local'
| 'notes'
| 'bitcoin'
/** Actions AIUI can request Archy to perform */
export type AIActionType = 'install-app' | 'open-app' | 'navigate' | 'launch-app' | 'search-web' | 'read-file' | 'tail-logs'
// ─── AIUI → Archy (Requests) ───────────────────────────────────────────────
export interface AIUIContextRequest {
type: 'context:request'
id: string
category: AIContextCategory
query?: string
}
export interface AIUIActionRequest {
type: 'action:request'
id: string
action: AIActionType
params: Record<string, string>
}
export interface AIUIReadyMessage {
type: 'ready'
}
export interface AIUIThemeRequest {
type: 'theme:request'
}
/**
* A chat turn from AIUI's embedded-mode client. Carries only the raw user
* text — tool selection is node-side (D-01/D-03) and must never be
* expressible as an AIUI-originated action, so this is deliberately NOT an
* `AIActionType` member.
*/
export interface AIUIChatRequest {
type: 'chat:request'
id: string
text: string
}
/**
* A content-grid request from AIUI's embedded-mode client. Carries only a
* `kind` discriminator and an optional `scope` — the iframe never names an
* RPC method or params (T-13-34); the broker decides the call. A single
* generic channel (not one per content type) so 13-11's music-library wave
* can extend `kind` without touching this file again.
*
* `'library'` (13-11) is the one addition this wave makes to the
* discriminator: it routes to `music.list-tracks` (13-07) instead of
* `content.*`, since a library track carries real tag-extracted metadata
* (artist/album/duration) that `ContentItem` has no field for at all.
*/
export interface AIUIContentRequest {
type: 'content:request'
id: string
kind: 'films' | 'songs' | 'podcasts' | 'all' | 'library'
scope?: 'own' | 'peers' | 'owned'
}
export type AIUIRequest =
| AIUIContextRequest
| AIUIActionRequest
| AIUIReadyMessage
| AIUIThemeRequest
| AIUIChatRequest
| AIUIContentRequest
// ─── Archy → AIUI (Responses) ──────────────────────────────────────────────
export interface ArchyContextResponse {
type: 'context:response'
id: string
data: unknown
permitted: boolean
}
export interface ArchyActionResponse {
type: 'action:response'
id: string
success: boolean
error?: string
data?: unknown
}
export interface ArchyThemeResponse {
type: 'theme:response'
theme: {
accent: string
mode: 'dark'
}
}
export interface ArchyPermissionsUpdate {
type: 'permissions:update'
categories: AIContextCategory[]
}
/** The node's answer to a `chat:request`. On RPC failure, `error` carries
* only the error message — never the raw exception object. */
export interface ArchyChatResponse {
type: 'chat:response'
id: string
success: boolean
text?: string
error?: string
/** Content-producing tool results from this turn, already adapted into
* the same grid records `content:push` delivers, so AIUI can RENDER
* what the answer describes instead of leaving its surface empty
* beside a correct paragraph. Absent when the turn ran no such tool. */
surfaces?: ArchyChatSurface[]
}
/** One content tool result from a chat turn. `scope` is the tool's own
* argument (`own` | `peers` | `purchased` | `films`) — it is what lets the
* surface title itself with what was actually asked for rather than
* inferring it from the payload's shape. */
export interface ArchyChatSurface {
tool: string
scope?: string
bundle: ArchyContentBundle
}
/**
* The node's answer to a `content:request` — adapted grid records
* (`archyContentAdapter.ts`'s `adaptContentItems` output) for whichever
* buckets the RPC scope produced. `permitted: false` means neither the
* `media` nor the `files` category is granted (content:request is treated
* as permitted if either is enabled — see `handleContentRequest`); the
* three arrays are empty in that case, never omitted, so AIUI can render an
* empty-state instead of hanging on an unresolved request.
*/
export type ArchyContentPush = {
type: 'content:push'
id: string
kind: string
permitted: boolean
} & Partial<ArchyContentBundle>
export type ArchyResponse =
| ArchyContextResponse
| ArchyActionResponse
| ArchyThemeResponse
| ArchyPermissionsUpdate
| ArchyChatResponse
| ArchyContentPush
// ─── All messages ───────────────────────────────────────────────────────────
export type AIUIMessage = AIUIRequest | ArchyResponse
/** Protocol version for compatibility checks */
export const AIUI_PROTOCOL_VERSION = '1.0.0'
/** Message origin prefix used for validation */
export const AIUI_MESSAGE_PREFIX = 'aiui:'
+304
View File
@@ -0,0 +1,304 @@
// API Types ported from Angular codebase
export interface DataModel {
'server-info': ServerInfo
'package-data': { [id: string]: PackageDataEntry }
'peer-health'?: { [onion: string]: boolean }
notifications?: AppNotification[]
ui: UIData
}
export interface AppNotification {
id: string
level: 'info' | 'warning' | 'error'
title: string
message: string
timestamp: string
app_id?: string
}
export interface ServerInfo {
id: string
version: string
name: string | null
pubkey: string
'status-info': StatusInfo
'lan-address': string | null
'tor-address': string | null
/** Live probe of the Tor daemon. NOT the same as `tor-address`, which is read
* from the hidden-service hostname file and outlives a dead daemon.
* Optional because daemons older than 2026-08-09 do not send it — consumers
* must treat absent as "unknown", and only `=== true` as connected. */
'tor-running'?: boolean
'node-address'?: string
unread: number
'wifi-ssids': string[]
'zram-enabled': boolean
'seed-backed': boolean
lat?: number | null
lon?: number | null
'share-location'?: boolean
}
export interface StatusInfo {
restarting: boolean
'shutting-down': boolean
'updated': boolean
'backup-progress': number | null
'update-progress': number | null
'containers-scanned'?: boolean
}
export type UIMode = 'gamer' | 'easy' | 'chat'
export interface UIData {
name: string | null
'ack-welcome': string
marketplace: UIMarketplaceData
theme: string
mode?: UIMode
}
export interface UIMarketplaceData {
'selected-hosts': string[]
'known-hosts': Record<string, MarketplaceHost>
}
export interface MarketplaceHost {
name: string
url: string
}
export const PackageState = {
Installing: 'installing',
Installed: 'installed',
Stopping: 'stopping',
Stopped: 'stopped',
Exited: 'exited',
Starting: 'starting',
Running: 'running',
Restarting: 'restarting',
Creating: 'creating-backup',
Restoring: 'restoring-backup',
Removing: 'removing',
BackingUp: 'backing-up',
Updating: 'updating',
} as const
export type PackageState = typeof PackageState[keyof typeof PackageState]
export interface PackageDataEntry {
state: PackageState
health?: string | null // "healthy", "unhealthy", "starting", or null
'exit-code'?: number | null // container exit code: 0 = clean stop, non-zero = crash
'static-files'?: {
license: string
instructions: string
icon: string
screenshots?: AppScreenshot[]
}
manifest: Manifest
installed?: InstalledPackageDataEntry
'install-progress'?: InstallProgress
/** Live label for the current uninstall step ("Stopping containers (2/5)", …). */
'uninstall-stage'?: string | null
'available-update'?: string | null
}
export interface Manifest {
id: string
title: string
version: string
description: {
short: string
long: string
}
'release-notes': string
license: string
'wrapper-repo': string
'upstream-repo': string
'support-site': string
'marketing-site': string
'donation-url': string | null
author?: string
website?: string
tier?: string
interfaces?: {
main?: {
ui?: string
'tor-config'?: string
'lan-config'?: string
}
}
screenshots?: AppScreenshot[]
}
export type AppScreenshot = string | {
src: string
alt?: string
}
export interface InstalledPackageDataEntry {
'current-dependents': Record<string, CurrentDependencyInfo>
'current-dependencies': Record<string, CurrentDependencyInfo>
'last-backup': string | null
'interface-addresses': Record<string, InterfaceAddress>
status: ServiceStatus
}
export interface CurrentDependencyInfo {
'health-checks': string[]
}
export interface InterfaceAddress {
'tor-address': string
'lan-address': string | null
}
export interface AppCredential {
label: string
value: string
sensitive?: boolean
}
export interface AppCredentialsResponse {
title?: string
description?: string
credentials: AppCredential[]
}
export const ServiceStatus = {
Stopped: 'stopped',
Starting: 'starting',
Running: 'running',
Stopping: 'stopping',
Restarting: 'restarting',
} as const
export type ServiceStatus = typeof ServiceStatus[keyof typeof ServiceStatus]
export type InstallPhase =
| 'preparing'
| 'pulling-image'
| 'creating-container'
| 'starting-container'
| 'waiting-healthy'
| 'post-install'
| 'done'
export interface InstallProgress {
size: number
downloaded: number
/** High-level pipeline phase. Preferred by the UI over the byte
* counters — podman pull doesn't emit parseable progress when
* stderr is piped, so byte counters are usually (0,0). */
phase?: InstallPhase
/** Optional explicit message — surfaced on install failures so the
* UI can show what went wrong instead of silently removing the card. */
message?: string
}
// RPC Request/Response types
export namespace RR {
// Auth
export interface LoginReq {
password: string
metadata: SessionMetadata
}
export type LoginRes = null
export interface SessionMetadata {
// Add session metadata fields
}
export interface LogoutReq {}
export type LogoutRes = null
export interface ResetPasswordReq {
'old-password': string
'new-password': string
}
export type ResetPasswordRes = null
// Server
export interface EchoReq {
message: string
timeout?: number
}
export type EchoRes = string
export interface GetSystemTimeReq {}
export interface GetSystemTimeRes {
now: string
uptime: number
}
export interface GetServerMetricsReq {}
export interface GetServerMetricsRes {
cpu: number
disk: DiskInfo
memory: MemoryInfo
}
export interface DiskInfo {
used: number
total: number
}
export interface MemoryInfo {
used: number
total: number
}
export interface UpdateServerReq {
'marketplace-url': string
}
export type UpdateServerRes = 'updating' | 'no-updates'
export interface RestartServerReq {}
export type RestartServerRes = null
export interface ShutdownServerReq {}
export type ShutdownServerRes = null
// Packages
export interface InstallPackageReq {
id: string
'marketplace-url': string
version: string
}
export type InstallPackageRes = string // guid
export interface UninstallPackageReq {
id: string
}
export type UninstallPackageRes = null
export interface StartPackageReq {
id: string
}
export type StartPackageRes = null
export interface StopPackageReq {
id: string
}
export type StopPackageRes = null
export interface RestartPackageReq {
id: string
}
export type RestartPackageRes = null
}
// JSON Patch types
export interface PatchOperation {
op: 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test'
path: string
value?: unknown
from?: string
}
export interface Update {
sequence: number
patch: PatchOperation[]
}
+40
View File
@@ -0,0 +1,40 @@
// Goal-based workflow types for Easy mode
export interface GoalDefinition {
id: string
title: string
subtitle: string
icon: string
category: 'commerce' | 'payments' | 'storage' | 'identity' | 'network' | 'backup' | 'community'
requiredApps: string[]
steps: GoalStep[]
estimatedTime: string
difficulty: 'beginner' | 'intermediate'
}
export interface GoalStep {
id: string
title: string
description: string
appId?: string
/**
* 'fund' renders the bitcoin-wallet funding UI: gated on IBD completion
* (with a live sync timer), then a "Fund Wallet" receive flow.
*/
action: 'install' | 'configure' | 'verify' | 'info' | 'fund'
isAutomatic: boolean
/** Custom step icon (e.g. the Zeus logo) — overrides the appId-derived icon */
icon?: string
/** Custom label for the step's CTA button (configure steps) */
ctaLabel?: string
}
export type GoalStatus = 'not-started' | 'in-progress' | 'completed' | 'error'
export interface GoalProgress {
goalId: string
status: GoalStatus
currentStepIndex: number
completedSteps: string[]
startedAt?: number
}
+9
View File
@@ -0,0 +1,9 @@
declare module 'qrloop' {
export type FramesState = unknown
export function parseFramesReducer(state: FramesState | null, frame: string): FramesState
export function areFramesComplete(state: FramesState): boolean
export function framesToData(state: FramesState): { toString(encoding: string): string }
export function totalNumberOfFrames(state: FramesState): number
export function currentNumberOfFrames(state: FramesState): number
export function progressOfFrames(state: FramesState): number
}