@alashi/cli 0.12.0 → 0.13.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.
@@ -1 +0,0 @@
1
- {"version":3,"file":"store-BNZqhcwm.mjs","names":["execFileAsync","execFileAsync"],"sources":["../src/auth.ts","../src/usage.ts","../src/budget.ts","../src/compat.ts","../src/loader.ts","../src/sources.ts","../src/registry.ts","../src/installer.ts","../src/portability.ts","../src/runtime.ts","../src/scheduler.ts","../src/settings-page.ts","../src/server.ts","../src/store.ts"],"sourcesContent":["import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';\nimport type { Context, Hono, MiddlewareHandler } from 'hono';\nimport { deleteCookie, getCookie, setCookie } from 'hono/cookie';\nimport type { AuthMode, AuthSessionRecord, AuthTokenRecord, Store } from './store.js';\n\n/** Session cookie lifetime. Fixed, not sliding: re-login once a month. */\nexport const SESSION_TTL_MS: number = 30 * 24 * 60 * 60 * 1000;\n\nexport const SESSION_COOKIE = 'alashi_session';\n\n/** Recognizable in `Authorization: Bearer ...` and in shell history. */\nconst TOKEN_PREFIX = 'alashi_tok_';\n\n/** Who a request is, once a credential (or the console heuristic) vouched for it. */\nexport interface Identity {\n level: 'owner' | 'user';\n via: 'console' | 'boot' | 'token' | 'session' | 'tailscale' | 'off';\n /** Display name for attribution: token label, tailscale login, or \"owner\". */\n name: string;\n}\n\n/** What a route demands. `user` is any signed-in identity; `owner` outranks it. */\nexport type AccessLevel = 'public' | 'user' | 'owner';\n\ndeclare module 'hono' {\n interface ContextVariableMap {\n identity?: Identity;\n }\n}\n\nfunction sha256(value: string): string {\n return createHash('sha256').update(value).digest('hex');\n}\n\n/** Constant-time compare of two hex digests of equal length. */\nfunction hashEquals(a: string, b: string): boolean {\n const bufA = Buffer.from(a, 'hex');\n const bufB = Buffer.from(b, 'hex');\n return bufA.length === bufB.length && timingSafeEqual(bufA, bufB);\n}\n\nconst LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1']);\n\nfunction isLoopbackAddress(addr: string): boolean {\n const bare = addr.startsWith('::ffff:') ? addr.slice('::ffff:'.length) : addr;\n return bare === '::1' || bare.startsWith('127.');\n}\n\nconst FORWARDING_HEADERS = [\n 'x-forwarded-for',\n 'x-forwarded-host',\n 'x-forwarded-proto',\n 'x-real-ip',\n 'forwarded',\n];\n\n/** True when the request carries any reverse-proxy forwarding header. */\nexport function hasForwardingHeaders(headers: { get(name: string): string | null }): boolean {\n return FORWARDING_HEADERS.some((name) => headers.get(name) !== null);\n}\n\n/**\n * The console heuristic: is this request verifiably from the local machine?\n *\n * Requires all three of: a loopback peer address, a local request hostname,\n * and no forwarding headers — because tunnels (ngrok, cloudflared, `tailscale\n * serve`) connect from loopback too, and what gives them away is the forwarded\n * hostname or the forwarding headers they add. A proxy configured to strip\n * both is indistinguishable from a local request; `mode: \"required\"` exists\n * for anyone deliberately exposing the host.\n *\n * `hostname` is the request URL's hostname (which the server derives from the\n * Host header) rather than the raw header, because in-process requests carry\n * no Host header at all. `remoteAddress === undefined` means an in-process\n * request (tests, the host calling itself) — there is no socket, so it is by\n * definition local.\n */\nexport function isConsoleRequest(\n hostname: string,\n headers: { get(name: string): string | null },\n remoteAddress: string | undefined,\n): boolean {\n if (remoteAddress !== undefined && !isLoopbackAddress(remoteAddress)) return false;\n if (hasForwardingHeaders(headers)) return false;\n // URL.hostname keeps IPv6 brackets (\"[::1]\"); strip them before matching.\n return LOOPBACK_HOSTS.has(hostname.toLowerCase().replace(/^\\[|\\]$/g, ''));\n}\n\n/**\n * The access level a path demands. Deny-by-default shape: everything is at\n * least `user` unless it is explicitly public, and the routes that change\n * host state or spend-relevant settings (config, budgets, manual job runs)\n * are `owner`.\n */\nexport function requiredLevel(path: string, publicApps: readonly string[]): AccessLevel {\n if (path === '/login' || path === '/logout' || path === '/api/me' || path === '/favicon.ico') {\n return 'public';\n }\n if (path === '/assets' || path.startsWith('/assets/')) return 'public';\n if (path.startsWith('/app/')) {\n const name = path.slice('/app/'.length).split('/', 1)[0] ?? '';\n return publicApps.includes(name) ? 'public' : 'user';\n }\n if (path.startsWith('/settings/')) return 'owner';\n if (/^\\/api\\/apps\\/[^/]+\\/(config|budget)$/.test(path)) return 'owner';\n if (/^\\/api\\/apps\\/[^/]+\\/jobs\\/[^/]+\\/run$/.test(path)) return 'owner';\n return 'user';\n}\n\nfunction meetsLevel(identity: Identity, required: AccessLevel): boolean {\n return required !== 'owner' || identity.level === 'owner';\n}\n\n/** Public shape of a token for `alashi token list` — never includes the hash. */\nexport interface TokenInfo {\n id: string;\n label?: string;\n createdAt: string;\n lastUsedAt?: string;\n}\n\n/**\n * Host authentication: bearer tokens and browser sessions persisted in\n * `auth.json` (hashes only), plus an in-memory one-time boot login token.\n * Writes are serialized so concurrent logins cannot lose each other's update.\n */\nexport class AuthService {\n private queue: Promise<unknown> = Promise.resolve();\n private bootHash: string | undefined;\n\n constructor(private readonly store: Store) {}\n\n /**\n * Mints the one-time login secret printed at serve startup. In-memory only\n * and consumed by the first successful login; restart the host (or mint a\n * real token) to get another.\n */\n issueBootToken(): string {\n const secret = `${TOKEN_PREFIX}${randomBytes(24).toString('hex')}`;\n this.bootHash = sha256(secret);\n return secret;\n }\n\n /** Creates a persistent owner bearer token; the secret is only returned here. */\n async mintToken(label?: string): Promise<{ id: string; secret: string }> {\n const secret = `${TOKEN_PREFIX}${randomBytes(24).toString('hex')}`;\n const record: AuthTokenRecord = {\n id: randomBytes(6).toString('hex'),\n ...(label !== undefined && label !== '' && { label }),\n hash: sha256(secret),\n createdAt: new Date().toISOString(),\n };\n await this.mutate((state) => {\n state.tokens.push(record);\n });\n return { id: record.id, secret };\n }\n\n async listTokens(): Promise<TokenInfo[]> {\n const { tokens } = await this.store.readAuthState();\n return tokens.map(({ hash: _hash, ...info }) => info);\n }\n\n /** Revokes a token and every session it created is left alone: sessions expire on their own. */\n async revokeToken(id: string): Promise<boolean> {\n let found = false;\n await this.mutate((state) => {\n const before = state.tokens.length;\n state.tokens = state.tokens.filter((token) => token.id !== id);\n found = state.tokens.length !== before;\n });\n return found;\n }\n\n /**\n * Exchanges a login secret (boot token or minted token) for an identity.\n * A boot token is consumed by success; minted tokens are reusable.\n */\n async redeemLoginSecret(secret: string): Promise<Identity | undefined> {\n const hash = sha256(secret);\n if (this.bootHash !== undefined && hashEquals(hash, this.bootHash)) {\n this.bootHash = undefined;\n return { level: 'owner', via: 'boot', name: 'owner' };\n }\n return this.tokenIdentity(hash);\n }\n\n /** Resolves `Authorization: Bearer <secret>`; undefined when absent or invalid. */\n async bearerIdentity(authorization: string | undefined): Promise<Identity | undefined> {\n if (authorization === undefined || !authorization.toLowerCase().startsWith('bearer ')) {\n return undefined;\n }\n return this.tokenIdentity(sha256(authorization.slice('bearer '.length).trim()));\n }\n\n private async tokenIdentity(hash: string): Promise<Identity | undefined> {\n const { tokens } = await this.store.readAuthState();\n const token = tokens.find((entry) => hashEquals(entry.hash, hash));\n if (!token) return undefined;\n // Best-effort bookkeeping; a failed write must not fail the request.\n void this.mutate((state) => {\n const entry = state.tokens.find((candidate) => candidate.id === token.id);\n if (entry) entry.lastUsedAt = new Date().toISOString();\n }).catch(() => undefined);\n return { level: 'owner', via: 'token', name: token.label ?? `token ${token.id}` };\n }\n\n /** Creates a session for an already-verified identity; returns the cookie value. */\n async createSession(identity: Identity, at: Date = new Date()): Promise<string> {\n const value = randomBytes(24).toString('hex');\n const record: AuthSessionRecord = {\n hash: sha256(value),\n name: identity.name,\n level: identity.level,\n createdAt: at.toISOString(),\n expiresAt: new Date(at.getTime() + SESSION_TTL_MS).toISOString(),\n };\n await this.mutate((state) => {\n state.sessions.push(record);\n });\n return value;\n }\n\n /** Resolves a session cookie value; undefined when unknown or expired. */\n async sessionIdentity(\n value: string | undefined,\n at: Date = new Date(),\n ): Promise<Identity | undefined> {\n if (value === undefined || value === '') return undefined;\n const hash = sha256(value);\n const { sessions } = await this.store.readAuthState();\n const session = sessions.find((entry) => hashEquals(entry.hash, hash));\n if (!session || session.expiresAt <= at.toISOString()) return undefined;\n return { level: session.level, via: 'session', name: session.name };\n }\n\n /** Deletes the session behind a cookie value (logout). */\n async destroySession(value: string): Promise<void> {\n const hash = sha256(value);\n await this.mutate((state) => {\n state.sessions = state.sessions.filter((entry) => !hashEquals(entry.hash, hash));\n });\n }\n\n /** Read-modify-write of auth.json, serialized, with expired sessions pruned. */\n private mutate(\n change: (state: { tokens: AuthTokenRecord[]; sessions: AuthSessionRecord[] }) => void,\n ): Promise<void> {\n const next = this.queue.then(async () => {\n const state = await this.store.readAuthState();\n const now = new Date().toISOString();\n state.sessions = state.sessions.filter((session) => session.expiresAt > now);\n change(state);\n await this.store.writeAuthState(state);\n });\n this.queue = next.catch(() => undefined);\n return next;\n }\n}\n\n/** How the middleware learns the peer address; injectable for tests. */\nexport type RemoteAddressResolver = (c: Context) => string | undefined;\n\nexport interface AuthMiddlewareOptions {\n getRemoteAddress?: RemoteAddressResolver;\n}\n\nfunction wantsHtml(c: Context): boolean {\n return c.req.method === 'GET' && (c.req.header('accept') ?? '').includes('text/html');\n}\n\n/** Tailscale identity, when the config says the headers can be believed. */\nfunction tailscaleIdentity(c: Context, trusted: boolean): Identity | undefined {\n if (!trusted) return undefined;\n const login = c.req.header('tailscale-user-login');\n if (login === undefined || login === '') return undefined;\n return { level: 'user', via: 'tailscale', name: c.req.header('tailscale-user-name') ?? login };\n}\n\n/**\n * The host's front door. Resolves an identity for every request (bearer token,\n * session cookie, tailscale headers, console heuristic — in that order) and\n * enforces the path's required level. Auth config is read per request so\n * settings changes apply without a restart.\n */\nexport function createAuthMiddleware(\n store: Store,\n auth: AuthService,\n options: AuthMiddlewareOptions = {},\n): MiddlewareHandler {\n let warnedProxyWhileOff = false;\n\n return async (c, next) => {\n const config = (await store.readGlobalConfig()).auth ?? {};\n const mode: AuthMode = config.mode ?? 'auto';\n const required = requiredLevel(c.req.path, config.public ?? []);\n\n if (mode === 'off') {\n if (!warnedProxyWhileOff && hasForwardingHeaders(c.req.raw.headers)) {\n warnedProxyWhileOff = true;\n console.warn(\n 'alashi: a request arrived through a proxy while auth.mode is \"off\" — ' +\n 'this host appears to be exposed; set auth.mode to \"required\" in config.json',\n );\n }\n c.set('identity', { level: 'owner', via: 'off', name: 'owner' });\n return next();\n }\n\n const identity =\n (await auth.bearerIdentity(c.req.header('authorization'))) ??\n (await auth.sessionIdentity(getCookie(c, SESSION_COOKIE))) ??\n tailscaleIdentity(c, config.trustTailscaleHeaders === true) ??\n (mode === 'auto' &&\n isConsoleRequest(\n new URL(c.req.url).hostname,\n c.req.raw.headers,\n options.getRemoteAddress?.(c),\n )\n ? ({ level: 'owner', via: 'console', name: 'owner' } satisfies Identity)\n : undefined);\n\n if (identity) c.set('identity', identity);\n if (required === 'public') return next();\n if (identity && meetsLevel(identity, required)) return next();\n\n if (wantsHtml(c)) return c.redirect('/login');\n return c.json(\n { error: identity ? 'This action requires the owner' : 'Authentication required' },\n identity ? 403 : 401,\n );\n };\n}\n\nfunction loginPage(error?: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>alashi — sign in</title>\n<link rel=\"stylesheet\" href=\"/assets/v1/system.css\">\n</head>\n<body>\n<header class=\"topbar\"><strong>alashi</strong></header>\n<main class=\"narrow\">\n<h1>Sign in</h1>\n<div class=\"panel\">\n${error ? '<p class=\"badge err\">Invalid or expired token</p>' : ''}\n<p class=\"muted\">Paste a login token — the one printed by <code>alashi serve</code>, or one minted with <code>alashi token create</code>.</p>\n<form method=\"post\" action=\"/login\">\n<p><input type=\"password\" name=\"token\" autocomplete=\"off\" autofocus style=\"width:100%\"></p>\n<p><button type=\"submit\">Sign in</button></p>\n</form>\n</div>\n</main>\n</body>\n</html>\n`;\n}\n\nfunction grantSession(c: Context, value: string): void {\n setCookie(c, SESSION_COOKIE, value, {\n path: '/',\n httpOnly: true,\n sameSite: 'Lax',\n maxAge: Math.floor(SESSION_TTL_MS / 1000),\n });\n}\n\n/**\n * The auth surface itself (all public in the policy map):\n * GET /login login form; `?token=` redeems the printed one-time URL\n * POST /login { token } (form-encoded) -> session cookie + redirect /\n * GET /logout destroys the session, back to /login\n * GET /api/me the resolved identity, or { identity: null }\n */\nexport function registerAuthRoutes(app: Hono, auth: AuthService): void {\n app.get('/login', async (c) => {\n const token = c.req.query('token');\n if (token === undefined) {\n const existing = c.get('identity');\n if (existing && existing.via !== 'console') return c.redirect('/');\n return c.html(loginPage());\n }\n const identity = await auth.redeemLoginSecret(token);\n if (!identity) return c.html(loginPage('invalid'), 401);\n grantSession(c, await auth.createSession(identity));\n return c.redirect('/');\n });\n\n app.post('/login', async (c) => {\n const body = await c.req.parseBody();\n const token = typeof body.token === 'string' ? body.token.trim() : '';\n const identity = token === '' ? undefined : await auth.redeemLoginSecret(token);\n if (!identity) return c.html(loginPage('invalid'), 401);\n grantSession(c, await auth.createSession(identity));\n return c.redirect('/');\n });\n\n app.get('/logout', async (c) => {\n const value = getCookie(c, SESSION_COOKIE);\n if (value !== undefined) await auth.destroySession(value);\n deleteCookie(c, SESSION_COOKIE, { path: '/' });\n return c.redirect('/login');\n });\n\n app.get('/api/me', (c) => c.json({ identity: c.get('identity') ?? null }));\n}\n","import type { ModelTokenUsage } from '@alashi/apps-sdk';\nimport type { ModelUsageTotals, Store, UsageDay, UsageMonth, UsageState } from './store.js';\n\n/** Days of history kept in usage.json; older days are dropped on write. */\nexport const USAGE_RETENTION_DAYS = 90;\n\n/** Cents-of-a-millicent precision — enough for per-turn costs, short enough to keep JSON tidy. */\nconst COST_PRECISION = 6;\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\n/** The UTC calendar day (`YYYY-MM-DD`) a timestamp falls in. */\nexport function utcDay(at: Date = new Date()): string {\n return at.toISOString().slice(0, 10);\n}\n\n/** The UTC calendar month (`YYYY-MM`) a timestamp falls in. */\nexport function utcMonth(at: Date = new Date()): string {\n return at.toISOString().slice(0, 7);\n}\n\nfunction round(value: number): number {\n return Number(value.toFixed(COST_PRECISION));\n}\n\n/** Money for humans: sub-dollar spend needs the extra digits, larger sums don't. */\nexport function formatUsd(value: number): string {\n return `$${value.toFixed(value >= 1 ? 2 : 4)}`;\n}\n\n/** One turn's metering as reported by the engine; either part may be missing. */\nexport interface TurnUsage {\n costUsd?: number;\n /** Per-turn token/cost deltas keyed by model id (provider-neutral). */\n modelUsage?: Record<string, ModelTokenUsage>;\n}\n\n/** A summary of an app's recorded spend, as served by GET /api/apps/:name/usage. */\nexport interface UsageSummary {\n /** The UTC day \"today\" refers to. */\n date: string;\n /** Spend recorded so far today. */\n todayCostUsd: number;\n /** Turns recorded so far today. */\n todayTurns: number;\n /** Per-model tokens/cost recorded so far today. */\n todayModels: Record<string, ModelUsageTotals>;\n /** Spend across the whole retention window. */\n totalCostUsd: number;\n /** Per-model tokens/cost across the whole retention window. */\n models: Record<string, ModelUsageTotals>;\n /** Days kept in the retention window. */\n retentionDays: number;\n /**\n * Whether the host has ever metered a turn for this app (monthly rollups\n * persist, so this survives the daily retention window). False means \"no\n * data at all\" — which is not the same thing as a measured zero, and must\n * not be rendered as `$0.0000`. The host meters only sessions an app opens\n * through `ctx.ai`, so an app that tracks its own costs elsewhere can be\n * unrecorded here while still reporting real spend of its own.\n */\n recorded: boolean;\n /** Daily series, oldest first; days with no spend are absent. */\n days: UsageDay[];\n /** Monthly rollups, oldest first; never pruned, so trends outlive the daily window. */\n months: UsageMonth[];\n}\n\n/** Host-wide totals across every installed app, as served by GET /api/usage. */\nexport interface CombinedUsage {\n todayCostUsd: number;\n todayTurns: number;\n totalCostUsd: number;\n /** Per-model tokens/cost so far today, across all apps. */\n todayModels: Record<string, ModelUsageTotals>;\n /** Per-model tokens/cost across the retention window, across all apps. */\n models: Record<string, ModelUsageTotals>;\n /** Monthly rollups merged across apps, oldest first. */\n months: UsageMonth[];\n /** True when at least one app has ever been metered. */\n recorded: boolean;\n}\n\n/** Merges per-app summaries into the host-wide view. */\nexport function combineSummaries(summaries: UsageSummary[]): CombinedUsage {\n const combined: CombinedUsage = {\n todayCostUsd: 0,\n todayTurns: 0,\n totalCostUsd: 0,\n todayModels: {},\n models: {},\n months: [],\n recorded: false,\n };\n const byMonth = new Map<string, UsageMonth>();\n for (const summary of summaries) {\n combined.todayCostUsd = round(combined.todayCostUsd + summary.todayCostUsd);\n combined.todayTurns += summary.todayTurns;\n combined.totalCostUsd = round(combined.totalCostUsd + summary.totalCostUsd);\n combined.recorded ||= summary.recorded;\n mergeTotals(combined.todayModels, summary.todayModels);\n mergeTotals(combined.models, summary.models);\n for (const month of summary.months) {\n const target = byMonth.get(month.month) ?? { month: month.month, costUsd: 0, turns: 0 };\n target.costUsd = round(target.costUsd + month.costUsd);\n target.turns += month.turns;\n if (month.models !== undefined) {\n target.models ??= {};\n mergeTotals(target.models, month.models);\n }\n byMonth.set(month.month, target);\n }\n }\n combined.months = [...byMonth.values()].sort((a, b) => a.month.localeCompare(b.month));\n return combined;\n}\n\nfunction emptyTotals(): ModelUsageTotals {\n return {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadInputTokens: 0,\n cacheCreationInputTokens: 0,\n costUsd: 0,\n };\n}\n\nfunction count(value: number | undefined): number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0;\n}\n\nfunction addModelUsage(\n models: Record<string, ModelUsageTotals>,\n usage: Record<string, ModelTokenUsage>,\n): void {\n for (const [model, turn] of Object.entries(usage)) {\n const totals = (models[model] ??= emptyTotals());\n totals.inputTokens += count(turn.inputTokens);\n totals.outputTokens += count(turn.outputTokens);\n totals.cacheReadInputTokens += count(turn.cacheReadInputTokens);\n totals.cacheCreationInputTokens += count(turn.cacheCreationInputTokens);\n totals.costUsd = round(totals.costUsd + count(turn.costUsd));\n }\n}\n\nfunction mergeTotals(\n into: Record<string, ModelUsageTotals>,\n from: Record<string, ModelUsageTotals> | undefined,\n): void {\n for (const [model, totals] of Object.entries(from ?? {})) {\n const target = (into[model] ??= emptyTotals());\n target.inputTokens += count(totals.inputTokens);\n target.outputTokens += count(totals.outputTokens);\n target.cacheReadInputTokens += count(totals.cacheReadInputTokens);\n target.cacheCreationInputTokens += count(totals.cacheCreationInputTokens);\n target.costUsd = round(target.costUsd + count(totals.costUsd));\n }\n}\n\nfunction sanitizeModels(\n models: Record<string, ModelUsageTotals> | undefined,\n): Record<string, ModelUsageTotals> | undefined {\n if (models === undefined) return undefined;\n const clean: Record<string, ModelUsageTotals> = {};\n for (const [model, totals] of Object.entries(models)) {\n clean[model] = {\n inputTokens: count(totals?.inputTokens),\n outputTokens: count(totals?.outputTokens),\n cacheReadInputTokens: count(totals?.cacheReadInputTokens),\n cacheCreationInputTokens: count(totals?.cacheCreationInputTokens),\n costUsd: round(count(totals?.costUsd)),\n };\n }\n return Object.keys(clean).length > 0 ? clean : undefined;\n}\n\n/**\n * Per-app AI spend and token usage, persisted as OS bookkeeping in the app\n * container (`apps/<name>/usage.json`, next to jobs.json and chat.json):\n * a per-UTC-day series trimmed to the retention window, plus per-month\n * rollups that are kept forever.\n *\n * Cost and per-model tokens arrive from the `result` event, whose fields the\n * SDK contract marks optional: engines that report neither simply contribute\n * nothing. Writes are serialized per app so concurrent turns cannot lose an\n * update, and each app has its own queue so a slow write for one app never\n * delays another.\n */\nexport class UsageRecorder {\n /** Serializes usage.json read-modify-write per app. */\n private readonly queues = new Map<string, Promise<unknown>>();\n\n constructor(\n private readonly store: Store,\n private readonly retentionDays: number = USAGE_RETENTION_DAYS,\n ) {}\n\n /**\n * Adds one turn's cost and token usage to the app's totals for the UTC day\n * and month of `at`. A missing, non-finite, or negative `costUsd` is\n * ignored; a turn that reports neither valid cost nor any model usage is\n * not counted and nothing is written, so a provider that omits metering\n * cannot corrupt the totals.\n */\n record(appName: string, turn: TurnUsage, at: Date = new Date()): Promise<void> {\n const costValid =\n typeof turn.costUsd === 'number' && Number.isFinite(turn.costUsd) && turn.costUsd >= 0;\n const modelUsage =\n turn.modelUsage !== undefined && Object.keys(turn.modelUsage).length > 0\n ? turn.modelUsage\n : undefined;\n if (!costValid && modelUsage === undefined) return Promise.resolve();\n const costUsd = costValid ? (turn.costUsd as number) : 0;\n\n return this.enqueue(appName, async () => {\n const state = this.normalize(await this.store.readUsageState(appName));\n\n const date = utcDay(at);\n const day = state.days.find((entry) => entry.date === date);\n if (day) this.apply(day, costUsd, modelUsage);\n else {\n const fresh: UsageDay = { date, costUsd: 0, turns: 0 };\n this.apply(fresh, costUsd, modelUsage);\n state.days.push(fresh);\n }\n\n const monthKey = utcMonth(at);\n const months = state.months ?? [];\n const month = months.find((entry) => entry.month === monthKey);\n if (month) this.apply(month, costUsd, modelUsage);\n else {\n const fresh: UsageMonth = { month: monthKey, costUsd: 0, turns: 0 };\n this.apply(fresh, costUsd, modelUsage);\n months.push(fresh);\n }\n state.months = months;\n\n await this.store.writeUsageState(appName, this.prune(state, at));\n });\n }\n\n /** The app's recorded usage, oldest first, with expired days filtered out. */\n async read(appName: string, at: Date = new Date()): Promise<Required<UsageState>> {\n return this.prune(this.normalize(await this.store.readUsageState(appName)), at);\n }\n\n /** Today's spend plus the daily/monthly series, for the dashboard and the usage endpoints. */\n async summary(appName: string, at: Date = new Date()): Promise<UsageSummary> {\n const { days, months } = await this.read(appName, at);\n const date = utcDay(at);\n const today = days.find((entry) => entry.date === date);\n const models: Record<string, ModelUsageTotals> = {};\n for (const day of days) mergeTotals(models, day.models);\n return {\n date,\n todayCostUsd: today?.costUsd ?? 0,\n todayTurns: today?.turns ?? 0,\n todayModels: today?.models ?? {},\n totalCostUsd: round(days.reduce((sum, entry) => sum + entry.costUsd, 0)),\n models,\n retentionDays: this.retentionDays,\n // Any surviving entry means the app has reported usage at some point;\n // today's total being zero says nothing about that either way.\n recorded: days.length > 0 || months.length > 0,\n days,\n months,\n };\n }\n\n private apply(\n bucket: { costUsd: number; turns: number; models?: Record<string, ModelUsageTotals> },\n costUsd: number,\n modelUsage: Record<string, ModelTokenUsage> | undefined,\n ): void {\n bucket.costUsd = round(bucket.costUsd + costUsd);\n bucket.turns += 1;\n if (modelUsage !== undefined) {\n bucket.models ??= {};\n addModelUsage(bucket.models, modelUsage);\n }\n }\n\n /**\n * One-time migration for files written before monthly rollups existed:\n * absent `months` is seeded from the surviving days, so history inside the\n * retention window is not lost to the rollups. After the first write the\n * field is always present (possibly empty) and this is a no-op.\n */\n private normalize(state: UsageState): Required<UsageState> {\n if (state.months !== undefined) return { days: state.days, months: state.months };\n const byMonth = new Map<string, UsageMonth>();\n for (const day of state.days) {\n if (typeof day?.date !== 'string' || day.date.length < 7) continue;\n const key = day.date.slice(0, 7);\n const month = byMonth.get(key) ?? { month: key, costUsd: 0, turns: 0 };\n month.costUsd = round(month.costUsd + (Number.isFinite(day.costUsd) ? day.costUsd : 0));\n month.turns += Number.isFinite(day.turns) ? day.turns : 0;\n if (day.models !== undefined) {\n month.models ??= {};\n mergeTotals(month.models, day.models);\n }\n byMonth.set(key, month);\n }\n return { days: state.days, months: [...byMonth.values()] };\n }\n\n /** Drops days outside the retention window and normalizes ordering/values. Months are kept forever. */\n private prune(state: Required<UsageState>, at: Date): Required<UsageState> {\n const cutoff = utcDay(new Date(at.getTime() - (this.retentionDays - 1) * DAY_MS));\n const days = state.days\n .filter(\n (entry) =>\n typeof entry?.date === 'string' &&\n entry.date >= cutoff &&\n Number.isFinite(entry.costUsd) &&\n entry.costUsd >= 0,\n )\n .map((entry) => {\n const models = sanitizeModels(entry.models);\n return {\n date: entry.date,\n costUsd: round(entry.costUsd),\n turns: Number.isFinite(entry.turns) ? entry.turns : 0,\n ...(models !== undefined && { models }),\n };\n })\n .sort((a, b) => a.date.localeCompare(b.date));\n const months = state.months\n .filter(\n (entry) =>\n typeof entry?.month === 'string' &&\n Number.isFinite(entry.costUsd) &&\n entry.costUsd >= 0,\n )\n .map((entry) => {\n const models = sanitizeModels(entry.models);\n return {\n month: entry.month,\n costUsd: round(entry.costUsd),\n turns: Number.isFinite(entry.turns) ? entry.turns : 0,\n ...(models !== undefined && { models }),\n };\n })\n .sort((a, b) => a.month.localeCompare(b.month));\n return { days, months };\n }\n\n private enqueue(appName: string, work: () => Promise<void>): Promise<void> {\n const queue = this.queues.get(appName) ?? Promise.resolve();\n const next = queue.then(work);\n // Keep the chain alive after a failed write, but surface the error to the caller.\n const settled = next.catch(() => undefined);\n this.queues.set(appName, settled);\n void settled.then(() => {\n if (this.queues.get(appName) === settled) this.queues.delete(appName);\n });\n return next;\n }\n}\n","import { AppSdkError } from '@alashi/apps-sdk';\nimport type { AppBudget, GlobalConfig, Store } from './store.js';\nimport { formatUsd, type UsageRecorder, type UsageSummary } from './usage.js';\n\n/**\n * Stable discriminator on the thrown error. Apps see the host's error object\n * across a package boundary (and, once `AppContext` becomes RPC, across a\n * process boundary), so the reliable check is this code — not `instanceof`.\n */\nexport const BUDGET_EXCEEDED_CODE = 'budget_exceeded';\n\n/** Today's spend for an app measured against its cap. */\nexport interface BudgetStatus {\n /** The UTC calendar day the numbers cover. */\n date: string;\n /** Spend recorded for that day. */\n spentUsd: number;\n /**\n * Whether the host has any metered history for this app at all. When false,\n * `spentUsd` is an absence of data rather than a measured zero, and should be\n * rendered as such — never as `$0.0000`.\n */\n recorded: boolean;\n /** The configured daily cap; absent when the app has none. */\n dailyUsd?: number;\n /** Spend left before sessions are refused; absent when there is no cap. */\n remainingUsd?: number;\n /** True only when a cap is set and the day's spend has reached it. */\n overBudget: boolean;\n}\n\n/**\n * Refusal from the `createSession` budget gate.\n *\n * Extends the SDK's `AppSdkError` so it stays inside the taxonomy apps already\n * catch, and carries a `code` plus the numbers behind the decision so an app\n * can degrade gracefully (queue the work, skip the run, tell the user) instead\n * of treating a spend cap as a crash. The message is written to stand alone:\n * it is often the only text a human sees, rendered somewhere far from the host.\n */\nexport class BudgetExceededError extends AppSdkError {\n readonly code: typeof BUDGET_EXCEEDED_CODE = BUDGET_EXCEEDED_CODE;\n readonly appName: string;\n /** UTC day the cap applies to. */\n readonly date: string;\n /** The configured cap in USD. */\n readonly limitUsd: number;\n /** Spend already recorded for `date`. */\n readonly spentUsd: number;\n\n constructor(appName: string, params: { date: string; limitUsd: number; spentUsd: number }) {\n super(\n `Daily AI spend cap reached: app \"${appName}\" has spent ${formatUsd(params.spentUsd)} of its ` +\n `${formatUsd(params.limitUsd)} cap for ${params.date} (UTC). New sessions are refused until ` +\n `the cap resets at 00:00 UTC, or until it is raised in the app's alashi settings.`,\n );\n this.name = 'BudgetExceededError';\n this.appName = appName;\n this.date = params.date;\n this.limitUsd = params.limitUsd;\n this.spentUsd = params.spentUsd;\n }\n\n /** Duck-typed check that survives duplicate copies of this module. */\n static is(err: unknown): err is BudgetExceededError {\n return (\n err instanceof Error && (err as { code?: unknown }).code === (BUDGET_EXCEEDED_CODE as string)\n );\n }\n}\n\n/**\n * The cap the *host* has set for an app, or undefined when there is none.\n *\n * Read only from `GlobalConfig.budgets`, which is deliberately not part of the\n * config layers merged into `ctx.config`: an app must not be able to raise its\n * own ceiling through its manifest defaults or `apps/<name>/config.json`.\n *\n * A hand-edited cap that is not a finite number >= 0 is ignored rather than\n * treated as zero — a typo in config.json should not brick an app, and the\n * dashboard/settings write path validates before it ever gets stored.\n */\nexport function appBudget(config: GlobalConfig, name: string): number | undefined {\n const dailyUsd = config.budgets?.[name]?.dailyUsd;\n if (typeof dailyUsd !== 'number' || !Number.isFinite(dailyUsd) || dailyUsd < 0) return undefined;\n return dailyUsd;\n}\n\n/** Whether `spentUsd` has reached `dailyUsd`; the cap itself is inclusive. */\nfunction isOver(spentUsd: number, dailyUsd: number | undefined): boolean {\n return dailyUsd !== undefined && spentUsd >= dailyUsd;\n}\n\n/** Combines an already-read usage summary with a cap; no I/O. */\nexport function budgetStatusFor(summary: UsageSummary, dailyUsd: number | undefined): BudgetStatus {\n return {\n date: summary.date,\n spentUsd: summary.todayCostUsd,\n recorded: summary.recorded,\n ...(dailyUsd !== undefined && {\n dailyUsd,\n remainingUsd: Math.max(0, Number((dailyUsd - summary.todayCostUsd).toFixed(6))),\n }),\n overBudget: isOver(summary.todayCostUsd, dailyUsd),\n };\n}\n\n/** Today's spend against the app's cap, for the gate, the dashboard, and settings. */\nexport async function budgetStatus(\n store: Store,\n usage: UsageRecorder,\n name: string,\n at: Date = new Date(),\n): Promise<BudgetStatus> {\n const [config, summary] = await Promise.all([store.readGlobalConfig(), usage.summary(name, at)]);\n return budgetStatusFor(summary, appBudget(config, name));\n}\n\n/**\n * Fail-closed gate: throws `BudgetExceededError` when the app has already\n * spent its daily cap. Evaluated fresh on every call, so a cap edit or a\n * mid-day overspend takes effect without restarting the app.\n */\nexport async function assertWithinBudget(\n store: Store,\n usage: UsageRecorder,\n name: string,\n at: Date = new Date(),\n): Promise<void> {\n const status = await budgetStatus(store, usage, name, at);\n if (!status.overBudget || status.dailyUsd === undefined) return;\n throw new BudgetExceededError(name, {\n date: status.date,\n limitUsd: status.dailyUsd,\n spentUsd: status.spentUsd,\n });\n}\n\n/**\n * Sets (or, with `undefined`, clears) an app's daily cap in the host config.\n * Rejects anything that is not a finite, non-negative dollar amount; `0` is a\n * legitimate value meaning \"no AI spend at all for this app\".\n */\nexport async function setAppBudget(\n store: Store,\n name: string,\n dailyUsd: number | undefined,\n): Promise<void> {\n if (dailyUsd !== undefined && (!Number.isFinite(dailyUsd) || dailyUsd < 0)) {\n throw new AppSdkError(`Invalid daily spend cap: ${JSON.stringify(dailyUsd)}`);\n }\n const config = await store.readGlobalConfig();\n const budgets: Record<string, AppBudget> = { ...config.budgets };\n if (dailyUsd === undefined) delete budgets[name];\n else budgets[name] = { dailyUsd };\n const next: GlobalConfig = { ...config };\n if (Object.keys(budgets).length === 0) delete next.budgets;\n else next.budgets = budgets;\n await store.writeGlobalConfig(next);\n}\n","import { type AppManifest, AppSdkError, SDK_VERSION } from '@alashi/apps-sdk';\nimport semver from 'semver';\n\n/**\n * Enforces the manifest's declared `sdkVersion` range against the SDK\n * this host is built with. Apps that declare nothing are accepted.\n */\nexport function checkSdkCompatibility(manifest: Pick<AppManifest, 'name' | 'sdkVersion'>): void {\n const range = manifest.sdkVersion;\n if (!range) return;\n if (!semver.validRange(range)) {\n throw new AppSdkError(\n `App \"${manifest.name}\" declares an invalid sdkVersion range: ${JSON.stringify(range)}`,\n );\n }\n if (!semver.satisfies(SDK_VERSION, range)) {\n throw new AppSdkError(\n `App \"${manifest.name}\" supports SDK ${range}, but this host uses SDK ${SDK_VERSION}. ` +\n 'Update the host (or the app) to a compatible version.',\n );\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport {\n type AppDefinition,\n type AppManifest,\n AppSdkError,\n parseManifestWithWarnings,\n SDK_VERSION,\n} from '@alashi/apps-sdk';\nimport { checkSdkCompatibility } from './compat.js';\n\ninterface PackageJson {\n main?: string;\n exports?: unknown;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction entryFromExports(exportsField: unknown): string | undefined {\n if (typeof exportsField === 'string') return exportsField;\n if (typeof exportsField !== 'object' || exportsField === null) return undefined;\n const record = exportsField as Record<string, unknown>;\n const dot = record['.'] ?? record.default ?? record.import;\n if (typeof dot === 'string') return dot;\n if (typeof dot === 'object' && dot !== null) {\n const conditions = dot as Record<string, unknown>;\n const target = conditions.default ?? conditions.import;\n if (typeof target === 'string') return target;\n }\n return undefined;\n}\n\nexport interface LoadedApp {\n definition: AppDefinition;\n manifest: AppManifest;\n entryPath: string;\n /**\n * One human-readable line per manifest key this host's SDK did not\n * recognize and therefore dropped from {@link LoadedApp.manifest}. Never a\n * reason to refuse the app: only `sdkVersion` decides version skew.\n */\n warnings: string[];\n}\n\nexport interface LoadAppOptions {\n /**\n * Where manifest warnings go. Defaults to `console.warn`, prefixed with the\n * app name — `getApp` in server.ts caches loads, so that is once per load,\n * not once per request. Callers that surface the warnings themselves (the\n * CLI on install/update) pass their own sink to avoid printing twice.\n */\n warn?: (message: string) => void;\n}\n\n/**\n * Imports an app's entry module and validates its default export.\n * The manifest is always re-validated here — the host never trusts\n * that an app used defineApp().\n */\nexport async function loadApp(repoDir: string, options: LoadAppOptions = {}): Promise<LoadedApp> {\n let pkg: PackageJson;\n try {\n pkg = JSON.parse(await readFile(join(repoDir, 'package.json'), 'utf8')) as PackageJson;\n } catch (err) {\n throw new AppSdkError(`App has no readable package.json in ${repoDir}`, { cause: err });\n }\n\n const entry = entryFromExports(pkg.exports) ?? pkg.main ?? 'index.js';\n const entryPath = join(repoDir, entry);\n\n let mod: Record<string, unknown>;\n try {\n // Cache-bust so `alashi update` reloads changed code within one process.\n mod = (await import(`${pathToFileURL(entryPath).href}?t=${Date.now()}`)) as Record<\n string,\n unknown\n >;\n } catch (err) {\n let detail = String(err);\n // defineApp() in the app's own bundled SDK validates at import time; an\n // \"unrecognized key\" there means the manifest uses fields newer than that\n // bundled SDK understands (cross-package, so match by name not instanceof).\n if ((err as Error)?.name === 'ManifestValidationError' && /Unrecognized key/i.test(detail)) {\n detail +=\n \"\\nThe manifest may use fields newer than the app's bundled @alashi/apps-sdk — update the app's dependency to match its sdkVersion.\";\n }\n throw new AppSdkError(`Failed to import app entry ${entry}: ${detail}`, { cause: err });\n }\n\n const definition = mod.default as AppDefinition | undefined;\n if (typeof definition !== 'object' || definition === null || !('manifest' in definition)) {\n throw new AppSdkError(\n 'App entry must default-export an app definition (use defineApp() from @alashi/apps-sdk)',\n );\n }\n\n // The app parsed its manifest with its *own* bundled SDK copy, which\n // already stripped every key that copy did not know. Re-validating the raw\n // input is what lets a field survive an app pinned to an older SDK than this\n // host. The host still parses with its own SDK — never trust the app's\n // parsed output.\n const input: unknown = isRecord(definition.rawManifest)\n ? definition.rawManifest\n : definition.manifest;\n\n // Check the declared SDK range before parsing: a manifest using fields newer\n // than this host's SDK should say \"update the host\" rather than quietly\n // dropping the fields it was written for.\n const raw = input as { name?: unknown; sdkVersion?: unknown };\n if (typeof raw.sdkVersion === 'string') {\n checkSdkCompatibility({\n name: typeof raw.name === 'string' ? raw.name : entry,\n sdkVersion: raw.sdkVersion,\n });\n }\n\n const { manifest, unknownKeys } = parseManifestWithWarnings(input);\n // Unknown keys are a warning, not a failure — older kernels ignore unknown\n // syscall flags. Turning them back into an error for *app authors* is\n // `alashi validate` (card #22), not the load path.\n const warnings = unknownKeys.map(\n (key) => `ignoring unknown manifest key \"${key}\" — this host uses SDK ${SDK_VERSION}`,\n );\n const warn = options.warn ?? ((message: string) => console.warn(`[${manifest.name}] ${message}`));\n for (const warning of warnings) warn(warning);\n\n return { definition, manifest, entryPath, warnings };\n}\n","import { execFile } from 'node:child_process';\nimport { mkdir, symlink } from 'node:fs/promises';\nimport { isAbsolute, resolve } from 'node:path';\nimport { promisify } from 'node:util';\nimport { AppSdkError } from '@alashi/apps-sdk';\n\nconst execFileAsync = promisify(execFile);\n\nexport type AppSourceSpec =\n | { type: 'git'; url: string; ref?: string }\n | { type: 'local'; path: string };\n\n/** How the CLI argument was understood: a source, or a name to resolve via registries. */\nexport type SourceArg = AppSourceSpec | { type: 'registry'; name: string };\n\nexport function parseSourceArg(arg: string): SourceArg {\n if (arg.startsWith('.') || isAbsolute(arg)) {\n return { type: 'local', path: resolve(arg) };\n }\n if (/^(https?:\\/\\/|git@|ssh:\\/\\/)/.test(arg)) {\n return { type: 'git', url: arg };\n }\n if (/^github\\.com\\//.test(arg)) {\n return { type: 'git', url: `https://${arg}` };\n }\n return { type: 'registry', name: arg };\n}\n\nexport async function git(args: string[], cwd?: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', args, cwd ? { cwd } : {});\n return stdout.trim();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new AppSdkError(`git ${args[0]} failed: ${message}`, { cause: err });\n }\n}\n\n/** Materializes a source at destDir: git clone, or symlink for local dev installs. */\nexport async function fetchSource(spec: AppSourceSpec, destDir: string): Promise<void> {\n await mkdir(resolve(destDir, '..'), { recursive: true });\n if (spec.type === 'git') {\n const args = ['clone', '--depth', '1'];\n if (spec.ref) args.push('--branch', spec.ref);\n args.push(spec.url, destDir);\n await git(args);\n return;\n }\n await symlink(spec.path, destDir, 'dir');\n}\n\n/** Brings an installed source up to date in place. Local symlinks are always live. */\nexport async function updateSource(spec: AppSourceSpec, repoDir: string): Promise<void> {\n if (spec.type === 'git') {\n await git(['pull', '--ff-only'], repoDir);\n }\n}\n","import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { AppSdkError } from '@alashi/apps-sdk';\nimport { z } from 'zod';\nimport { fetchSource, git } from './sources.js';\nimport type { RegistryRef, Store } from './store.js';\n\nexport interface RegistryApp {\n name: string;\n repo: string;\n description?: string;\n ref?: string;\n sdkVersion?: string;\n}\n\nexport interface RegistryIndex {\n apps: RegistryApp[];\n}\n\nconst registryIndexSchema = z.object({\n apps: z.array(\n z.object({\n name: z.string().min(1),\n repo: z.string().min(1),\n description: z.string().optional(),\n ref: z.string().optional(),\n sdkVersion: z.string().optional(),\n }),\n ),\n});\n\nfunction parseIndex(raw: string, source: string): RegistryIndex {\n let data: unknown;\n try {\n data = JSON.parse(raw);\n } catch (err) {\n throw new AppSdkError(`Registry ${source} did not return valid JSON`, { cause: err });\n }\n const result = registryIndexSchema.safeParse(data);\n if (!result.success) {\n throw new AppSdkError(`Registry ${source} has an invalid index: ${result.error.message}`);\n }\n return result.data;\n}\n\n/**\n * Fetches a registry index and caches it under registries/<name>.json.\n * URLs ending in .json are fetched over HTTP; anything else is treated\n * as a git repo containing index.json at its root (brew-tap style).\n */\nexport async function refreshRegistry(store: Store, registry: RegistryRef): Promise<RegistryIndex> {\n let raw: string;\n if (/^https?:\\/\\/.*\\.json$/.test(registry.url)) {\n const response = await fetch(registry.url);\n if (!response.ok) {\n throw new AppSdkError(`Registry fetch failed (${response.status}) for ${registry.url}`);\n }\n raw = await response.text();\n } else {\n const cloneDir = join(store.stagingDir, `registry-${registry.name}-${Date.now()}`);\n await fetchSource({ type: 'git', url: registry.url }, cloneDir);\n try {\n raw = await readFile(join(cloneDir, 'index.json'), 'utf8');\n } finally {\n await rm(cloneDir, { recursive: true, force: true });\n }\n }\n const index = parseIndex(raw, registry.url);\n await mkdir(store.registriesDir, { recursive: true });\n await writeFile(store.registryCachePath(registry.name), JSON.stringify(index, null, 2), 'utf8');\n return index;\n}\n\nexport async function readCachedIndex(\n store: Store,\n name: string,\n): Promise<RegistryIndex | undefined> {\n try {\n return parseIndex(await readFile(store.registryCachePath(name), 'utf8'), name);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw err;\n }\n}\n\nexport async function addRegistry(store: Store, name: string, url: string): Promise<RegistryIndex> {\n const config = await store.readGlobalConfig();\n if (config.registries.some((r) => r.name === name)) {\n throw new AppSdkError(`Registry \"${name}\" already exists`);\n }\n const index = await refreshRegistry(store, { name, url });\n config.registries.push({ name, url });\n await store.writeGlobalConfig(config);\n return index;\n}\n\nexport async function removeRegistry(store: Store, name: string): Promise<void> {\n const config = await store.readGlobalConfig();\n const next = config.registries.filter((r) => r.name !== name);\n if (next.length === config.registries.length) {\n throw new AppSdkError(`Registry \"${name}\" is not configured`);\n }\n config.registries = next;\n await store.writeGlobalConfig(config);\n await rm(store.registryCachePath(name), { force: true });\n}\n\nexport interface ResolvedApp {\n registry: string;\n app: RegistryApp;\n}\n\n/** Finds an app by name across configured registries (refreshing caches). */\nexport async function resolveApp(store: Store, appName: string): Promise<ResolvedApp> {\n const config = await store.readGlobalConfig();\n if (config.registries.length === 0) {\n throw new AppSdkError(\n `No registries configured. Add one with: alashi registry add <name> <url>`,\n );\n }\n for (const registry of config.registries) {\n let index: RegistryIndex;\n try {\n index = await refreshRegistry(store, registry);\n } catch {\n const cached = await readCachedIndex(store, registry.name);\n if (!cached) continue;\n index = cached;\n }\n const app = index.apps.find((a) => a.name === appName);\n if (app) return { registry: registry.name, app };\n }\n throw new AppSdkError(`App \"${appName}\" not found in any configured registry`);\n}\n\nexport { git };\n","import { execFile } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, rename, rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { AppSdkError } from '@alashi/apps-sdk';\nimport { loadApp } from './loader.js';\nimport { resolveApp } from './registry.js';\nimport { type AppSourceSpec, fetchSource, parseSourceArg, updateSource } from './sources.js';\nimport type { InstallRecord, Store } from './store.js';\n\nconst execFileAsync = promisify(execFile);\n\nasync function hasDependencies(dir: string): Promise<boolean> {\n try {\n const pkg = JSON.parse(await readFile(join(dir, 'package.json'), 'utf8')) as {\n dependencies?: Record<string, string>;\n };\n return Object.keys(pkg.dependencies ?? {}).length > 0;\n } catch {\n return false;\n }\n}\n\n/** Lifecycle scripts stay off: installing an app must not execute arbitrary code. */\nexport async function installDependencies(dir: string): Promise<void> {\n if (!(await hasDependencies(dir))) return;\n try {\n await execFileAsync(\n 'npm',\n ['install', '--ignore-scripts', '--omit=dev', '--no-fund', '--no-audit'],\n { cwd: dir },\n );\n } catch (err) {\n throw new AppSdkError(`npm install failed: ${err instanceof Error ? err.message : err}`, {\n cause: err,\n });\n }\n}\n\nexport interface InstallOptions {\n /** Expected app name (enforced against the manifest when set). */\n expectName?: string;\n}\n\n/**\n * What an install or update produced: the record that was persisted, plus the\n * manifest warnings from loading the app. Warnings are deliberately *not* part\n * of {@link InstallRecord} — they describe this host's SDK, not the app, and\n * go stale the moment the host is upgraded.\n */\nexport interface InstallResult {\n record: InstallRecord;\n /** Manifest keys this host's SDK ignored; never a reason to fail the command. */\n warnings: string[];\n}\n\n/**\n * Install flow: materialize the source into a staging dir, install deps\n * (scripts disabled), import + validate the manifest, and only then move\n * it into apps/<name>/repo. A failed validation never leaves a broken app.\n */\nexport async function installApp(\n store: Store,\n source: string | AppSourceSpec,\n options: InstallOptions = {},\n): Promise<InstallResult> {\n let spec: AppSourceSpec;\n let expectName = options.expectName;\n\n if (typeof source === 'string') {\n const parsed = parseSourceArg(source);\n if (parsed.type === 'registry') {\n const resolved = await resolveApp(store, parsed.name);\n spec = {\n type: 'git',\n url: resolved.app.repo,\n ...(resolved.app.ref && { ref: resolved.app.ref }),\n };\n expectName = expectName ?? parsed.name;\n } else {\n spec = parsed;\n }\n } else {\n spec = source;\n }\n\n // Local sources are symlinked in place; git sources are staged first.\n let stageDir: string | undefined;\n let inspectDir: string;\n if (spec.type === 'local') {\n inspectDir = spec.path;\n } else {\n stageDir = join(store.stagingDir, randomUUID());\n await fetchSource(spec, stageDir);\n await installDependencies(stageDir);\n inspectDir = stageDir;\n }\n\n try {\n // Collect rather than log: the CLI prints these with the install summary.\n const { manifest, warnings } = await loadApp(inspectDir, { warn: () => {} });\n if (expectName && manifest.name !== expectName) {\n throw new AppSdkError(\n `App manifest name \"${manifest.name}\" does not match requested \"${expectName}\"`,\n );\n }\n const existing = await store.readAppRecord(manifest.name);\n if (existing) {\n throw new AppSdkError(\n `App \"${manifest.name}\" is already installed (use: alashi update ${manifest.name})`,\n );\n }\n\n const repoDir = store.appRepoDir(manifest.name);\n await mkdir(store.appDir(manifest.name), { recursive: true });\n if (stageDir) {\n await rename(stageDir, repoDir);\n stageDir = undefined;\n } else {\n await fetchSource(spec, repoDir);\n }\n await mkdir(store.appDataDir(manifest.name), { recursive: true });\n\n const now = new Date().toISOString();\n const record: InstallRecord = {\n name: manifest.name,\n version: manifest.version,\n ...(manifest.sdkVersion && { sdkVersion: manifest.sdkVersion }),\n source: spec,\n installedAt: now,\n updatedAt: now,\n };\n await store.writeAppRecord(record);\n return { record, warnings };\n } finally {\n if (stageDir) await rm(stageDir, { recursive: true, force: true });\n }\n}\n\nexport async function updateApp(store: Store, name: string): Promise<InstallResult> {\n const record = await store.readAppRecord(name);\n if (!record) throw new AppSdkError(`App \"${name}\" is not installed`);\n\n const repoDir = store.appRepoDir(name);\n await updateSource(record.source, repoDir);\n await installDependencies(repoDir);\n const { manifest, warnings } = await loadApp(repoDir, { warn: () => {} });\n if (manifest.name !== name) {\n throw new AppSdkError(`App renamed itself to \"${manifest.name}\"; refusing update`);\n }\n\n const updated: InstallRecord = {\n ...record,\n version: manifest.version,\n ...(manifest.sdkVersion && { sdkVersion: manifest.sdkVersion }),\n updatedAt: new Date().toISOString(),\n };\n await store.writeAppRecord(updated);\n return { record: updated, warnings };\n}\n\nexport interface RemoveOptions {\n /** Also delete the app's data/ and config (default keeps them). */\n purge?: boolean;\n}\n\nexport async function removeApp(\n store: Store,\n name: string,\n options: RemoveOptions = {},\n): Promise<void> {\n const record = await store.readAppRecord(name);\n if (!record) throw new AppSdkError(`App \"${name}\" is not installed`);\n\n if (options.purge) {\n await rm(store.appDir(name), { recursive: true, force: true });\n return;\n }\n await rm(store.appRepoDir(name), { recursive: true, force: true });\n await rm(store.appRecordPath(name), { force: true });\n}\n","import { execFile } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport {\n cp,\n lstat,\n mkdir,\n readdir,\n readFile,\n readlink,\n rename,\n rm,\n stat,\n writeFile,\n} from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';\nimport { promisify } from 'node:util';\nimport {\n type AIProvider,\n APP_NAME_PATTERN,\n AppSdkError,\n type SessionTranscript,\n} from '@alashi/apps-sdk';\nimport { installDependencies } from './installer.js';\nimport type { InstallRecord, Store } from './store.js';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Container portability. An app is only half-portable on its own: the\n * container holds sessionId pointers, but the conversation transcripts live\n * with the engine (keyed by the app's data dir as cwd). An export therefore\n * snapshots the container *plus* the transcripts, engine-neutrally, and the\n * import re-keys them to this machine's data dir.\n *\n * Archive layout (tar.gz):\n * alashi-export.json { formatVersion, exportedAt, app, transcripts }\n * container/ the contents of $ALASHI_HOME/apps/<name>/\n * transcripts/<sessionId>.jsonl\n */\nexport const EXPORT_FORMAT_VERSION = 1;\n\nconst METADATA_FILE = 'alashi-export.json';\nconst CONTAINER_DIR = 'container';\nconst TRANSCRIPTS_DIR = 'transcripts';\nconst TRANSCRIPT_EXT = '.jsonl';\n\n/** Re-derivable from the source, and by far the biggest thing in a container. */\nconst CONTAINER_EXCLUDES = [join('repo', 'node_modules'), join('repo', '.git')];\n\n/** Session ids become file names, so they must not be able to escape the dir. */\nconst SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\nexport interface ExportMetadata {\n formatVersion: number;\n exportedAt: string;\n app: InstallRecord;\n /** Session ids that have a transcripts/<id>.jsonl entry in the archive. */\n transcripts: string[];\n}\n\nexport interface ExportOptions {\n /** Archive path; defaults to ./<name>-<version>.tar.gz under the cwd. */\n out?: string;\n}\n\nexport interface ExportResult {\n archivePath: string;\n record: InstallRecord;\n transcriptCount: number;\n warnings: string[];\n}\n\nexport interface ImportResult {\n record: InstallRecord;\n transcriptCount: number;\n warnings: string[];\n}\n\n/** Shelling out to tar matches how git and npm are already invoked. */\nasync function tar(args: string[]): Promise<string> {\n try {\n const { stdout } = await execFileAsync('tar', args);\n return stdout;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new AppSdkError(`tar ${args[0]} failed: ${message}`, { cause: err });\n }\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await lstat(path);\n return true;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return false;\n throw err;\n }\n}\n\nasync function isDirectory(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isDirectory();\n } catch {\n return false;\n }\n}\n\nfunction containerFilter(appDir: string): (src: string) => boolean {\n return (src: string) => {\n const rel = relative(appDir, src);\n if (rel === '') return true;\n return !CONTAINER_EXCLUDES.some(\n (excluded) => rel === excluded || rel.startsWith(excluded + sep),\n );\n };\n}\n\n/**\n * Packs apps/<name>/ plus the transcripts of every conversation that ever had\n * a turn into a single self-contained tar.gz.\n */\nexport async function exportApp(\n store: Store,\n provider: AIProvider,\n name: string,\n options: ExportOptions = {},\n): Promise<ExportResult> {\n const record = await store.readAppRecord(name);\n if (!record) throw new AppSdkError(`App \"${name}\" is not installed`);\n\n const archivePath = resolve(\n process.cwd(),\n options.out ?? `${record.name}-${record.version}.tar.gz`,\n );\n if (await exists(archivePath)) {\n throw new AppSdkError(`Refusing to overwrite existing file: ${archivePath}`);\n }\n\n const warnings: string[] = [];\n const appDir = store.appDir(name);\n const stage = join(store.stagingDir, randomUUID());\n try {\n await mkdir(stage, { recursive: true });\n // dereference: local installs symlink repo/, and an archive must be\n // self-contained on the machine it lands on.\n await cp(appDir, join(stage, CONTAINER_DIR), {\n recursive: true,\n dereference: true,\n filter: containerFilter(appDir),\n });\n\n const { conversations } = await store.readChatState(name);\n const sessionIds = [\n ...new Set(\n conversations\n .map((conversation) => conversation.sessionId)\n .filter((sessionId): sessionId is string => !!sessionId),\n ),\n ];\n\n let transcripts: SessionTranscript[] = [];\n if (!provider.exportTranscripts) {\n if (sessionIds.length > 0) {\n warnings.push(\n `Provider \"${provider.name}\" cannot export transcripts; the archive holds the container only`,\n );\n }\n } else if (sessionIds.length > 0) {\n transcripts = await provider.exportTranscripts(sessionIds);\n }\n\n await mkdir(join(stage, TRANSCRIPTS_DIR), { recursive: true });\n const written: string[] = [];\n for (const transcript of transcripts) {\n if (!SESSION_ID_PATTERN.test(transcript.sessionId)) {\n warnings.push(\n `Skipped transcript with unusable session id ${JSON.stringify(transcript.sessionId)}`,\n );\n continue;\n }\n await writeFile(\n join(stage, TRANSCRIPTS_DIR, `${transcript.sessionId}${TRANSCRIPT_EXT}`),\n transcript.content,\n 'utf8',\n );\n written.push(transcript.sessionId);\n }\n if (provider.exportTranscripts && written.length < sessionIds.length) {\n warnings.push(\n `${sessionIds.length - written.length} of ${sessionIds.length} conversations had no transcript in \"${provider.name}\" storage`,\n );\n }\n\n const metadata: ExportMetadata = {\n formatVersion: EXPORT_FORMAT_VERSION,\n exportedAt: new Date().toISOString(),\n app: record,\n transcripts: written,\n };\n await writeFile(join(stage, METADATA_FILE), `${JSON.stringify(metadata, null, 2)}\\n`, 'utf8');\n\n await mkdir(dirname(archivePath), { recursive: true });\n await tar(['-czf', archivePath, '-C', stage, '.']);\n\n return { archivePath, record, transcriptCount: written.length, warnings };\n } finally {\n await rm(stage, { recursive: true, force: true });\n }\n}\n\n/** An archive is untrusted input: no absolute members, no \"..\" traversal. */\nasync function assertSafeMembers(archive: string): Promise<void> {\n const listing = await tar(['-tzf', archive]);\n for (const line of listing.split('\\n')) {\n const member = line.trim();\n if (member === '') continue;\n const segments = member.split(/[\\\\/]/);\n if (isAbsolute(member) || member.startsWith('/') || segments.includes('..')) {\n throw new AppSdkError(\n `Refusing to import ${archive}: it contains an unsafe path ${JSON.stringify(member)}`,\n );\n }\n }\n}\n\n/** Second belt: a symlink inside the archive must not point out of the stage. */\nasync function assertNothingEscaped(stage: string): Promise<void> {\n const root = resolve(stage);\n const walk = async (dir: string): Promise<void> => {\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n const path = join(dir, entry.name);\n if (entry.isSymbolicLink()) {\n const target = resolve(dir, await readlink(path));\n const rel = relative(root, target);\n if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) {\n throw new AppSdkError(\n `Refusing to import: ${relative(root, path)} links outside the archive (${target})`,\n );\n }\n continue;\n }\n if (entry.isDirectory()) await walk(path);\n }\n };\n await walk(root);\n}\n\nasync function readMetadata(path: string): Promise<ExportMetadata> {\n let raw: string;\n try {\n raw = await readFile(path, 'utf8');\n } catch (err) {\n throw new AppSdkError(`Not an alashi export archive: ${METADATA_FILE} is missing`, {\n cause: err,\n });\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new AppSdkError(`Corrupt ${METADATA_FILE} in archive`, { cause: err });\n }\n const metadata = parsed as Partial<ExportMetadata> | null;\n if (metadata?.formatVersion !== EXPORT_FORMAT_VERSION) {\n throw new AppSdkError(\n `Unsupported export formatVersion ${JSON.stringify(metadata?.formatVersion)}; this alashi reads formatVersion ${EXPORT_FORMAT_VERSION}`,\n );\n }\n return metadata as ExportMetadata;\n}\n\nasync function readInstallRecord(path: string): Promise<InstallRecord> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(await readFile(path, 'utf8'));\n } catch (err) {\n throw new AppSdkError(`Archive has no readable ${CONTAINER_DIR}/app.json`, { cause: err });\n }\n const record = parsed as Partial<InstallRecord> | null;\n if (!record || typeof record.name !== 'string' || typeof record.version !== 'string') {\n throw new AppSdkError(`Archive ${CONTAINER_DIR}/app.json is not an install record`);\n }\n if (!APP_NAME_PATTERN.test(record.name)) {\n throw new AppSdkError(`Archive carries an invalid app name: ${JSON.stringify(record.name)}`);\n }\n return record as InstallRecord;\n}\n\nasync function readTranscripts(dir: string): Promise<SessionTranscript[]> {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw err;\n }\n const transcripts: SessionTranscript[] = [];\n for (const entry of entries.sort()) {\n if (!entry.endsWith(TRANSCRIPT_EXT)) continue;\n const sessionId = entry.slice(0, -TRANSCRIPT_EXT.length);\n if (!SESSION_ID_PATTERN.test(sessionId)) continue;\n transcripts.push({ sessionId, content: await readFile(join(dir, entry), 'utf8') });\n }\n return transcripts;\n}\n\n/** Staging and apps/ are both under $ALASHI_HOME, so rename is the fast path. */\nasync function moveDir(from: string, to: string): Promise<void> {\n try {\n await rename(from, to);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EXDEV') throw err;\n await cp(from, to, { recursive: true });\n await rm(from, { recursive: true, force: true });\n }\n}\n\n/** Restores a container exported by {@link exportApp}, plus its transcripts. */\nexport async function importApp(\n store: Store,\n provider: AIProvider,\n archivePath: string,\n): Promise<ImportResult> {\n const archive = resolve(process.cwd(), archivePath);\n if (!(await exists(archive))) throw new AppSdkError(`Archive not found: ${archive}`);\n await assertSafeMembers(archive);\n\n const warnings: string[] = [];\n const stage = join(store.stagingDir, randomUUID());\n try {\n await mkdir(stage, { recursive: true });\n await tar(['-xzf', archive, '-C', stage, '--no-same-owner']);\n await assertNothingEscaped(stage);\n\n await readMetadata(join(stage, METADATA_FILE));\n const containerDir = join(stage, CONTAINER_DIR);\n if (!(await isDirectory(containerDir))) {\n throw new AppSdkError(`Archive has no ${CONTAINER_DIR}/ directory: ${archive}`);\n }\n\n const record = await readInstallRecord(join(containerDir, 'app.json'));\n const name = record.name;\n if (await store.readAppRecord(name)) {\n throw new AppSdkError(`App \"${name}\" is already installed (use: alashi remove ${name})`);\n }\n const appDir = store.appDir(name);\n if (await exists(appDir)) {\n throw new AppSdkError(\n `${appDir} already exists (leftover data from a previous install); delete it before importing`,\n );\n }\n\n await mkdir(store.appsDir, { recursive: true });\n await moveDir(containerDir, appDir);\n\n // Past this point the container is live: every failure has to say so\n // rather than look like a clean abort.\n try {\n await mkdir(store.appDataDir(name), { recursive: true });\n await installDependencies(store.appRepoDir(name));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new AppSdkError(\n `Restored ${name} to ${appDir}, but reinstalling its dependencies failed: ${message}. Run \"alashi update ${name}\" before using it.`,\n { cause: err },\n );\n }\n\n const transcripts = await readTranscripts(join(stage, TRANSCRIPTS_DIR));\n let restored = 0;\n if (transcripts.length > 0) {\n if (!provider.importTranscripts) {\n warnings.push(\n `Provider \"${provider.name}\" cannot import transcripts; ${transcripts.length} conversation transcripts were not restored`,\n );\n } else {\n try {\n // The *destination* data dir: that is what re-keys the transcripts\n // for this machine's engine storage.\n await provider.importTranscripts(transcripts, { cwd: store.appDataDir(name) });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new AppSdkError(\n `Restored ${name} to ${appDir}, but restoring its transcripts failed: ${message}. The app works; its chat history does not.`,\n { cause: err },\n );\n }\n restored = transcripts.length;\n }\n }\n\n return { record, transcriptCount: restored, warnings };\n } finally {\n await rm(stage, { recursive: true, force: true });\n }\n}\n","import { mkdir, readFile } from 'node:fs/promises';\nimport { basename, join, resolve } from 'node:path';\nimport {\n type AgentDefinition,\n type AIProvider,\n type AISession,\n type AppConfigStatus,\n type AppContext,\n type AppManifest,\n AppSdkError,\n ConfigError,\n type ConfigObject,\n evaluateAppConfig,\n interpolateConfig,\n JsonFileStore,\n type Logger,\n type McpServerConfig,\n manifestConfigDefaults,\n mergeConfig,\n parseAgentFile,\n type SessionOptions,\n} from '@alashi/apps-sdk';\nimport { assertWithinBudget } from './budget.js';\nimport type { Store } from './store.js';\nimport type { TurnUsage, UsageRecorder } from './usage.js';\n\nfunction createLogger(prefix: string): Logger {\n const log = (\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n data?: Record<string, unknown>,\n ): void => {\n const line = `[${prefix}] ${message}`;\n if (data === undefined) console[level](line);\n else console[level](line, data);\n };\n return {\n debug: (message, data) => log('debug', message, data),\n info: (message, data) => log('info', message, data),\n warn: (message, data) => log('warn', message, data),\n error: (message, data) => log('error', message, data),\n };\n}\n\ninterface SessionDefaults {\n mcpServers: Record<string, McpServerConfig>;\n allowedTools: string[];\n agents: Record<string, AgentDefinition>;\n skillDirs: string[];\n instructions?: string;\n cwd: string;\n /** When set, createSession fails closed with this message (missing required config). */\n unreadyError?: string;\n /**\n * Second fail-closed gate: throws `BudgetExceededError` when the app has\n * already spent its host-set daily cap. Called on every createSession, so\n * the answer reflects spend and cap as of right now.\n */\n checkBudget?: () => Promise<void>;\n /**\n * Called once per `result` event with the cost and per-model token usage\n * the engine reported (either possibly undefined). Must not throw — the\n * caller's stream is not the place to fail.\n */\n recordUsage?: (turn: TurnUsage) => Promise<void>;\n}\n\n/**\n * Wraps a session so every `result` event's cost is accounted for before the\n * event reaches the caller. This sits under *every* session an app opens —\n * chat turns, cron job handlers, and app routes alike — so spend is recorded\n * no matter who opened the session.\n */\nfunction meterSession(\n session: AISession,\n recordUsage: (turn: TurnUsage) => Promise<void>,\n): AISession {\n return {\n get id() {\n return session.id;\n },\n send(input: string) {\n const events = session.send(input);\n return (async function* metered() {\n for await (const event of events) {\n if (event.type === 'result') {\n await recordUsage({ costUsd: event.costUsd, modelUsage: event.modelUsage });\n }\n yield event;\n }\n })();\n },\n interrupt: () => session.interrupt(),\n close: () => session.close(),\n };\n}\n\n/**\n * Wraps the host provider so every session an app opens automatically gets\n * its manifest-declared MCP servers, tool allowlist, agents, skills,\n * instructions, and data-dir cwd. Per-call options still win where they\n * overlap.\n */\nfunction scopeProvider(provider: AIProvider, defaults: SessionDefaults): AIProvider {\n return {\n name: provider.name,\n async createSession(options: SessionOptions = {}): Promise<AISession> {\n if (defaults.unreadyError !== undefined) throw new ConfigError(defaults.unreadyError);\n // Money gate, above provider.createSession on purpose: the engine process\n // is the first thing that can cost, so an over-budget app must be turned\n // away before one is ever spawned.\n await defaults.checkBudget?.();\n const mcpServers = { ...defaults.mcpServers, ...options.mcpServers };\n const allowedTools = [\n ...new Set([...defaults.allowedTools, ...(options.allowedTools ?? [])]),\n ];\n const agents = { ...defaults.agents, ...options.agents };\n const skillDirs = [...new Set([...defaults.skillDirs, ...(options.skillDirs ?? [])])];\n const session = await provider.createSession({\n cwd: defaults.cwd,\n ...(defaults.instructions !== undefined && { systemPrompt: defaults.instructions }),\n ...options,\n ...(Object.keys(mcpServers).length > 0 && { mcpServers }),\n ...(allowedTools.length > 0 && { allowedTools }),\n ...(Object.keys(agents).length > 0 && { agents }),\n ...(skillDirs.length > 0 && { skillDirs }),\n });\n return defaults.recordUsage ? meterSession(session, defaults.recordUsage) : session;\n },\n };\n}\n\n/**\n * Reads and parses the manifest-declared agent files. The agent's name is\n * the filename without the `.md` extension.\n */\nexport async function loadAgents(\n repoDir: string,\n paths: string[],\n): Promise<Record<string, AgentDefinition>> {\n const entries = await Promise.all(\n paths.map(async (path) => {\n const content = await readFile(resolve(repoDir, path), 'utf8');\n return [basename(path, '.md'), parseAgentFile(content, path)] as const;\n }),\n );\n return Object.fromEntries(entries);\n}\n\n/** Reads the manifest-declared instructions file; the content is the app's system prompt. */\nexport async function loadInstructions(\n repoDir: string,\n path: string | undefined,\n): Promise<string | undefined> {\n if (path === undefined) return undefined;\n const content = (await readFile(resolve(repoDir, path), 'utf8')).trim();\n if (content === '') {\n throw new AppSdkError(`Instructions file \"${path}\" is empty`);\n }\n return content;\n}\n\nexport interface ResolvedAppConfig {\n /** Merged config layers, before `${config.*}` interpolation. */\n merged: ConfigObject;\n status: AppConfigStatus;\n}\n\n/** Merges the config layers for an app and evaluates its setup state. */\nexport async function resolveAppConfig(\n store: Store,\n manifest: AppManifest,\n): Promise<ResolvedAppConfig> {\n const globalConfig = await store.readGlobalConfig();\n const merged = mergeConfig(\n manifestConfigDefaults(manifest),\n globalConfig.apps?.[manifest.name],\n await store.readAppConfig(manifest.name),\n );\n return { merged, status: evaluateAppConfig(manifest, merged) };\n}\n\n/**\n * Builds the AppContext for an installed app: merges the config layers\n * (manifest defaults < global apps[name] < per-app config), resolves\n * `${config.*}` placeholders (with `dataDir` available as a built-in key),\n * and scopes the AI provider to the app's manifest.\n *\n * Setup state shapes the session defaults: unconfigured MCP servers are\n * dropped (with their tools) so interpolation never hits a missing key, and\n * while required keys are missing the scoped provider fails closed.\n *\n * When a `usage` recorder is supplied, every session opened through the\n * returned context reports its per-turn cost to it, and the app's host-set\n * daily spend cap (if any) is enforced on every createSession.\n */\nexport async function buildAppContext(\n store: Store,\n manifest: AppManifest,\n provider: AIProvider,\n usage?: UsageRecorder,\n): Promise<AppContext> {\n const name = manifest.name;\n const dataDir = store.appDataDir(name);\n const repoDir = store.appRepoDir(name);\n await mkdir(dataDir, { recursive: true });\n\n const { merged, status } = await resolveAppConfig(store, manifest);\n // dataDir is a host-provided lookup key: apps reference it as ${config.dataDir}.\n const lookup: ConfigObject = { ...merged, dataDir };\n const interpolate = <T>(value: T): T =>\n interpolateConfig(value, lookup, { resolveRelativeTo: repoDir });\n\n const config = interpolate(merged);\n\n const instructions = await loadInstructions(repoDir, manifest.instructions);\n\n const excluded = status.integrations.filter((i) => !i.configured).map((i) => i.name);\n const activeServers = Object.fromEntries(\n Object.entries(manifest.mcpServers)\n .filter(([server]) => !excluded.includes(server))\n .map(([server, { optional: _optional, ...decl }]) => [server, decl as McpServerConfig]),\n );\n const mcpServers = interpolate(activeServers);\n const allowedTools = manifest.permissions.allowedTools.filter(\n (tool) => !excluded.some((server) => tool.startsWith(`mcp__${server}__`)),\n );\n\n const logger = createLogger(name);\n // Bookkeeping must never break a turn: a failed usage write is logged, not thrown.\n const recordUsage = usage\n ? async (turn: TurnUsage): Promise<void> => {\n try {\n await usage.record(name, turn);\n } catch (err) {\n logger.warn('Failed to record AI usage', { error: String(err) });\n }\n }\n : undefined;\n\n return {\n appName: name,\n dataDir,\n config,\n logger,\n state: new JsonFileStore(join(dataDir, 'state.json')),\n ai: scopeProvider(provider, {\n mcpServers,\n allowedTools,\n agents: await loadAgents(repoDir, manifest.agents),\n skillDirs: manifest.skills.map((dir) => resolve(repoDir, dir)),\n ...(instructions !== undefined && { instructions }),\n cwd: dataDir,\n ...(recordUsage !== undefined && { recordUsage }),\n // Enforcement rides with accounting: without a recorder nothing is\n // metered, so there is no spend to compare a cap against.\n ...(usage !== undefined && {\n checkBudget: (): Promise<void> => assertWithinBudget(store, usage, name),\n }),\n ...(!status.ready && {\n unreadyError: `App \"${name}\" is not configured: missing ${status.missing.join(', ')}. Set the missing keys in the dashboard settings or ${store.appConfigPath(name)}.`,\n }),\n }),\n };\n}\n","import type { AppContext, AppManifest } from '@alashi/apps-sdk';\nimport { Cron } from 'croner';\nimport type { JobRun, Store } from './store.js';\n\nexport interface SchedulableApp {\n manifest: AppManifest;\n ctx: AppContext;\n jobHandlers?: Record<string, (ctx: AppContext) => Promise<void>>;\n}\n\nexport interface JobStatus {\n name: string;\n schedule: string;\n description?: string;\n /** False when the cron expression failed to parse (job never fires). */\n valid: boolean;\n running: boolean;\n nextRun?: string;\n lastRun?: string;\n recentRuns: JobRun[];\n}\n\nconst RUN_LOG_CAP = 50;\nconst RECENT_RUNS = 5;\n\nfunction nextFire(schedule: string, after?: Date): Date | null {\n // A Cron without a callback only computes times; it never schedules timers.\n return new Cron(schedule).nextRun(after ?? undefined);\n}\n\n/**\n * Runs manifest-declared jobs on their cron schedules. One instance per host\n * process; start() schedules every installed app's jobs, stop() cancels them.\n * A job missed while the host was down runs once at startup (\"catchup\"), but\n * only if it has run before — a fresh install waits for its first slot.\n */\nexport class Scheduler {\n private crons: Cron[] = [];\n private runningJobs = new Set<string>();\n private started = false;\n /** Serializes jobs.json read-modify-write across concurrently finishing jobs. */\n private stateQueue: Promise<unknown> = Promise.resolve();\n\n constructor(\n private readonly store: Store,\n private readonly getApp: (name: string) => Promise<SchedulableApp | undefined>,\n ) {}\n\n async start(): Promise<void> {\n if (this.started) return;\n this.started = true;\n for (const record of await this.store.listInstalled()) {\n let app: SchedulableApp | undefined;\n try {\n app = await this.getApp(record.name);\n } catch (err) {\n console.error(`[scheduler] skipping ${record.name}: failed to load (${String(err)})`);\n continue;\n }\n if (!app || app.manifest.jobs.length === 0) continue;\n const state = await this.store.readJobState(record.name);\n for (const job of app.manifest.jobs) {\n let cron: Cron;\n try {\n cron = new Cron(job.schedule, { paused: true }, () => {\n void this.run(record.name, job.name, 'schedule');\n });\n } catch (err) {\n app.ctx.logger.error(`Job \"${job.name}\" has an invalid schedule`, {\n schedule: job.schedule,\n error: String(err),\n });\n continue;\n }\n const last = state.lastRun[job.name];\n if (last) {\n const missed = nextFire(job.schedule, new Date(last));\n if (missed && missed.getTime() <= Date.now()) {\n void this.run(record.name, job.name, 'catchup');\n }\n }\n cron.resume();\n this.crons.push(cron);\n }\n }\n }\n\n stop(): void {\n for (const cron of this.crons) cron.stop();\n this.crons = [];\n this.started = false;\n }\n\n /**\n * Runs one job immediately. Returns the recorded run, 'skipped' when that\n * job is already mid-run, or undefined when the app/job doesn't exist.\n */\n async run(\n appName: string,\n jobName: string,\n trigger: JobRun['trigger'] = 'manual',\n ): Promise<JobRun | 'skipped' | undefined> {\n const app = await this.getApp(appName).catch(() => undefined);\n const handler = app?.jobHandlers?.[jobName];\n if (!app || !handler || !app.manifest.jobs.some((j) => j.name === jobName)) return undefined;\n\n const key = `${appName}/${jobName}`;\n if (this.runningJobs.has(key)) return 'skipped';\n this.runningJobs.add(key);\n\n const startedAt = new Date().toISOString();\n let ok = true;\n let error: string | undefined;\n try {\n await handler(app.ctx);\n } catch (err) {\n ok = false;\n error = String(err);\n app.ctx.logger.error(`Job \"${jobName}\" failed`, { error });\n } finally {\n this.runningJobs.delete(key);\n }\n\n const run: JobRun = {\n job: jobName,\n trigger,\n startedAt,\n finishedAt: new Date().toISOString(),\n ok,\n ...(error !== undefined && { error }),\n };\n await this.recordRun(appName, run);\n return run;\n }\n\n /** Job statuses for an app, or undefined when the app isn't installed. */\n async status(appName: string): Promise<JobStatus[] | undefined> {\n const app = await this.getApp(appName).catch(() => undefined);\n if (!app) return undefined;\n const state = await this.store.readJobState(appName);\n return app.manifest.jobs.map((job) => {\n let valid = true;\n let nextRun: string | undefined;\n try {\n nextRun = nextFire(job.schedule)?.toISOString();\n } catch {\n valid = false;\n }\n return {\n name: job.name,\n schedule: job.schedule,\n ...(job.description !== undefined && { description: job.description }),\n valid,\n running: this.runningJobs.has(`${appName}/${job.name}`),\n ...(nextRun !== undefined && { nextRun }),\n ...(state.lastRun[job.name] !== undefined && { lastRun: state.lastRun[job.name] }),\n recentRuns: state.runs\n .filter((r) => r.job === job.name)\n .slice(-RECENT_RUNS)\n .reverse(),\n };\n });\n }\n\n private recordRun(appName: string, run: JobRun): Promise<void> {\n const update = this.stateQueue.then(async () => {\n const state = await this.store.readJobState(appName);\n state.lastRun[run.job] = run.startedAt;\n state.runs.push(run);\n if (state.runs.length > RUN_LOG_CAP) state.runs.splice(0, state.runs.length - RUN_LOG_CAP);\n await this.store.writeJobState(appName, state);\n });\n this.stateQueue = update.catch(() => undefined);\n return update;\n }\n}\n","/**\n * Self-contained per-app settings page. Config data comes from\n * GET /api/apps/:name/config at runtime; saving PUTs only changed keys.\n * The read-only \"App definition\" section (instructions, agents, skills,\n * allowed tools, MCP servers, jobs) comes from GET /api/apps/:name/manifest.\n * The app name is injected via a JSON-encoded script constant, never into\n * markup, so no escaping subtleties apply.\n */\nexport function settingsPage(name: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Settings</title>\n<link rel=\"stylesheet\" href=\"/assets/v1/system.css\">\n<style>\n .card { margin-bottom: 16px; }\n #status { margin-left: 12px; color: var(--muted); }\n #manifest-cards { margin-top: 32px; }\n .ro-pre { white-space: pre-wrap; word-break: break-word; margin: 8px 0 0; }\n .ro-item { margin-bottom: 12px; }\n .ro-item:last-child { margin-bottom: 0; }\n details summary { cursor: pointer; }\n #budget-card input { max-width: 180px; }\n #budget-status { margin-left: 12px; color: var(--muted); }\n</style>\n</head>\n<body>\n<header class=\"topbar\"><a href=\"/\">alashi</a> <span>/</span> <strong id=\"title\"></strong> <span>settings</span></header>\n<main class=\"narrow\">\n <div id=\"banner\" class=\"banner\" hidden></div>\n <div id=\"budget-card\"></div>\n <div id=\"usage-card\"></div>\n <div id=\"cards\"></div>\n <button id=\"save\" class=\"primary\">Save changes</button><span id=\"status\"></span>\n <div id=\"manifest-cards\"></div>\n</main>\n<script>\nconst APP = ${JSON.stringify(name)};\ndocument.getElementById('title').textContent = APP;\n\nconst state = { data: null, dirty: new Map() };\n\nfunction el(tag, attrs = {}, children = []) {\n const node = document.createElement(tag);\n for (const [k, v] of Object.entries(attrs)) {\n if (k === 'text') node.textContent = v;\n else if (k.startsWith('on')) node.addEventListener(k.slice(2), v);\n else node.setAttribute(k, v);\n }\n for (const child of children) node.append(child);\n return node;\n}\n\nfunction parseInput(raw) {\n if (raw === '') return null; // empty clears the user-set value\n try { const v = JSON.parse(raw); if (typeof v !== 'string') return v; } catch {}\n return raw;\n}\n\nfunction displayValue(value) {\n return typeof value === 'string' ? value : value === undefined ? '' : JSON.stringify(value);\n}\n\nconst ACRONYMS = new Set(['url', 'uri', 'api', 'id', 'ip', 'db', 'ssl', 'tls', 'ttl', 'http', 'https']);\n\nfunction labelFor(key, decl) {\n if (decl.label) return decl.label;\n const words = key\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .toLowerCase()\n .split(' ');\n return words\n .map((w, i) => (ACRONYMS.has(w) ? w.toUpperCase() : i === 0 ? w.charAt(0).toUpperCase() + w.slice(1) : w))\n .join(' ');\n}\n\nfunction field(key, decl, data) {\n const secret = decl.secret === true;\n const isBool = !secret &&\n (typeof decl.default === 'boolean' || typeof data.values[key] === 'boolean');\n if (isBool) {\n const input = el('input', {\n type: 'checkbox',\n onchange: () => state.dirty.set(key, { secret: false, input }),\n });\n input.checked = data.values[key] === true;\n const labelChildren = [input, document.createTextNode(labelFor(key, decl))];\n if (decl.required) labelChildren.push(el('span', { class: 'req', text: ' *' }));\n const parts = [el('label', { class: 'check' }, labelChildren)];\n if (decl.description) parts.push(el('div', { class: 'desc', text: decl.description }));\n return el('div', { class: 'field' }, parts);\n }\n const isSet = secret ? data.secretsSet.includes(key) : data.values[key] !== undefined;\n const input = el('input', {\n type: secret ? 'password' : 'text',\n value: secret ? '' : displayValue(data.values[key]),\n placeholder: secret && isSet ? '(set — type to replace)' : '',\n oninput: () => state.dirty.set(key, { secret, input }),\n });\n const labelChildren = [document.createTextNode(labelFor(key, decl))];\n if (decl.required) labelChildren.push(el('span', { class: 'req', text: ' *' }));\n const parts = [el('label', {}, labelChildren)];\n if (decl.description) parts.push(el('div', { class: 'desc', text: decl.description }));\n parts.push(input);\n return el('div', { class: 'field' }, parts);\n}\n\nfunction card(title, keys, data, configured) {\n const chip = configured === undefined\n ? ''\n : el('span', { class: 'chip ' + (configured ? 'on' : 'off'),\n text: configured ? 'configured' : 'not configured' });\n return el('div', { class: 'card' }, [\n el('h2', {}, chip === '' ? [document.createTextNode(title)] : [document.createTextNode(title), chip]),\n el('div', { class: 'body' }, keys.map((key) => field(key, data.schema[key] ?? {}, data))),\n ]);\n}\n\nfunction render(data) {\n state.data = data;\n state.dirty.clear();\n const banner = document.getElementById('banner');\n banner.hidden = false;\n if (data.ready) {\n banner.className = 'banner ok';\n banner.textContent = 'App is configured and ready.';\n } else {\n banner.className = 'banner warn';\n banner.textContent = 'Needs setup — missing: ' + data.missing.join(', ');\n }\n\n const cards = document.getElementById('cards');\n cards.replaceChildren();\n const integrationKeys = new Set(data.integrations.flatMap((i) => i.configKeys));\n\n // Non-integration keys, sectioned by their declared group (\"General\" first).\n const sections = new Map();\n for (const key of Object.keys(data.schema)) {\n if (integrationKeys.has(key)) continue;\n const title = data.schema[key].group ?? 'General';\n if (!sections.has(title)) sections.set(title, []);\n sections.get(title).push(key);\n }\n if (sections.has('General')) cards.append(card('General', sections.get('General'), data, undefined));\n for (const [title, keys] of sections) {\n if (title !== 'General') cards.append(card(title, keys, data, undefined));\n }\n\n // Integration cards, titled by the keys' shared group when declared.\n for (const integration of data.integrations) {\n if (integration.configKeys.length === 0) continue;\n const groups = new Set(integration.configKeys.map((k) => data.schema[k]?.group).filter(Boolean));\n const title = groups.size === 1 ? [...groups][0] : integration.name;\n cards.append(card(\n title + (integration.optional ? ' (optional integration)' : ''),\n integration.configKeys, data, integration.configured,\n ));\n }\n if (cards.childElementCount === 0) {\n cards.append(el('p', { text: 'This app declares no configurable settings.' }));\n }\n}\n\nfunction usd(value) {\n return '$' + value.toFixed(value >= 1 ? 2 : 4);\n}\n\n/**\n * Daily spend cap. A host setting, not an app config key: it lives in the\n * host's own config.json and is enforced in createSession, so an app cannot\n * raise its own ceiling through its manifest defaults or its config layer.\n */\nfunction renderBudget(b, message) {\n const root = document.getElementById('budget-card');\n root.replaceChildren();\n const capped = typeof b.dailyUsd === 'number';\n\n const input = el('input', {\n type: 'number', min: '0', step: '0.01', placeholder: 'no cap',\n value: capped ? String(b.dailyUsd) : '',\n });\n const status = el('span', { id: 'budget-status', text: message || '' });\n\n const save = el('button', {\n class: 'primary',\n text: 'Save cap',\n onclick: async () => {\n const raw = input.value.trim();\n const dailyUsd = raw === '' ? null : Number(raw);\n if (dailyUsd !== null && !(dailyUsd >= 0)) {\n status.textContent = 'Enter a dollar amount, or leave empty for no cap.';\n return;\n }\n status.textContent = 'Saving…';\n const res = await fetch('/api/apps/' + APP + '/budget', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ dailyUsd }),\n });\n if (!res.ok) {\n status.textContent = 'Save failed (' + res.status + ')';\n return;\n }\n renderBudget(await res.json(), dailyUsd === null ? 'Cap removed.' : 'Cap saved.');\n },\n });\n\n const capNote = capped\n ? usd(b.remainingUsd) + ' left of ' + usd(b.dailyUsd)\n : 'no cap set, spend is unlimited';\n // No ledger at all is an absence, not $0.0000: the host meters only sessions\n // opened through ctx.ai, so an app that tracks its own costs elsewhere can\n // report real spend while the host has recorded nothing for it.\n const spent = b.recorded === false\n ? 'No AI spend recorded for this app yet (' + b.date + ', UTC) — ' + capNote\n + '. The host meters only sessions the app opens through ctx.ai, so the '\n + \"app's own cost display may differ.\"\n : 'Spent today (' + b.date + ', UTC): ' + usd(b.spentUsd) + ' — ' + capNote;\n const body = [\n el('div', { class: 'desc', text:\n 'Enforced by the host: once the spend for the UTC day reaches the cap, new AI sessions for '\n + 'this app are refused, and the counter resets at 00:00 UTC. This is a host setting — the '\n + 'app cannot read or raise it through its own config.' }),\n el('div', { class: 'field' }, [\n el('label', { text: 'Daily spend cap (USD)' }),\n input,\n ]),\n el('div', { class: 'desc', text: spent }),\n el('div', { style: 'margin-top:12px' }, [save, status]),\n ];\n if (b.overBudget) {\n body.unshift(el('div', { class: 'banner warn', text:\n 'Over budget — new AI sessions are refused. ' + usd(b.spentUsd) + ' spent of the '\n + usd(b.dailyUsd) + ' daily cap for ' + b.date + ' (UTC). Raise or clear the cap below, '\n + 'or wait for the reset at 00:00 UTC.' }));\n }\n\n root.append(el('div', { class: 'card' }, [\n el('h2', { text: 'Spend cap' }),\n el('div', { class: 'body' }, body),\n ]));\n}\n\nfunction tokens(n) {\n return (n ?? 0).toLocaleString('en-US');\n}\n\nfunction usageTable(headers, rows) {\n return el('table', {}, [\n el('thead', {}, [el('tr', {}, headers.map((h, i) =>\n el('th', i === 0 ? { text: h } : { text: h, style: 'text-align:right' })))]),\n el('tbody', {}, rows.map((cells) => el('tr', {}, cells.map((c, i) =>\n el('td', i === 0 ? { text: c } : { text: c, style: 'text-align:right' }))))),\n ]);\n}\n\nfunction monthTokens(models) {\n let input = 0, output = 0;\n for (const t of Object.values(models ?? {})) {\n input += t.inputTokens ?? 0;\n output += t.outputTokens ?? 0;\n }\n return { input, output };\n}\n\n/** Read-only token/cost breakdown from GET /api/apps/:name/usage. */\nfunction renderUsage(u) {\n const root = document.getElementById('usage-card');\n root.replaceChildren();\n const body = [];\n if (!u.recorded) {\n body.push(el('div', { class: 'desc', text:\n 'No AI usage recorded for this app yet. The host meters only sessions the app opens '\n + 'through ctx.ai, so usage the app tracks on its own does not appear here.' }));\n } else {\n body.push(el('div', { class: 'desc', text:\n 'Today (' + u.date + ', UTC): ' + usd(u.todayCostUsd) + ' across ' + u.todayTurns\n + ' turn' + (u.todayTurns === 1 ? '' : 's') + '.' }));\n const models = Object.keys(u.models).sort();\n if (models.length > 0) {\n body.push(el('h3', { text: 'By model (last ' + u.retentionDays + ' days)' }));\n body.push(usageTable(\n ['Model', 'Input', 'Output', 'Cache read', 'Cache write', 'Cost'],\n models.map((m) => {\n const t = u.models[m];\n return [m, tokens(t.inputTokens), tokens(t.outputTokens),\n tokens(t.cacheReadInputTokens), tokens(t.cacheCreationInputTokens), usd(t.costUsd)];\n }),\n ));\n }\n if (u.months.length > 0) {\n body.push(el('h3', { text: 'By month' }));\n body.push(usageTable(\n ['Month', 'Turns', 'Input', 'Output', 'Cost'],\n [...u.months].reverse().map((m) => {\n const t = monthTokens(m.models);\n return [m.month, String(m.turns), tokens(t.input), tokens(t.output), usd(m.costUsd)];\n }),\n ));\n }\n }\n root.append(el('div', { class: 'card' }, [\n el('h2', { text: 'AI usage' }),\n el('div', { class: 'body' }, body),\n ]));\n}\n\nfunction roCard(title, children) {\n return el('div', { class: 'card' }, [\n el('h2', { text: title }),\n el('div', { class: 'body' }, children),\n ]);\n}\n\nfunction renderManifest(m) {\n const root = document.getElementById('manifest-cards');\n root.replaceChildren();\n const cards = [];\n\n const about = [el('div', { class: 'desc', text: 'Version ' + m.version + (m.model ? ' · model: ' + m.model : '') })];\n if (m.description) about.unshift(el('div', { text: m.description }));\n cards.push(roCard('About', about));\n\n if (m.instructions) {\n cards.push(roCard('Instructions', [el('pre', { class: 'ro-pre', text: m.instructions })]));\n }\n\n const agentNames = Object.keys(m.agents);\n if (agentNames.length > 0) {\n cards.push(roCard('Agents', agentNames.map((name) => {\n const a = m.agents[name];\n const meta = [a.model && 'model: ' + a.model, a.tools && 'tools: ' + a.tools.join(', ')]\n .filter(Boolean).join(' · ');\n const body = meta ? [el('div', { class: 'desc', text: meta })] : [];\n body.push(el('pre', { class: 'ro-pre', text: a.prompt }));\n return el('details', { class: 'ro-item' }, [\n el('summary', {}, [el('strong', { text: name }), document.createTextNode(' — ' + a.description)]),\n ...body,\n ]);\n })));\n }\n\n if (m.skills.length > 0) {\n cards.push(roCard('Skills', m.skills.map((s) => el('div', { class: 'ro-item' }, [\n el('code', { text: s.dir + '/' }),\n el('div', { class: 'desc', text: s.entries.length ? s.entries.join(', ') : '(directory not found or empty)' }),\n ]))));\n }\n\n if (m.mcpServers.length > 0) {\n cards.push(roCard('MCP servers', m.mcpServers.map((s) => el('div', { class: 'ro-item' }, [\n el('strong', { text: s.name }),\n document.createTextNode(' (' + s.type + (s.optional ? ', optional' : '') + ') '),\n el('span', { class: 'chip ' + (s.configured ? 'on' : 'off'),\n text: s.configured ? 'configured' : 'not configured' }),\n el('div', { class: 'desc', text: s.type === 'stdio' ? s.command : s.url }),\n ]))));\n }\n\n if (m.allowedTools.length > 0) {\n cards.push(roCard('Allowed tools', m.allowedTools.map((t) =>\n el('div', {}, [el('code', { text: t })]))));\n }\n\n if (m.jobs.length > 0) {\n cards.push(roCard('Jobs', m.jobs.map((j) => el('div', { class: 'ro-item' }, [\n el('strong', { text: j.name }),\n el('code', { text: ' ' + j.schedule }),\n ...(j.description ? [el('div', { class: 'desc', text: j.description })] : []),\n ]))));\n }\n\n root.append(\n el('h1', { text: 'App definition' }),\n el('p', { class: 'subtitle', text: 'Declared by the app; read-only.' }),\n ...cards,\n );\n}\n\nasync function load() {\n const [res, manRes, budgetRes, usageRes] = await Promise.all([\n fetch('/api/apps/' + APP + '/config'),\n fetch('/api/apps/' + APP + '/manifest'),\n fetch('/api/apps/' + APP + '/budget'),\n fetch('/api/apps/' + APP + '/usage'),\n ]);\n // Host bookkeeping: the cap and usage render even when the app itself fails to load.\n if (budgetRes.ok) renderBudget(await budgetRes.json());\n if (usageRes.ok) renderUsage(await usageRes.json());\n if (!res.ok) {\n document.getElementById('banner').hidden = false;\n document.getElementById('banner').className = 'banner warn';\n document.getElementById('banner').textContent = 'Failed to load config (' + res.status + ')';\n return;\n }\n render(await res.json());\n if (manRes.ok) renderManifest(await manRes.json());\n}\n\ndocument.getElementById('save').addEventListener('click', async () => {\n const body = {};\n for (const [key, { secret, input }] of state.dirty) {\n if (secret && input.value === '') continue; // untouched secret: keep as-is\n body[key] = input.type === 'checkbox' ? input.checked : parseInput(input.value);\n }\n if (Object.keys(body).length === 0) return;\n const status = document.getElementById('status');\n status.textContent = 'Saving…';\n const res = await fetch('/api/apps/' + APP + '/config', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n });\n status.textContent = res.ok ? 'Saved — app restarted.' : 'Save failed (' + res.status + ')';\n if (res.ok) await load();\n});\n\nload();\n</script>\n</body>\n</html>\n`;\n}\n","import { randomUUID } from 'node:crypto';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { extname, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n type AIProvider,\n type AISession,\n APP_NAME_PATTERN,\n type AppContext,\n type AppManifest,\n type FetchHandler,\n} from '@alashi/apps-sdk';\nimport { getConnInfo } from '@hono/node-server/conninfo';\nimport { Hono } from 'hono';\nimport { streamSSE } from 'hono/streaming';\nimport {\n AuthService,\n createAuthMiddleware,\n type RemoteAddressResolver,\n registerAuthRoutes,\n} from './auth.js';\nimport {\n appBudget,\n assertWithinBudget,\n BudgetExceededError,\n budgetStatus,\n budgetStatusFor,\n setAppBudget,\n} from './budget.js';\nimport { loadApp } from './loader.js';\nimport { buildAppContext, loadAgents, loadInstructions, resolveAppConfig } from './runtime.js';\nimport { Scheduler } from './scheduler.js';\nimport { settingsPage } from './settings-page.js';\nimport type { ChatState, Conversation, Store } from './store.js';\nimport {\n combineSummaries,\n formatUsd,\n USAGE_RETENTION_DAYS,\n UsageRecorder,\n utcDay,\n} from './usage.js';\n\ninterface RunningApp {\n manifest: AppManifest;\n ctx: AppContext;\n handler?: FetchHandler;\n jobHandlers?: Record<string, (ctx: AppContext) => Promise<void>>;\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.ico': 'image/x-icon',\n '.txt': 'text/plain; charset=utf-8',\n '.woff2': 'font/woff2',\n};\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;');\n}\n\n/**\n * What the dashboard's Today column actually measures. The host ledger\n * (`apps/<name>/usage.json`) only ever grows when a session opened through\n * `ctx.ai` reports `costUsd`, so an app that spends through its own bookkeeping\n * can legitimately show a different number in its own UI. Saying so in the\n * header keeps a smaller host figure from reading as a bug.\n */\nconst TODAY_COLUMN_TITLE =\n 'AI spend today (UTC) as metered by the host: only sessions opened through an ' +\n \"app's ctx.ai are counted, so an app's own in-app cost display may differ.\";\n\n/** Tooltip for the em dash that stands in for \"no host ledger at all\". */\nconst NO_SPEND_RECORDED_TITLE =\n 'No AI spend recorded for this app yet — the host meters only sessions an app ' +\n 'opens through ctx.ai';\n\n/** The same absence, said for the whole table rather than one app. */\nconst NO_SPEND_RECORDED_ANY_TITLE =\n 'No AI spend recorded for any installed app yet — the host meters only sessions ' +\n 'an app opens through ctx.ai';\n\n/** Host-rendered failure page — apps never need to ship their own error UI. */\nfunction appErrorPage(name: string, err: unknown): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${escapeHtml(name)} — failed to load</title>\n<link rel=\"stylesheet\" href=\"/assets/v1/system.css\">\n</head>\n<body>\n<header class=\"topbar\"><strong>alashi</strong></header>\n<main class=\"narrow\">\n<h1>${escapeHtml(name)} failed to load</h1>\n<div class=\"panel\">\n<p>${escapeHtml(String(err))}</p>\n<p class=\"muted\"><a href=\"/\">Back to apps</a></p>\n</div>\n</main>\n</body>\n</html>\n`;\n}\n\nexport interface AppServer {\n /** The Hono app — pass its fetch to a Node server or call request() in tests. */\n app: Hono;\n /** Cron scheduler for manifest-declared jobs; call start() to begin firing. */\n scheduler: Scheduler;\n /** Host auth: the CLI uses it to issue the boot login token and mint bearer tokens. */\n auth: AuthService;\n /** Stops the scheduler and closes all warm AI sessions. */\n close(): Promise<void>;\n}\n\nexport interface ServerOptions {\n /** Host auth service; created internally when not supplied. */\n auth?: AuthService;\n /**\n * How the auth middleware learns the peer address. Defaults to\n * @hono/node-server's conninfo; in-process requests (tests) have no socket\n * and resolve to undefined, which the console heuristic treats as local.\n */\n getRemoteAddress?: RemoteAddressResolver;\n}\n\n/**\n * The host HTTP surface (all routes sit behind the auth policy middleware —\n * see auth.ts: public/user/owner levels, config-driven via config.auth):\n * GET /login sign-in form; ?token= redeems a printed login URL\n * POST /login { token } form -> session cookie\n * GET /logout destroy the session\n * GET /api/me the request's resolved identity, or null\n * GET / dashboard of installed apps\n * GET /settings/:name per-app settings page (config form)\n * GET /api/apps installed apps as JSON, each with todayCostUsd,\n * `recorded` (false when the host has never metered\n * the app, so todayCostUsd is an absence rather than\n * a measured zero), and its daily spend cap /\n * over-budget state\n * GET /api/usage host-wide usage: per-app summaries (cost, turns,\n * per-model tokens, daily series, monthly rollups)\n * plus combined totals across apps\n * GET /api/apps/:name/usage recorded AI spend: today's total, a daily series,\n * per-model token totals, monthly rollups,\n * `recorded`, and the budget status\n * GET /api/apps/:name/budget today's spend against the app's daily cap,\n * plus `recorded`\n * PUT /api/apps/:name/budget { dailyUsd: number | null } -> set or clear the cap\n * (a host setting; apps cannot set their own)\n * POST /api/apps/:name/chat { message, conversationId? } -> SSE stream of AgentEvents\n * (omit conversationId to start a new conversation; the\n * id is echoed in the x-conversation-id response header;\n * 402 when the app is over its daily spend cap)\n * GET /api/apps/:name/chat/conversations conversations, most recent first\n * POST /api/apps/:name/chat/conversations { sessionId?, title? } -> create a conversation,\n * optionally adopting an existing provider session (e.g. an\n * app-run investigation) so chat continues where it left off\n * GET /api/apps/:name/chat/:conversationId/history past turns from the provider transcript\n * GET /api/apps/:name/jobs manifest-declared jobs with schedule/status\n * POST /api/apps/:name/jobs/:job/run trigger a job now (409 while already running)\n * GET /api/apps/:name/manifest read-only app definition: instructions, agents,\n * skills (with dir contents), allowed tools,\n * MCP servers (with configured state), jobs, model\n * GET /api/apps/:name/config declared config schema + merged values\n * (secrets masked) + readiness/integrations\n * PUT /api/apps/:name/config merge keys into the per-app config layer\n * (null deletes a key) and restart the app\n * ALL /app/:name/api/* dispatched to the app's fetch handler (prefix stripped)\n * ALL /app/:name/* a matching static file from the app's ui dir wins,\n * otherwise the app's fetch handler (/app/:name stripped),\n * otherwise 404\n */\nexport function createServer(\n store: Store,\n provider: AIProvider,\n options: ServerOptions = {},\n): AppServer {\n const auth = options.auth ?? new AuthService(store);\n const apps = new Map<string, Promise<RunningApp>>();\n const sessions = new Map<string, Promise<AISession>>();\n // One recorder for the process: every app context meters its sessions\n // through it, so chat turns, cron jobs, and app-opened sessions all count.\n const usage = new UsageRecorder(store);\n\n async function startApp(name: string): Promise<RunningApp> {\n const { definition, manifest } = await loadApp(store.appRepoDir(name));\n const ctx = await buildAppContext(store, manifest, provider, usage);\n const handler = definition.routes ? await definition.routes(ctx) : undefined;\n return { manifest, ctx, handler, jobHandlers: definition.jobHandlers };\n }\n\n /** Loads an installed app once per process; undefined when not installed. */\n async function getApp(name: string): Promise<RunningApp | undefined> {\n if (!APP_NAME_PATTERN.test(name)) return undefined;\n if (!(await store.readAppRecord(name))) return undefined;\n let running = apps.get(name);\n if (!running) {\n running = startApp(name);\n running.catch(() => apps.delete(name));\n apps.set(name, running);\n }\n return running;\n }\n\n /** chat.json per app, loaded once and mutated in place so turns share one view. */\n const chatStates = new Map<string, Promise<ChatState>>();\n\n function getChatState(name: string): Promise<ChatState> {\n let state = chatStates.get(name);\n if (!state) {\n state = store.readChatState(name);\n state.catch(() => chatStates.delete(name));\n chatStates.set(name, state);\n }\n return state;\n }\n\n function getSession(name: string, running: RunningApp, convo: Conversation): Promise<AISession> {\n const key = `${name}/${convo.id}`;\n let session = sessions.get(key);\n if (!session) {\n const model = running.ctx.config.model;\n session = running.ctx.ai.createSession({\n ...(typeof model === 'string' && { model }),\n ...(convo.sessionId && { resume: convo.sessionId }),\n });\n session.catch(() => sessions.delete(key));\n sessions.set(key, session);\n }\n return session;\n }\n\n const scheduler = new Scheduler(store, getApp);\n\n /** Rebuilds the request with `prefix` stripped and hands it to the app's handler. */\n function dispatch(\n handler: FetchHandler,\n raw: Request,\n prefix: string,\n ): Response | Promise<Response> {\n const url = new URL(raw.url);\n url.pathname = url.pathname.slice(prefix.length) || '/';\n const request = new Request(url, {\n method: raw.method,\n headers: raw.headers,\n body: raw.body,\n ...(raw.body ? { duplex: 'half' } : {}),\n } as RequestInit);\n return handler(request);\n }\n\n /** A file from the app's ui dir, or undefined when there is no match. */\n async function serveStatic(\n name: string,\n manifest: AppManifest,\n reqPath: string,\n ): Promise<Response | undefined> {\n if (!manifest.ui) return undefined;\n const uiDir = resolve(store.appRepoDir(name), manifest.ui);\n let rest: string;\n try {\n rest = decodeURIComponent(reqPath.slice(`/app/${name}/`.length));\n } catch {\n return undefined;\n }\n if (rest === '' || rest.endsWith('/')) rest += 'index.html';\n const filePath = resolve(uiDir, rest);\n if (filePath !== uiDir && !filePath.startsWith(uiDir + sep)) return undefined;\n try {\n const body = await readFile(filePath);\n const type = CONTENT_TYPES[extname(filePath)] ?? 'application/octet-stream';\n return new Response(new Uint8Array(body), {\n status: 200,\n headers: { 'content-type': type },\n });\n } catch {\n return undefined;\n }\n }\n\n const app = new Hono();\n\n app.onError((err, c) => c.json({ error: String(err) }, 500));\n\n // The front door: every route below is behind the auth policy. Must be\n // registered before any route so no handler is reachable without a verdict.\n const getRemoteAddress: RemoteAddressResolver =\n options.getRemoteAddress ??\n ((c) => {\n try {\n return getConnInfo(c).remote.address;\n } catch {\n // In-process requests (tests, self-calls) have no socket.\n return undefined;\n }\n });\n app.use('*', createAuthMiddleware(store, auth, { getRemoteAddress }));\n registerAuthRoutes(app, auth);\n\n // System UI assets (design system, later vendored libs). Versioned paths:\n // breaking changes ship as /assets/v2/, existing apps keep working.\n const assetsDir = fileURLToPath(new URL('../assets', import.meta.url));\n\n app.get('/assets/*', async (c) => {\n let rest: string;\n try {\n rest = decodeURIComponent(c.req.path.slice('/assets/'.length));\n } catch {\n return c.json({ error: 'Not found' }, 404);\n }\n const filePath = resolve(assetsDir, rest);\n if (!filePath.startsWith(assetsDir + sep)) return c.json({ error: 'Not found' }, 404);\n try {\n const body = await readFile(filePath);\n const type = CONTENT_TYPES[extname(filePath)] ?? 'application/octet-stream';\n return new Response(new Uint8Array(body), {\n status: 200,\n headers: { 'content-type': type, 'cache-control': 'public, max-age=300' },\n });\n } catch {\n return c.json({ error: 'Not found' }, 404);\n }\n });\n\n /** Readiness for the dashboard badge; loadError carries the failure reason. */\n async function readiness(\n name: string,\n ): Promise<{ ready: boolean } | { loadError: string } | undefined> {\n try {\n const running = await getApp(name);\n if (!running) return undefined;\n return (await resolveAppConfig(store, running.manifest)).status;\n } catch (err) {\n return { loadError: String(err) };\n }\n }\n\n app.get('/', async (c) => {\n const records = await store.listInstalled();\n const cells = await Promise.all(\n records.map(async (r) => {\n const name = escapeHtml(r.name);\n const status = await readiness(r.name);\n const badge = !status\n ? '<span class=\"badge err\">failed to load</span>'\n : 'loadError' in status\n ? `<span class=\"badge err\" title=\"${escapeHtml(status.loadError)}\">failed to load</span>`\n : status.ready\n ? '<span class=\"badge ok\">ready</span>'\n : `<a href=\"/settings/${name}\"><span class=\"badge err\">needs setup</span></a>`;\n const budget = await budgetStatus(store, usage, r.name);\n // Over budget is a distinct, actionable state: the app still loads and\n // is still \"ready\", it just cannot open a session until the day rolls.\n const overBadge = budget.overBudget\n ? ` <a href=\"/settings/${name}\" title=\"Daily AI spend cap reached — new sessions are refused until 00:00 UTC\"><span class=\"badge err\">over budget</span></a>`\n : '';\n const cap =\n budget.dailyUsd === undefined\n ? ''\n : ` <span class=\"muted\" title=\"Daily spend cap\">/ ${formatUsd(budget.dailyUsd)}</span>`;\n // \"Never metered\" and \"metered, and today is zero\" are different facts.\n // Only the second one is a number; the first is an em dash that says so.\n const today = budget.recorded\n ? formatUsd(budget.spentUsd)\n : `<span title=\"${escapeHtml(NO_SPEND_RECORDED_TITLE)}\">—</span>`;\n const row = `<tr>\n <td><a href=\"/app/${name}/\">${name}</a></td>\n <td class=\"muted\">v${escapeHtml(r.version)}</td>\n <td class=\"muted\">${escapeHtml(r.source.type)}</td>\n <td>${badge}${overBadge}</td>\n <td class=\"muted\" style=\"text-align:right\">${today}${cap}</td>\n <td style=\"text-align:right\"><a href=\"/settings/${name}\">settings</a></td>\n</tr>`;\n return { row, todayCostUsd: budget.spentUsd, recorded: budget.recorded };\n }),\n );\n const rows = cells.map((cell) => cell.row).join('\\n');\n const recorded = cells.filter((cell) => cell.recorded);\n // Sum only the apps the host actually has a ledger for: folding unrecorded\n // apps in as zero would quietly understate a total nobody can verify.\n const totalToday =\n recorded.length === 0\n ? `<span title=\"${escapeHtml(NO_SPEND_RECORDED_ANY_TITLE)}\">—</span>`\n : formatUsd(recorded.reduce((sum, cell) => sum + cell.todayCostUsd, 0));\n const body =\n records.length === 0\n ? '<div class=\"empty\">No apps installed. Run <code>alashi install &lt;name&gt;</code>.</div>'\n : `<table>\n<thead><tr><th>App</th><th>Version</th><th>Source</th><th>Status</th><th style=\"text-align:right\" title=\"${escapeHtml(TODAY_COLUMN_TITLE)}\">Today</th><th></th></tr></thead>\n<tbody>\n${rows}\n</tbody>\n<tfoot><tr><td colspan=\"4\" class=\"muted\" title=\"${escapeHtml(TODAY_COLUMN_TITLE)}\">AI spend today (UTC)</td><td class=\"muted\" style=\"text-align:right\">${totalToday}</td><td></td></tr></tfoot>\n</table>`;\n return c.html(\n `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>alashi</title>\n<link rel=\"stylesheet\" href=\"/assets/v1/system.css\">\n</head>\n<body>\n<header class=\"topbar\"><strong>alashi</strong></header>\n<main class=\"narrow\">\n<h1>Apps</h1>\n<p class=\"subtitle\">Installed apps and their status.</p>\n<div class=\"panel\">\n${body}\n</div>\n</main>\n</body>\n</html>\n`,\n );\n });\n\n app.get('/api/apps', async (c) => {\n const records = await store.listInstalled();\n const apps = await Promise.all(\n records.map(async (record) => {\n const budget = await budgetStatus(store, usage, record.name);\n return {\n ...record,\n todayCostUsd: budget.spentUsd,\n // False means the host has never metered this app: `todayCostUsd` is\n // an absence, not a measured zero. Clients should render it as such.\n recorded: budget.recorded,\n overBudget: budget.overBudget,\n ...(budget.dailyUsd !== undefined && { dailyBudgetUsd: budget.dailyUsd }),\n };\n }),\n );\n return c.json({ apps });\n });\n\n /** Spend and budget are host bookkeeping: readable even when the app fails to load. */\n async function installedApp(name: string): Promise<boolean> {\n return APP_NAME_PATTERN.test(name) && (await store.readAppRecord(name)) !== undefined;\n }\n\n app.get('/api/usage', async (c) => {\n const records = await store.listInstalled();\n const summaries = await Promise.all(\n records.map(async (record) => ({ name: record.name, ...(await usage.summary(record.name)) })),\n );\n return c.json({\n date: utcDay(),\n retentionDays: USAGE_RETENTION_DAYS,\n apps: summaries,\n totals: combineSummaries(summaries),\n });\n });\n\n app.get('/api/apps/:name/usage', async (c) => {\n const name = c.req.param('name');\n if (!(await installedApp(name))) {\n return c.json({ error: `App \"${name}\" is not installed` }, 404);\n }\n const [summary, config] = await Promise.all([usage.summary(name), store.readGlobalConfig()]);\n return c.json({ ...summary, budget: budgetStatusFor(summary, appBudget(config, name)) });\n });\n\n app.get('/api/apps/:name/budget', async (c) => {\n const name = c.req.param('name');\n if (!(await installedApp(name))) {\n return c.json({ error: `App \"${name}\" is not installed` }, 404);\n }\n return c.json(await budgetStatus(store, usage, name));\n });\n\n /**\n * Sets the app's daily spend cap. A host setting, not an app config key: it\n * never touches `apps/<name>/config.json`, so the app cannot raise it via\n * PUT /api/apps/:name/config or by shipping a manifest default.\n */\n app.put('/api/apps/:name/budget', async (c) => {\n const name = c.req.param('name');\n if (!(await installedApp(name))) {\n return c.json({ error: `App \"${name}\" is not installed` }, 404);\n }\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Body must be JSON' }, 400);\n }\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Body must be { \"dailyUsd\": number | null }' }, 400);\n }\n const { dailyUsd } = body as { dailyUsd?: unknown };\n if (dailyUsd !== null && (typeof dailyUsd !== 'number' || !Number.isFinite(dailyUsd))) {\n return c.json({ error: '\"dailyUsd\" must be a number, or null to remove the cap' }, 400);\n }\n if (typeof dailyUsd === 'number' && dailyUsd < 0) {\n return c.json({ error: '\"dailyUsd\" must not be negative' }, 400);\n }\n await setAppBudget(store, name, dailyUsd === null ? undefined : dailyUsd);\n return c.json(await budgetStatus(store, usage, name));\n });\n\n app.get('/settings/:name', async (c) => {\n const name = c.req.param('name');\n if (!APP_NAME_PATTERN.test(name) || !(await store.readAppRecord(name))) {\n return c.json({ error: `App \"${name}\" is not installed` }, 404);\n }\n return c.html(settingsPage(name));\n });\n\n app.get('/api/apps/:name/chat/conversations', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n const { conversations } = await getChatState(name);\n return c.json({\n conversations: [...conversations].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)),\n });\n });\n\n app.post('/api/apps/:name/chat/conversations', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n\n let sessionId: unknown;\n let title: unknown;\n try {\n ({ sessionId, title } = (await c.req.json()) as { sessionId?: unknown; title?: unknown });\n } catch {\n return c.json({ error: 'Body must be JSON' }, 400);\n }\n if (sessionId !== undefined && (typeof sessionId !== 'string' || sessionId.length === 0)) {\n return c.json({ error: '\"sessionId\" must be a non-empty string' }, 400);\n }\n if (title !== undefined && typeof title !== 'string') {\n return c.json({ error: '\"title\" must be a string' }, 400);\n }\n\n const state = await getChatState(name);\n const convo: Conversation = {\n id: randomUUID(),\n title: (title ?? '').slice(0, 80) || 'New conversation',\n updatedAt: new Date().toISOString(),\n ...(sessionId !== undefined && { sessionId }),\n };\n state.conversations.push(convo);\n await store.writeChatState(name, state);\n return c.json({ conversation: convo }, 201);\n });\n\n app.get('/api/apps/:name/chat/:conversationId/history', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n const conversationId = c.req.param('conversationId');\n const { conversations } = await getChatState(name);\n const convo = conversations.find((conv) => conv.id === conversationId);\n if (!convo) return c.json({ error: 'Unknown conversation' }, 404);\n const messages = convo.sessionId ? ((await provider.history?.(convo.sessionId)) ?? []) : [];\n return c.json({ messages });\n });\n\n /** Drops the cached app and its warm sessions so the next request rebuilds with fresh config. */\n function restartApp(name: string): void {\n apps.delete(name);\n for (const [key, session] of sessions) {\n if (!key.startsWith(`${name}/`)) continue;\n sessions.delete(key);\n void session.then((s) => s.close()).catch(() => undefined);\n }\n }\n\n app.get('/api/apps/:name/manifest', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n const { manifest } = running;\n const { merged, status } = await resolveAppConfig(store, manifest);\n const repoDir = store.appRepoDir(name);\n\n const instructions = await loadInstructions(repoDir, manifest.instructions);\n\n const skills = await Promise.all(\n manifest.skills.map(async (dir) => {\n let entries: string[] = [];\n try {\n entries = (await readdir(resolve(repoDir, dir))).sort();\n } catch {\n // A missing skill dir is still worth surfacing, just with no entries.\n }\n return { dir, entries };\n }),\n );\n\n const configured = new Map(status.integrations.map((i) => [i.name, i.configured]));\n // Raw manifest declarations only: `${config.*}` placeholders are never\n // interpolated here, so secret values cannot leak through env/headers.\n const mcpServers = Object.entries(manifest.mcpServers).map(([server, decl]) => ({\n name: server,\n ...('command' in decl\n ? { type: 'stdio', command: [decl.command, ...(decl.args ?? [])].join(' ') }\n : { type: decl.type, url: decl.url }),\n optional: decl.optional === true,\n configured: configured.get(server) ?? true,\n }));\n\n return c.json({\n name: manifest.name,\n version: manifest.version,\n ...(manifest.description !== undefined && { description: manifest.description }),\n ...(instructions !== undefined && { instructions }),\n ...(typeof merged.model === 'string' && { model: merged.model }),\n agents: await loadAgents(repoDir, manifest.agents),\n skills,\n allowedTools: manifest.permissions.allowedTools,\n mcpServers,\n jobs: manifest.jobs,\n });\n });\n\n app.get('/api/apps/:name/config', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n const { manifest } = running;\n const { merged, status } = await resolveAppConfig(store, manifest);\n // Secret values never leave the host; the UI only learns whether they are set.\n const values: Record<string, unknown> = { ...merged };\n const secretsSet: string[] = [];\n for (const [key, decl] of Object.entries(manifest.config)) {\n if (decl.secret !== true) continue;\n if (values[key] !== undefined) secretsSet.push(key);\n delete values[key];\n }\n return c.json({\n schema: manifest.config,\n values,\n secretsSet,\n userKeys: Object.keys(await store.readAppConfig(name)),\n ...status,\n });\n });\n\n app.put('/api/apps/:name/config', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Body must be JSON' }, 400);\n }\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Body must be an object of config keys' }, 400);\n }\n\n const config = await store.readAppConfig(name);\n for (const [key, value] of Object.entries(body)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;\n if (value === null) delete config[key];\n else config[key] = value;\n }\n await store.writeAppConfig(name, config);\n restartApp(name);\n\n const { status } = await resolveAppConfig(store, running.manifest);\n return c.json({ ok: true, ...status });\n });\n\n app.get('/api/apps/:name/jobs', async (c) => {\n const name = c.req.param('name');\n const jobs = await scheduler.status(name);\n if (!jobs) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n return c.json({ jobs });\n });\n\n app.post('/api/apps/:name/jobs/:job/run', async (c) => {\n const result = await scheduler.run(c.req.param('name'), c.req.param('job'));\n if (result === undefined) return c.json({ error: 'Unknown app or job' }, 404);\n if (result === 'skipped') return c.json({ error: 'Job is already running' }, 409);\n return c.json({ run: result }, result.ok ? 200 : 500);\n });\n\n app.post('/api/apps/:name/chat', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n\n let message: unknown;\n let conversationId: unknown;\n try {\n ({ message, conversationId } = (await c.req.json()) as {\n message?: unknown;\n conversationId?: unknown;\n });\n } catch {\n return c.json({ error: 'Body must be JSON' }, 400);\n }\n if (typeof message !== 'string' || message.length === 0) {\n return c.json({ error: 'Body must be { \"message\": string }' }, 400);\n }\n if (conversationId !== undefined && typeof conversationId !== 'string') {\n return c.json({ error: '\"conversationId\" must be a string' }, 400);\n }\n\n // Chat sessions are kept warm across turns, so the createSession gate only\n // sees the first turn of a conversation. Re-check here: the same cap, one\n // level up, so a conversation that goes over mid-chat stops too.\n try {\n await assertWithinBudget(store, usage, name);\n } catch (err) {\n if (BudgetExceededError.is(err)) return c.json({ error: err.message }, 402);\n throw err;\n }\n\n const state = await getChatState(name);\n let convo: Conversation | undefined;\n if (conversationId !== undefined) {\n convo = state.conversations.find((conv) => conv.id === conversationId);\n if (!convo) return c.json({ error: 'Unknown conversation' }, 404);\n } else {\n convo = {\n id: randomUUID(),\n title: message.slice(0, 80),\n updatedAt: new Date().toISOString(),\n };\n state.conversations.push(convo);\n await store.writeChatState(name, state);\n }\n const conversation = convo;\n\n let session: AISession;\n try {\n session = await getSession(name, running, conversation);\n } catch (err) {\n return c.json({ error: `Failed to start AI session: ${String(err)}` }, 502);\n }\n\n c.header('x-conversation-id', conversation.id);\n return streamSSE(c, async (stream) => {\n try {\n for await (const event of session.send(message)) {\n // One small write per turn: tracks the provider session and bumps recency.\n if (event.type === 'session') {\n conversation.sessionId = event.sessionId;\n conversation.updatedAt = new Date().toISOString();\n await store.writeChatState(name, state);\n }\n await stream.writeSSE({ event: event.type, data: JSON.stringify(event) });\n }\n } catch (err) {\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({ type: 'result', ok: false, error: String(err) }),\n });\n }\n });\n });\n\n app.all('/app/:name/api/*', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n if (!running.handler) return c.json({ error: `App \"${name}\" has no routes` }, 404);\n return dispatch(running.handler, c.req.raw, `/app/${name}/api`);\n });\n\n // Relative asset URLs in app UIs only work with a trailing slash.\n app.get('/app/:name', (c) => c.redirect(`/app/${c.req.param('name')}/`));\n\n app.all('/app/:name/*', async (c) => {\n const name = c.req.param('name');\n let running: RunningApp | undefined;\n try {\n running = await getApp(name);\n } catch (err) {\n // Browser navigations get a host-rendered page; API-ish requests keep JSON.\n if (c.req.method === 'GET' && (c.req.header('accept') ?? '').includes('text/html')) {\n return c.html(appErrorPage(name, err), 500);\n }\n throw err;\n }\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n\n if (c.req.method === 'GET' || c.req.method === 'HEAD') {\n const file = await serveStatic(name, running.manifest, c.req.path);\n if (file) return file;\n }\n if (running.handler) return dispatch(running.handler, c.req.raw, `/app/${name}`);\n return c.json({ error: 'Not found' }, 404);\n });\n\n return {\n app,\n scheduler,\n auth,\n async close() {\n scheduler.stop();\n const open = [...sessions.values()];\n sessions.clear();\n await Promise.all(\n open.map(async (p) => {\n try {\n await (await p).close();\n } catch {\n // Session may already be gone; closing is best-effort.\n }\n }),\n );\n },\n };\n}\n","import { mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { APP_NAME_PATTERN, AppSdkError, type ConfigObject } from '@alashi/apps-sdk';\nimport type { AppSourceSpec } from './sources.js';\n\nexport interface RegistryRef {\n name: string;\n url: string;\n}\n\n/** Host-enforced spend limits for one app. */\nexport interface AppBudget {\n /**\n * Daily AI spend ceiling in USD. Once the app's spend for the UTC day\n * reaches it, the host refuses `createSession`. `0` means \"no AI at all\".\n */\n dailyUsd: number;\n}\n\n/**\n * How the host decides whether a request must be authenticated.\n *\n * - `auto` (default): requests that verifiably come from the local console\n * (loopback peer, local Host header, no forwarding headers) are trusted as\n * the owner; everything else must present a credential. Note the limit: a\n * proxy that strips its forwarding headers and rewrites Host is\n * indistinguishable from a local request — anyone deliberately exposing the\n * host through a tunnel should set `required`.\n * - `required`: every request needs a credential, even on localhost.\n * - `off`: no authentication at all (current behavior; loopback binds only).\n */\nexport type AuthMode = 'auto' | 'required' | 'off';\n\n/** Host auth settings (`config.auth`) — a host concern, never app config. */\nexport interface AuthConfig {\n mode?: AuthMode;\n /**\n * App names whose UI and API (`/app/<name>/*`) need no identity. A grant,\n * so it lives here where only the host's owner can set it — an app manifest\n * can never declare itself public.\n */\n public?: string[];\n /**\n * Trust `Tailscale-User-*` identity headers (set by `tailscale serve`) as a\n * signed-in user. Only enable when the host is reachable exclusively\n * through Tailscale — the headers are plain text to anyone who can connect\n * directly.\n */\n trustTailscaleHeaders?: boolean;\n}\n\nexport interface GlobalConfig {\n registries: RegistryRef[];\n port?: number;\n /** Bind address for `alashi serve`; defaults to loopback (the dashboard has no auth). */\n host?: string;\n /** Host authentication; absent means `mode: \"auto\"` with no public apps. */\n auth?: AuthConfig;\n /** Per-app global overrides, keyed by app name (middle config layer). */\n apps?: Record<string, ConfigObject>;\n /**\n * Host-enforced per-app spend caps, keyed by app name.\n *\n * Deliberately a sibling of `apps` rather than a key inside it: everything in\n * `apps` is merged into the app's config and can be overridden by the app's\n * own `config.json` layer, which would let an app raise its own ceiling.\n * A cap is enforcement, so it lives where only the host can set it.\n */\n budgets?: Record<string, AppBudget>;\n}\n\nexport interface Conversation {\n id: string;\n /** Provider session backing this conversation; set after the first turn. */\n sessionId?: string;\n title?: string;\n updatedAt: string;\n}\n\n/** Host-side chat state for an app; the transcripts themselves live with the AI provider. */\nexport interface ChatState {\n conversations: Conversation[];\n}\n\nexport interface JobRun {\n job: string;\n trigger: 'schedule' | 'catchup' | 'manual';\n startedAt: string;\n finishedAt: string;\n ok: boolean;\n error?: string;\n}\n\n/** Host-side job state for an app: last run start per job plus a capped run log. */\nexport interface JobState {\n lastRun: Record<string, string>;\n runs: JobRun[];\n}\n\n/** Tokens and cost one model consumed within a usage bucket (a day or a month). */\nexport interface ModelUsageTotals {\n inputTokens: number;\n outputTokens: number;\n cacheReadInputTokens: number;\n cacheCreationInputTokens: number;\n costUsd: number;\n}\n\n/** One UTC day of AI spend for an app. */\nexport interface UsageDay {\n /** UTC calendar day, `YYYY-MM-DD`. */\n date: string;\n /** Sum of the `costUsd` the provider reported for that day's turns. */\n costUsd: number;\n /** Turns that reported cost or token usage (turns without either are not counted). */\n turns: number;\n /** Per-model token/cost totals, keyed by the engine's model id. Absent for days recorded before token metering. */\n models?: Record<string, ModelUsageTotals>;\n}\n\n/** One UTC calendar month of AI spend; months are never pruned. */\nexport interface UsageMonth {\n /** UTC calendar month, `YYYY-MM`. */\n month: string;\n costUsd: number;\n turns: number;\n models?: Record<string, ModelUsageTotals>;\n}\n\n/**\n * Host-side AI spend for an app: daily series (oldest first, trimmed to the\n * retention window) plus monthly rollups that survive daily pruning so\n * long-term trends remain visible. `months` is absent only in files written\n * before rollups existed; the recorder backfills it from `days` on first touch.\n */\nexport interface UsageState {\n days: UsageDay[];\n months?: UsageMonth[];\n}\n\n/** A long-lived bearer credential; only the sha256 of the secret is stored. */\nexport interface AuthTokenRecord {\n id: string;\n label?: string;\n hash: string;\n createdAt: string;\n lastUsedAt?: string;\n}\n\n/** A browser session; only the sha256 of the cookie value is stored. */\nexport interface AuthSessionRecord {\n hash: string;\n /** Display name for attribution (token label, tailscale login, or \"owner\"). */\n name: string;\n level: 'owner' | 'user';\n createdAt: string;\n expiresAt: string;\n}\n\n/** Host auth state (`auth.json` at the store root — OS bookkeeping, not app state). */\nexport interface AuthState {\n tokens: AuthTokenRecord[];\n sessions: AuthSessionRecord[];\n}\n\nexport interface InstallRecord {\n name: string;\n version: string;\n /** The SDK range the app's manifest declared at install/update time. */\n sdkVersion?: string;\n source: AppSourceSpec;\n installedAt: string;\n updatedAt: string;\n}\n\nasync function readJson<T>(path: string): Promise<T | undefined> {\n try {\n return JSON.parse(await readFile(path, 'utf8')) as T;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw err;\n }\n}\n\nasync function writeJson(path: string, value: unknown): Promise<void> {\n await mkdir(join(path, '..'), { recursive: true });\n const tmp = `${path}.tmp`;\n await writeFile(tmp, `${JSON.stringify(value, null, 2)}\\n`, 'utf8');\n await rename(tmp, path);\n}\n\n/**\n * The portable state directory ($ALASHI_HOME, default ~/.alashi).\n * Everything the host knows lives under this root so it can be moved\n * between machines as a unit.\n */\nexport class Store {\n readonly root: string;\n\n constructor(root?: string) {\n this.root = root ?? process.env.ALASHI_HOME ?? join(homedir(), '.alashi');\n }\n\n get configPath(): string {\n return join(this.root, 'config.json');\n }\n\n get appsDir(): string {\n return join(this.root, 'apps');\n }\n\n get registriesDir(): string {\n return join(this.root, 'registries');\n }\n\n get stagingDir(): string {\n return join(this.root, '.staging');\n }\n\n /** Validates the name before using it in a path (traversal guard). */\n appDir(name: string): string {\n if (!APP_NAME_PATTERN.test(name)) {\n throw new AppSdkError(`Invalid app name: ${JSON.stringify(name)}`);\n }\n return join(this.appsDir, name);\n }\n\n appRepoDir(name: string): string {\n return join(this.appDir(name), 'repo');\n }\n\n appDataDir(name: string): string {\n return join(this.appDir(name), 'data');\n }\n\n appConfigPath(name: string): string {\n return join(this.appDir(name), 'config.json');\n }\n\n appRecordPath(name: string): string {\n return join(this.appDir(name), 'app.json');\n }\n\n appChatStatePath(name: string): string {\n return join(this.appDir(name), 'chat.json');\n }\n\n appJobStatePath(name: string): string {\n return join(this.appDir(name), 'jobs.json');\n }\n\n appUsagePath(name: string): string {\n return join(this.appDir(name), 'usage.json');\n }\n\n registryCachePath(name: string): string {\n if (!APP_NAME_PATTERN.test(name)) {\n throw new AppSdkError(`Invalid registry name: ${JSON.stringify(name)}`);\n }\n return join(this.registriesDir, `${name}.json`);\n }\n\n async readGlobalConfig(): Promise<GlobalConfig> {\n return (await readJson<GlobalConfig>(this.configPath)) ?? { registries: [] };\n }\n\n async writeGlobalConfig(config: GlobalConfig): Promise<void> {\n await writeJson(this.configPath, config);\n }\n\n async readAppRecord(name: string): Promise<InstallRecord | undefined> {\n return readJson<InstallRecord>(this.appRecordPath(name));\n }\n\n async writeAppRecord(record: InstallRecord): Promise<void> {\n await writeJson(this.appRecordPath(record.name), record);\n }\n\n async readAppConfig(name: string): Promise<ConfigObject> {\n return (await readJson<ConfigObject>(this.appConfigPath(name))) ?? {};\n }\n\n async writeAppConfig(name: string, config: ConfigObject): Promise<void> {\n await writeJson(this.appConfigPath(name), config);\n }\n\n async readChatState(name: string): Promise<ChatState> {\n const state = await readJson<Partial<ChatState>>(this.appChatStatePath(name));\n return { conversations: state?.conversations ?? [] };\n }\n\n async writeChatState(name: string, state: ChatState): Promise<void> {\n await writeJson(this.appChatStatePath(name), state);\n }\n\n async readJobState(name: string): Promise<JobState> {\n const state = await readJson<Partial<JobState>>(this.appJobStatePath(name));\n return { lastRun: state?.lastRun ?? {}, runs: state?.runs ?? [] };\n }\n\n async writeJobState(name: string, state: JobState): Promise<void> {\n await writeJson(this.appJobStatePath(name), state);\n }\n\n async readUsageState(name: string): Promise<UsageState> {\n const state = await readJson<Partial<UsageState>>(this.appUsagePath(name));\n // `months` is deliberately passed through as possibly-undefined: the\n // recorder uses that to tell \"legacy file, backfill from days\" apart\n // from \"rollups tracked, currently empty\".\n return { days: state?.days ?? [], ...(state?.months !== undefined && { months: state.months }) };\n }\n\n async writeUsageState(name: string, state: UsageState): Promise<void> {\n await writeJson(this.appUsagePath(name), state);\n }\n\n get authStatePath(): string {\n return join(this.root, 'auth.json');\n }\n\n async readAuthState(): Promise<AuthState> {\n const state = await readJson<Partial<AuthState>>(this.authStatePath);\n return { tokens: state?.tokens ?? [], sessions: state?.sessions ?? [] };\n }\n\n async writeAuthState(state: AuthState): Promise<void> {\n await writeJson(this.authStatePath, state);\n }\n\n async listInstalled(): Promise<InstallRecord[]> {\n let entries: string[];\n try {\n entries = await readdir(this.appsDir);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw err;\n }\n const records: InstallRecord[] = [];\n for (const entry of entries.sort()) {\n if (!APP_NAME_PATTERN.test(entry)) continue;\n const record = await this.readAppRecord(entry);\n if (record) records.push(record);\n }\n return records;\n }\n}\n"],"mappings":"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"}