@alashi/cli 0.9.0 → 0.11.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.
- package/dist/cli.mjs +52 -4
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +230 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{store-Cxxnjq70.mjs → store-VoJe_U1Q.mjs} +736 -29
- package/dist/store-VoJe_U1Q.mjs.map +1 -0
- package/package.json +3 -3
- package/templates/basic-app/AGENTS.md +3 -1
- package/templates/basic-app/index.mjs +1 -1
- package/templates/basic-app/package.json +1 -1
- package/dist/store-Cxxnjq70.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { A as UsageRecorder, E as budgetStatus, O as setAppBudget, b as loadApp, c as installApp, d as updateApp, f as addRegistry, h as removeRegistry, i as resolveAppConfig, n as createServer, o as exportApp, s as importApp, t as Store, u as removeApp } from "./store-VoJe_U1Q.mjs";
|
|
3
3
|
import { APP_NAME_PATTERN, AppSdkError } from "@alashi/apps-sdk";
|
|
4
4
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
5
|
-
import { join, resolve } from "node:path";
|
|
5
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { ClaudeCodeProvider } from "@alashi/provider-claude";
|
|
8
8
|
import { serve } from "@hono/node-server";
|
|
@@ -109,10 +109,39 @@ program.command("remove").argument("<name>", "installed app name").option("--pur
|
|
|
109
109
|
await removeApp(store, name, { purge: options.purge ?? false });
|
|
110
110
|
console.log(options.purge ? `Removed ${name} (purged)` : `Removed ${name} (data kept)`);
|
|
111
111
|
});
|
|
112
|
+
/** Prints archive paths the way the user would type them. */
|
|
113
|
+
function displayPath(path) {
|
|
114
|
+
const rel = relative(process.cwd(), path);
|
|
115
|
+
return rel === "" || rel.startsWith("..") || isAbsolute(rel) ? path : `./${rel}`;
|
|
116
|
+
}
|
|
117
|
+
program.command("export").argument("<name>", "installed app name").option("-o, --out <file>", "archive path (default: ./<name>-<version>.tar.gz)").description("Pack an app container and its chat transcripts into a tar.gz").action(async (name, options) => {
|
|
118
|
+
const result = await exportApp(store, new ClaudeCodeProvider(), name, { ...options.out && { out: options.out } });
|
|
119
|
+
for (const warning of result.warnings) console.log(` warning: ${warning}`);
|
|
120
|
+
console.log(`Exported ${result.record.name}@${result.record.version} -> ${displayPath(result.archivePath)} (${result.transcriptCount} transcripts)`);
|
|
121
|
+
if (result.record.source.type === "local") console.log(" note: repo/ was captured as real files, not as the local symlink");
|
|
122
|
+
});
|
|
123
|
+
async function importArchive(file) {
|
|
124
|
+
const result = await importApp(store, new ClaudeCodeProvider(), file);
|
|
125
|
+
for (const warning of result.warnings) console.log(` warning: ${warning}`);
|
|
126
|
+
console.log(`Imported ${result.record.name}@${result.record.version} (${result.transcriptCount} transcripts restored)`);
|
|
127
|
+
console.log(` data: ${store.appDataDir(result.record.name)}`);
|
|
128
|
+
console.log(` config: ${store.appConfigPath(result.record.name)}`);
|
|
129
|
+
}
|
|
130
|
+
program.command("import").argument("<file>", "archive produced by alashi export").description("Restore an app container and its chat transcripts from a tar.gz").action(importArchive);
|
|
112
131
|
program.command("list").option("--json", "machine-readable output").description("List installed apps").action(async (options) => {
|
|
132
|
+
const usage = new UsageRecorder(store);
|
|
113
133
|
const records = await store.listInstalled();
|
|
134
|
+
const spend = new Map(await Promise.all(records.map(async (record) => [record.name, await budgetStatus(store, usage, record.name)])));
|
|
114
135
|
if (options.json) {
|
|
115
|
-
console.log(JSON.stringify(records
|
|
136
|
+
console.log(JSON.stringify(records.map((record) => {
|
|
137
|
+
const budget = spend.get(record.name);
|
|
138
|
+
return {
|
|
139
|
+
...record,
|
|
140
|
+
todayCostUsd: budget?.spentUsd ?? 0,
|
|
141
|
+
overBudget: budget?.overBudget ?? false,
|
|
142
|
+
...budget?.dailyUsd !== void 0 && { dailyBudgetUsd: budget.dailyUsd }
|
|
143
|
+
};
|
|
144
|
+
}), null, 2));
|
|
116
145
|
return;
|
|
117
146
|
}
|
|
118
147
|
if (records.length === 0) {
|
|
@@ -121,8 +150,27 @@ program.command("list").option("--json", "machine-readable output").description(
|
|
|
121
150
|
}
|
|
122
151
|
for (const record of records) {
|
|
123
152
|
const origin = record.source.type === "git" ? record.source.url : record.source.path;
|
|
124
|
-
|
|
153
|
+
const budget = spend.get(record.name);
|
|
154
|
+
const today = (budget?.spentUsd ?? 0).toFixed(4);
|
|
155
|
+
const cap = budget?.dailyUsd === void 0 ? "" : ` of $${budget.dailyUsd.toFixed(2)} cap${budget.overBudget ? " (OVER BUDGET)" : ""}`;
|
|
156
|
+
console.log(`${record.name}@${record.version} ${record.source.type} ${origin} $${today} today${cap}`);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
program.command("budget").argument("<name>", "installed app name").argument("[dailyUsd]", "daily cap in USD, or \"none\" to remove it (omit to just show)").description("Show or set an app's host-enforced daily AI spend cap").action(async (name, dailyUsd) => {
|
|
160
|
+
if (!await store.readAppRecord(name)) throw new Error(`App "${name}" is not installed`);
|
|
161
|
+
if (dailyUsd !== void 0) {
|
|
162
|
+
const amount = dailyUsd === "none" || dailyUsd === "off" ? void 0 : Number(dailyUsd);
|
|
163
|
+
if (amount !== void 0 && (!Number.isFinite(amount) || amount < 0)) throw new Error(`Invalid daily spend cap: ${dailyUsd}`);
|
|
164
|
+
await setAppBudget(store, name, amount);
|
|
165
|
+
}
|
|
166
|
+
const status = await budgetStatus(store, new UsageRecorder(store), name);
|
|
167
|
+
const spent = `$${status.spentUsd.toFixed(4)} spent on ${status.date} (UTC)`;
|
|
168
|
+
if (status.dailyUsd === void 0) {
|
|
169
|
+
console.log(`${name}: no daily cap — ${spent}`);
|
|
170
|
+
return;
|
|
125
171
|
}
|
|
172
|
+
console.log(`${name}: $${status.dailyUsd.toFixed(2)} daily cap — ${spent}, $${(status.remainingUsd ?? 0).toFixed(4)} left`);
|
|
173
|
+
if (status.overBudget) console.log(" OVER BUDGET: new AI sessions are refused until 00:00 UTC");
|
|
126
174
|
});
|
|
127
175
|
const registry = program.command("registry").description("Manage app registries");
|
|
128
176
|
registry.command("add").argument("<name>", "registry name").argument("<url>", "index.json URL or git repo URL").action(async (name, url) => {
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/create.ts","../src/cli.ts"],"sourcesContent":["import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { APP_NAME_PATTERN, AppSdkError } from '@alashi/apps-sdk';\n\n// Works from both src/ (tests) and dist/ (published build) — the templates\n// dir sits one level up in the package root either way.\nconst TEMPLATE_DIR = fileURLToPath(new URL('../templates/basic-app', import.meta.url));\n\n// npm strips .gitignore from published packages, so it ships unprefixed.\nconst RENAMES: Record<string, string> = { gitignore: '.gitignore' };\n\n/**\n * Scaffolds a new app from the basic-app template into targetDir,\n * substituting the app name. Returns the created files, relative to\n * targetDir. Refuses to write into a non-empty directory.\n */\nexport async function scaffoldApp(name: string, targetDir: string): Promise<string[]> {\n if (!APP_NAME_PATTERN.test(name)) {\n throw new AppSdkError(\n `Invalid app name \"${name}\": use lowercase letters, digits, and hyphens (max 64 chars)`,\n );\n }\n const existing = await readdir(targetDir).catch(() => undefined);\n if (existing && existing.length > 0) {\n throw new AppSdkError(`Directory is not empty: ${targetDir}`);\n }\n\n const written: string[] = [];\n async function copyDir(rel: string): Promise<void> {\n const entries = await readdir(join(TEMPLATE_DIR, rel), { withFileTypes: true });\n await mkdir(join(targetDir, rel), { recursive: true });\n for (const entry of entries) {\n const relPath = join(rel, entry.name);\n if (entry.isDirectory()) {\n await copyDir(relPath);\n continue;\n }\n const content = await readFile(join(TEMPLATE_DIR, relPath), 'utf8');\n const outPath = join(rel, RENAMES[entry.name] ?? entry.name);\n await writeFile(join(targetDir, outPath), content.replaceAll('__APP_NAME__', name), 'utf8');\n written.push(outPath);\n }\n }\n await copyDir('');\n return written.sort();\n}\n","#!/usr/bin/env node\nimport { resolve } from 'node:path';\nimport { ClaudeCodeProvider } from '@alashi/provider-claude';\nimport { serve } from '@hono/node-server';\nimport { Command } from 'commander';\nimport { scaffoldApp } from './create.js';\nimport { installApp, removeApp, updateApp } from './installer.js';\nimport { loadApp } from './loader.js';\nimport { addRegistry, removeRegistry } from './registry.js';\nimport { resolveAppConfig } from './runtime.js';\nimport { createServer } from './server.js';\nimport { Store } from './store.js';\n\nconst program = new Command();\nconst store = new Store();\n\nprogram.name('alashi').description('Install, manage, and run Claude Code-powered apps');\n\nasync function serveDashboard(portArg?: string, hostArg?: string): Promise<void> {\n const config = await store.readGlobalConfig();\n const port = portArg ? Number(portArg) : (config.port ?? 4141);\n if (!Number.isInteger(port) || port < 0 || port > 65535) {\n throw new Error(`Invalid port: ${portArg}`);\n }\n // Loopback by default: the dashboard has no auth, so it must not be\n // LAN-reachable unless the user explicitly opts in with --host.\n const hostname = hostArg ?? config.host ?? '127.0.0.1';\n const server = createServer(store, new ClaudeCodeProvider());\n await server.scheduler.start();\n const httpServer = serve({ fetch: server.app.fetch, port, hostname }, (info) => {\n console.log(`alashi dashboard at http://localhost:${info.port}`);\n if (hostname !== '127.0.0.1' && hostname !== 'localhost') {\n console.log(` WARNING: listening on ${hostname} — the dashboard has no authentication`);\n }\n });\n const shutdown = (): void => {\n httpServer.close();\n void server.close().finally(() => process.exit(0));\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n\nprogram\n .command('serve', { isDefault: true })\n .option('-p, --port <port>', 'port to listen on (default: config port or 4141)')\n .option('--host <host>', 'address to bind (default: 127.0.0.1; the dashboard has no auth)')\n .description('Serve the web dashboard (the default when no command is given)')\n .action(async (options: { port?: string; host?: string }) =>\n serveDashboard(options.port, options.host),\n );\n\nprogram\n .command('create')\n .argument('<name>', 'app name (lowercase letters, digits, hyphens)')\n .argument('[directory]', 'target directory (default: ./<name>)')\n .description('Scaffold a new app from the basic template')\n .action(async (name: string, directory?: string) => {\n const target = directory ?? name;\n const files = await scaffoldApp(name, resolve(target));\n console.log(`Created ${name}/`);\n for (const file of files) console.log(` ${file}`);\n console.log('\\nNext steps:');\n console.log(` cd ${target}`);\n console.log(' npm install');\n console.log(' alashi install .');\n console.log(' alashi # dashboard at http://localhost:4141');\n });\n\nprogram\n .command('install')\n .argument('<source>', 'app name (via registry), git URL, or local path')\n .description('Install an app')\n .action(async (source: string) => {\n const record = await installApp(store, source);\n console.log(`Installed ${record.name}@${record.version} (${record.source.type})`);\n console.log(` data: ${store.appDataDir(record.name)}`);\n console.log(` config: ${store.appConfigPath(record.name)}`);\n await printSetupHints(record.name);\n });\n\n/** After install: what still needs configuring, and which integrations exist. */\nasync function printSetupHints(name: string): Promise<void> {\n let manifest: Awaited<ReturnType<typeof loadApp>>['manifest'];\n try {\n ({ manifest } = await loadApp(store.appRepoDir(name)));\n } catch {\n return; // install already succeeded; hints are best-effort\n }\n const { status } = await resolveAppConfig(store, manifest);\n if (status.missing.length > 0) {\n console.log(` requires: ${status.missing.join(', ')}`);\n }\n const optional = status.integrations.filter((i) => i.optional && !i.configured);\n if (optional.length > 0) {\n const list = optional.map((i) => `${i.name} (needs ${i.missing.join(', ')})`).join(', ');\n console.log(` optional integrations: ${list}`);\n }\n if (status.missing.length > 0 || optional.length > 0) {\n console.log(` configure via the dashboard settings page or the config file above`);\n }\n}\n\nprogram\n .command('update')\n .argument('<name>', 'installed app name')\n .description('Update an installed app')\n .action(async (name: string) => {\n const record = await updateApp(store, name);\n console.log(`Updated ${record.name} to ${record.version}`);\n });\n\nprogram\n .command('remove')\n .argument('<name>', 'installed app name')\n .option('--purge', 'also delete the app data and config')\n .description('Remove an installed app (keeps data unless --purge)')\n .action(async (name: string, options: { purge?: boolean }) => {\n await removeApp(store, name, { purge: options.purge ?? false });\n console.log(options.purge ? `Removed ${name} (purged)` : `Removed ${name} (data kept)`);\n });\n\nprogram\n .command('list')\n .option('--json', 'machine-readable output')\n .description('List installed apps')\n .action(async (options: { json?: boolean }) => {\n const records = await store.listInstalled();\n if (options.json) {\n console.log(JSON.stringify(records, null, 2));\n return;\n }\n if (records.length === 0) {\n console.log('No apps installed. Try: alashi install <name|git-url|path>');\n return;\n }\n for (const record of records) {\n const origin = record.source.type === 'git' ? record.source.url : record.source.path;\n console.log(`${record.name}@${record.version} ${record.source.type} ${origin}`);\n }\n });\n\nconst registry = program.command('registry').description('Manage app registries');\n\nregistry\n .command('add')\n .argument('<name>', 'registry name')\n .argument('<url>', 'index.json URL or git repo URL')\n .action(async (name: string, url: string) => {\n const index = await addRegistry(store, name, url);\n console.log(`Added registry \"${name}\" (${index.apps.length} apps)`);\n });\n\nregistry\n .command('remove')\n .argument('<name>', 'registry name')\n .action(async (name: string) => {\n await removeRegistry(store, name);\n console.log(`Removed registry \"${name}\"`);\n });\n\nregistry.command('list').action(async () => {\n const config = await store.readGlobalConfig();\n if (config.registries.length === 0) {\n console.log('No registries configured.');\n return;\n }\n for (const r of config.registries) console.log(`${r.name} ${r.url}`);\n});\n\nprogram.parseAsync().catch((err: unknown) => {\n console.error(err instanceof Error ? err.message : err);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/create.ts","../src/cli.ts"],"sourcesContent":["import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { APP_NAME_PATTERN, AppSdkError } from '@alashi/apps-sdk';\n\n// Works from both src/ (tests) and dist/ (published build) — the templates\n// dir sits one level up in the package root either way.\nconst TEMPLATE_DIR = fileURLToPath(new URL('../templates/basic-app', import.meta.url));\n\n// npm strips .gitignore from published packages, so it ships unprefixed.\nconst RENAMES: Record<string, string> = { gitignore: '.gitignore' };\n\n/**\n * Scaffolds a new app from the basic-app template into targetDir,\n * substituting the app name. Returns the created files, relative to\n * targetDir. Refuses to write into a non-empty directory.\n */\nexport async function scaffoldApp(name: string, targetDir: string): Promise<string[]> {\n if (!APP_NAME_PATTERN.test(name)) {\n throw new AppSdkError(\n `Invalid app name \"${name}\": use lowercase letters, digits, and hyphens (max 64 chars)`,\n );\n }\n const existing = await readdir(targetDir).catch(() => undefined);\n if (existing && existing.length > 0) {\n throw new AppSdkError(`Directory is not empty: ${targetDir}`);\n }\n\n const written: string[] = [];\n async function copyDir(rel: string): Promise<void> {\n const entries = await readdir(join(TEMPLATE_DIR, rel), { withFileTypes: true });\n await mkdir(join(targetDir, rel), { recursive: true });\n for (const entry of entries) {\n const relPath = join(rel, entry.name);\n if (entry.isDirectory()) {\n await copyDir(relPath);\n continue;\n }\n const content = await readFile(join(TEMPLATE_DIR, relPath), 'utf8');\n const outPath = join(rel, RENAMES[entry.name] ?? entry.name);\n await writeFile(join(targetDir, outPath), content.replaceAll('__APP_NAME__', name), 'utf8');\n written.push(outPath);\n }\n }\n await copyDir('');\n return written.sort();\n}\n","#!/usr/bin/env node\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { ClaudeCodeProvider } from '@alashi/provider-claude';\nimport { serve } from '@hono/node-server';\nimport { Command } from 'commander';\nimport { budgetStatus, setAppBudget } from './budget.js';\nimport { scaffoldApp } from './create.js';\nimport { installApp, removeApp, updateApp } from './installer.js';\nimport { loadApp } from './loader.js';\nimport { exportApp, importApp } from './portability.js';\nimport { addRegistry, removeRegistry } from './registry.js';\nimport { resolveAppConfig } from './runtime.js';\nimport { createServer } from './server.js';\nimport { Store } from './store.js';\nimport { UsageRecorder } from './usage.js';\n\nconst program = new Command();\nconst store = new Store();\n\nprogram.name('alashi').description('Install, manage, and run Claude Code-powered apps');\n\nasync function serveDashboard(portArg?: string, hostArg?: string): Promise<void> {\n const config = await store.readGlobalConfig();\n const port = portArg ? Number(portArg) : (config.port ?? 4141);\n if (!Number.isInteger(port) || port < 0 || port > 65535) {\n throw new Error(`Invalid port: ${portArg}`);\n }\n // Loopback by default: the dashboard has no auth, so it must not be\n // LAN-reachable unless the user explicitly opts in with --host.\n const hostname = hostArg ?? config.host ?? '127.0.0.1';\n const server = createServer(store, new ClaudeCodeProvider());\n await server.scheduler.start();\n const httpServer = serve({ fetch: server.app.fetch, port, hostname }, (info) => {\n console.log(`alashi dashboard at http://localhost:${info.port}`);\n if (hostname !== '127.0.0.1' && hostname !== 'localhost') {\n console.log(` WARNING: listening on ${hostname} — the dashboard has no authentication`);\n }\n });\n const shutdown = (): void => {\n httpServer.close();\n void server.close().finally(() => process.exit(0));\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n\nprogram\n .command('serve', { isDefault: true })\n .option('-p, --port <port>', 'port to listen on (default: config port or 4141)')\n .option('--host <host>', 'address to bind (default: 127.0.0.1; the dashboard has no auth)')\n .description('Serve the web dashboard (the default when no command is given)')\n .action(async (options: { port?: string; host?: string }) =>\n serveDashboard(options.port, options.host),\n );\n\nprogram\n .command('create')\n .argument('<name>', 'app name (lowercase letters, digits, hyphens)')\n .argument('[directory]', 'target directory (default: ./<name>)')\n .description('Scaffold a new app from the basic template')\n .action(async (name: string, directory?: string) => {\n const target = directory ?? name;\n const files = await scaffoldApp(name, resolve(target));\n console.log(`Created ${name}/`);\n for (const file of files) console.log(` ${file}`);\n console.log('\\nNext steps:');\n console.log(` cd ${target}`);\n console.log(' npm install');\n console.log(' alashi install .');\n console.log(' alashi # dashboard at http://localhost:4141');\n });\n\nprogram\n .command('install')\n .argument('<source>', 'app name (via registry), git URL, or local path')\n .description('Install an app')\n .action(async (source: string) => {\n const record = await installApp(store, source);\n console.log(`Installed ${record.name}@${record.version} (${record.source.type})`);\n console.log(` data: ${store.appDataDir(record.name)}`);\n console.log(` config: ${store.appConfigPath(record.name)}`);\n await printSetupHints(record.name);\n });\n\n/** After install: what still needs configuring, and which integrations exist. */\nasync function printSetupHints(name: string): Promise<void> {\n let manifest: Awaited<ReturnType<typeof loadApp>>['manifest'];\n try {\n ({ manifest } = await loadApp(store.appRepoDir(name)));\n } catch {\n return; // install already succeeded; hints are best-effort\n }\n const { status } = await resolveAppConfig(store, manifest);\n if (status.missing.length > 0) {\n console.log(` requires: ${status.missing.join(', ')}`);\n }\n const optional = status.integrations.filter((i) => i.optional && !i.configured);\n if (optional.length > 0) {\n const list = optional.map((i) => `${i.name} (needs ${i.missing.join(', ')})`).join(', ');\n console.log(` optional integrations: ${list}`);\n }\n if (status.missing.length > 0 || optional.length > 0) {\n console.log(` configure via the dashboard settings page or the config file above`);\n }\n}\n\nprogram\n .command('update')\n .argument('<name>', 'installed app name')\n .description('Update an installed app')\n .action(async (name: string) => {\n const record = await updateApp(store, name);\n console.log(`Updated ${record.name} to ${record.version}`);\n });\n\nprogram\n .command('remove')\n .argument('<name>', 'installed app name')\n .option('--purge', 'also delete the app data and config')\n .description('Remove an installed app (keeps data unless --purge)')\n .action(async (name: string, options: { purge?: boolean }) => {\n await removeApp(store, name, { purge: options.purge ?? false });\n console.log(options.purge ? `Removed ${name} (purged)` : `Removed ${name} (data kept)`);\n });\n\n/** Prints archive paths the way the user would type them. */\nfunction displayPath(path: string): string {\n const rel = relative(process.cwd(), path);\n return rel === '' || rel.startsWith('..') || isAbsolute(rel) ? path : `./${rel}`;\n}\n\nprogram\n .command('export')\n .argument('<name>', 'installed app name')\n .option('-o, --out <file>', 'archive path (default: ./<name>-<version>.tar.gz)')\n .description('Pack an app container and its chat transcripts into a tar.gz')\n .action(async (name: string, options: { out?: string }) => {\n const result = await exportApp(store, new ClaudeCodeProvider(), name, {\n ...(options.out && { out: options.out }),\n });\n for (const warning of result.warnings) console.log(` warning: ${warning}`);\n console.log(\n `Exported ${result.record.name}@${result.record.version} -> ${displayPath(result.archivePath)} (${result.transcriptCount} transcripts)`,\n );\n if (result.record.source.type === 'local') {\n console.log(' note: repo/ was captured as real files, not as the local symlink');\n }\n });\n\n// `import` is a reserved word, so the handler cannot be named after it; the\n// commander command string itself is fine.\nasync function importArchive(file: string): Promise<void> {\n const result = await importApp(store, new ClaudeCodeProvider(), file);\n for (const warning of result.warnings) console.log(` warning: ${warning}`);\n console.log(\n `Imported ${result.record.name}@${result.record.version} (${result.transcriptCount} transcripts restored)`,\n );\n console.log(` data: ${store.appDataDir(result.record.name)}`);\n console.log(` config: ${store.appConfigPath(result.record.name)}`);\n}\n\nprogram\n .command('import')\n .argument('<file>', 'archive produced by alashi export')\n .description('Restore an app container and its chat transcripts from a tar.gz')\n .action(importArchive);\n\nprogram\n .command('list')\n .option('--json', 'machine-readable output')\n .description('List installed apps')\n .action(async (options: { json?: boolean }) => {\n const usage = new UsageRecorder(store);\n const records = await store.listInstalled();\n const spend = new Map(\n await Promise.all(\n records.map(\n async (record) => [record.name, await budgetStatus(store, usage, record.name)] as const,\n ),\n ),\n );\n if (options.json) {\n console.log(\n JSON.stringify(\n records.map((record) => {\n const budget = spend.get(record.name);\n return {\n ...record,\n todayCostUsd: budget?.spentUsd ?? 0,\n overBudget: budget?.overBudget ?? false,\n ...(budget?.dailyUsd !== undefined && { dailyBudgetUsd: budget.dailyUsd }),\n };\n }),\n null,\n 2,\n ),\n );\n return;\n }\n if (records.length === 0) {\n console.log('No apps installed. Try: alashi install <name|git-url|path>');\n return;\n }\n for (const record of records) {\n const origin = record.source.type === 'git' ? record.source.url : record.source.path;\n const budget = spend.get(record.name);\n const today = (budget?.spentUsd ?? 0).toFixed(4);\n const cap =\n budget?.dailyUsd === undefined\n ? ''\n : ` of $${budget.dailyUsd.toFixed(2)} cap${budget.overBudget ? ' (OVER BUDGET)' : ''}`;\n console.log(\n `${record.name}@${record.version} ${record.source.type} ${origin} $${today} today${cap}`,\n );\n }\n });\n\nprogram\n .command('budget')\n .argument('<name>', 'installed app name')\n .argument('[dailyUsd]', 'daily cap in USD, or \"none\" to remove it (omit to just show)')\n .description(\"Show or set an app's host-enforced daily AI spend cap\")\n .action(async (name: string, dailyUsd?: string) => {\n if (!(await store.readAppRecord(name))) throw new Error(`App \"${name}\" is not installed`);\n if (dailyUsd !== undefined) {\n const cleared = dailyUsd === 'none' || dailyUsd === 'off';\n const amount = cleared ? undefined : Number(dailyUsd);\n if (amount !== undefined && (!Number.isFinite(amount) || amount < 0)) {\n throw new Error(`Invalid daily spend cap: ${dailyUsd}`);\n }\n await setAppBudget(store, name, amount);\n }\n const status = await budgetStatus(store, new UsageRecorder(store), name);\n const spent = `$${status.spentUsd.toFixed(4)} spent on ${status.date} (UTC)`;\n if (status.dailyUsd === undefined) {\n console.log(`${name}: no daily cap — ${spent}`);\n return;\n }\n console.log(\n `${name}: $${status.dailyUsd.toFixed(2)} daily cap — ${spent}, $${(status.remainingUsd ?? 0).toFixed(4)} left`,\n );\n if (status.overBudget)\n console.log(' OVER BUDGET: new AI sessions are refused until 00:00 UTC');\n });\n\nconst registry = program.command('registry').description('Manage app registries');\n\nregistry\n .command('add')\n .argument('<name>', 'registry name')\n .argument('<url>', 'index.json URL or git repo URL')\n .action(async (name: string, url: string) => {\n const index = await addRegistry(store, name, url);\n console.log(`Added registry \"${name}\" (${index.apps.length} apps)`);\n });\n\nregistry\n .command('remove')\n .argument('<name>', 'registry name')\n .action(async (name: string) => {\n await removeRegistry(store, name);\n console.log(`Removed registry \"${name}\"`);\n });\n\nregistry.command('list').action(async () => {\n const config = await store.readGlobalConfig();\n if (config.registries.length === 0) {\n console.log('No registries configured.');\n return;\n }\n for (const r of config.registries) console.log(`${r.name} ${r.url}`);\n});\n\nprogram.parseAsync().catch((err: unknown) => {\n console.error(err instanceof Error ? err.message : err);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import { AIProvider, AppContext, AppDefinition, AppManifest, ConfigObject } from "@alashi/apps-sdk";
|
|
1
|
+
import { AIProvider, AppContext, AppDefinition, AppManifest, AppSdkError, ConfigObject } from "@alashi/apps-sdk";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
|
-
//#region src/compat.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Enforces the manifest's declared `sdkVersion` range against the SDK
|
|
6
|
-
* this host is built with. Apps that declare nothing are accepted.
|
|
7
|
-
*/
|
|
8
|
-
declare function checkSdkCompatibility(manifest: AppManifest): void;
|
|
9
|
-
//#endregion
|
|
10
3
|
//#region src/sources.d.ts
|
|
11
4
|
type AppSourceSpec = {
|
|
12
5
|
type: "git";
|
|
@@ -32,6 +25,14 @@ interface RegistryRef {
|
|
|
32
25
|
name: string;
|
|
33
26
|
url: string;
|
|
34
27
|
}
|
|
28
|
+
/** Host-enforced spend limits for one app. */
|
|
29
|
+
interface AppBudget {
|
|
30
|
+
/**
|
|
31
|
+
* Daily AI spend ceiling in USD. Once the app's spend for the UTC day
|
|
32
|
+
* reaches it, the host refuses `createSession`. `0` means "no AI at all".
|
|
33
|
+
*/
|
|
34
|
+
dailyUsd: number;
|
|
35
|
+
}
|
|
35
36
|
interface GlobalConfig {
|
|
36
37
|
registries: RegistryRef[];
|
|
37
38
|
port?: number;
|
|
@@ -39,6 +40,15 @@ interface GlobalConfig {
|
|
|
39
40
|
host?: string;
|
|
40
41
|
/** Per-app global overrides, keyed by app name (middle config layer). */
|
|
41
42
|
apps?: Record<string, ConfigObject>;
|
|
43
|
+
/**
|
|
44
|
+
* Host-enforced per-app spend caps, keyed by app name.
|
|
45
|
+
*
|
|
46
|
+
* Deliberately a sibling of `apps` rather than a key inside it: everything in
|
|
47
|
+
* `apps` is merged into the app's config and can be overridden by the app's
|
|
48
|
+
* own `config.json` layer, which would let an app raise its own ceiling.
|
|
49
|
+
* A cap is enforcement, so it lives where only the host can set it.
|
|
50
|
+
*/
|
|
51
|
+
budgets?: Record<string, AppBudget>;
|
|
42
52
|
}
|
|
43
53
|
interface Conversation {
|
|
44
54
|
id: string;
|
|
@@ -64,6 +74,19 @@ interface JobState {
|
|
|
64
74
|
lastRun: Record<string, string>;
|
|
65
75
|
runs: JobRun[];
|
|
66
76
|
}
|
|
77
|
+
/** One UTC day of AI spend for an app. */
|
|
78
|
+
interface UsageDay {
|
|
79
|
+
/** UTC calendar day, `YYYY-MM-DD`. */
|
|
80
|
+
date: string;
|
|
81
|
+
/** Sum of the `costUsd` the provider reported for that day's turns. */
|
|
82
|
+
costUsd: number;
|
|
83
|
+
/** Turns that reported a cost (turns without one are not counted). */
|
|
84
|
+
turns: number;
|
|
85
|
+
}
|
|
86
|
+
/** Host-side AI spend for an app, oldest day first, trimmed to the retention window. */
|
|
87
|
+
interface UsageState {
|
|
88
|
+
days: UsageDay[];
|
|
89
|
+
}
|
|
67
90
|
interface InstallRecord {
|
|
68
91
|
name: string;
|
|
69
92
|
version: string;
|
|
@@ -93,6 +116,7 @@ declare class Store {
|
|
|
93
116
|
appRecordPath(name: string): string;
|
|
94
117
|
appChatStatePath(name: string): string;
|
|
95
118
|
appJobStatePath(name: string): string;
|
|
119
|
+
appUsagePath(name: string): string;
|
|
96
120
|
registryCachePath(name: string): string;
|
|
97
121
|
readGlobalConfig(): Promise<GlobalConfig>;
|
|
98
122
|
writeGlobalConfig(config: GlobalConfig): Promise<void>;
|
|
@@ -104,10 +128,149 @@ declare class Store {
|
|
|
104
128
|
writeChatState(name: string, state: ChatState): Promise<void>;
|
|
105
129
|
readJobState(name: string): Promise<JobState>;
|
|
106
130
|
writeJobState(name: string, state: JobState): Promise<void>;
|
|
131
|
+
readUsageState(name: string): Promise<UsageState>;
|
|
132
|
+
writeUsageState(name: string, state: UsageState): Promise<void>;
|
|
107
133
|
listInstalled(): Promise<InstallRecord[]>;
|
|
108
134
|
}
|
|
109
135
|
//#endregion
|
|
136
|
+
//#region src/usage.d.ts
|
|
137
|
+
/** Days of history kept in usage.json; older days are dropped on write. */
|
|
138
|
+
declare const USAGE_RETENTION_DAYS = 90;
|
|
139
|
+
/** The UTC calendar day (`YYYY-MM-DD`) a timestamp falls in. */
|
|
140
|
+
declare function utcDay(at?: Date): string;
|
|
141
|
+
/** Money for humans: sub-dollar spend needs the extra digits, larger sums don't. */
|
|
142
|
+
declare function formatUsd(value: number): string;
|
|
143
|
+
/** A summary of an app's recorded spend, as served by GET /api/apps/:name/usage. */
|
|
144
|
+
interface UsageSummary {
|
|
145
|
+
/** The UTC day "today" refers to. */
|
|
146
|
+
date: string;
|
|
147
|
+
/** Spend recorded so far today. */
|
|
148
|
+
todayCostUsd: number;
|
|
149
|
+
/** Turns recorded so far today. */
|
|
150
|
+
todayTurns: number;
|
|
151
|
+
/** Spend across the whole retention window. */
|
|
152
|
+
totalCostUsd: number;
|
|
153
|
+
/** Days kept in the retention window. */
|
|
154
|
+
retentionDays: number;
|
|
155
|
+
/** Daily series, oldest first; days with no spend are absent. */
|
|
156
|
+
days: UsageDay[];
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Per-app, per-UTC-day AI spend, persisted as OS bookkeeping in the app
|
|
160
|
+
* container (`apps/<name>/usage.json`, next to jobs.json and chat.json).
|
|
161
|
+
*
|
|
162
|
+
* Cost arrives from the `result` event's `costUsd`, which the SDK contract
|
|
163
|
+
* marks optional: engines that do not report cost simply contribute nothing.
|
|
164
|
+
* Writes are serialized per app so concurrent turns cannot lose an update,
|
|
165
|
+
* and each app has its own queue so a slow write for one app never delays
|
|
166
|
+
* another.
|
|
167
|
+
*/
|
|
168
|
+
declare class UsageRecorder {
|
|
169
|
+
private readonly store;
|
|
170
|
+
private readonly retentionDays;
|
|
171
|
+
/** Serializes usage.json read-modify-write per app. */
|
|
172
|
+
private readonly queues;
|
|
173
|
+
constructor(store: Store, retentionDays?: number);
|
|
174
|
+
/**
|
|
175
|
+
* Adds one turn's cost to the app's total for the UTC day of `at`.
|
|
176
|
+
* A missing, non-finite, or negative `costUsd` is ignored (no turn counted,
|
|
177
|
+
* no file written) so a provider that omits cost cannot corrupt the totals.
|
|
178
|
+
*/
|
|
179
|
+
record(appName: string, costUsd: number | undefined, at?: Date): Promise<void>;
|
|
180
|
+
/** The app's recorded days, oldest first, with expired days filtered out. */
|
|
181
|
+
read(appName: string, at?: Date): Promise<UsageState>;
|
|
182
|
+
/** Today's spend plus the daily series, for the dashboard and the usage endpoint. */
|
|
183
|
+
summary(appName: string, at?: Date): Promise<UsageSummary>;
|
|
184
|
+
/** Drops days outside the retention window and normalizes ordering/values. */
|
|
185
|
+
private prune;
|
|
186
|
+
private enqueue;
|
|
187
|
+
}
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/budget.d.ts
|
|
190
|
+
/**
|
|
191
|
+
* Stable discriminator on the thrown error. Apps see the host's error object
|
|
192
|
+
* across a package boundary (and, once `AppContext` becomes RPC, across a
|
|
193
|
+
* process boundary), so the reliable check is this code — not `instanceof`.
|
|
194
|
+
*/
|
|
195
|
+
declare const BUDGET_EXCEEDED_CODE = "budget_exceeded";
|
|
196
|
+
/** Today's spend for an app measured against its cap. */
|
|
197
|
+
interface BudgetStatus {
|
|
198
|
+
/** The UTC calendar day the numbers cover. */
|
|
199
|
+
date: string;
|
|
200
|
+
/** Spend recorded for that day. */
|
|
201
|
+
spentUsd: number;
|
|
202
|
+
/** The configured daily cap; absent when the app has none. */
|
|
203
|
+
dailyUsd?: number;
|
|
204
|
+
/** Spend left before sessions are refused; absent when there is no cap. */
|
|
205
|
+
remainingUsd?: number;
|
|
206
|
+
/** True only when a cap is set and the day's spend has reached it. */
|
|
207
|
+
overBudget: boolean;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Refusal from the `createSession` budget gate.
|
|
211
|
+
*
|
|
212
|
+
* Extends the SDK's `AppSdkError` so it stays inside the taxonomy apps already
|
|
213
|
+
* catch, and carries a `code` plus the numbers behind the decision so an app
|
|
214
|
+
* can degrade gracefully (queue the work, skip the run, tell the user) instead
|
|
215
|
+
* of treating a spend cap as a crash. The message is written to stand alone:
|
|
216
|
+
* it is often the only text a human sees, rendered somewhere far from the host.
|
|
217
|
+
*/
|
|
218
|
+
declare class BudgetExceededError extends AppSdkError {
|
|
219
|
+
readonly code: typeof BUDGET_EXCEEDED_CODE;
|
|
220
|
+
readonly appName: string;
|
|
221
|
+
/** UTC day the cap applies to. */
|
|
222
|
+
readonly date: string;
|
|
223
|
+
/** The configured cap in USD. */
|
|
224
|
+
readonly limitUsd: number;
|
|
225
|
+
/** Spend already recorded for `date`. */
|
|
226
|
+
readonly spentUsd: number;
|
|
227
|
+
constructor(appName: string, params: {
|
|
228
|
+
date: string;
|
|
229
|
+
limitUsd: number;
|
|
230
|
+
spentUsd: number;
|
|
231
|
+
});
|
|
232
|
+
/** Duck-typed check that survives duplicate copies of this module. */
|
|
233
|
+
static is(err: unknown): err is BudgetExceededError;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* The cap the *host* has set for an app, or undefined when there is none.
|
|
237
|
+
*
|
|
238
|
+
* Read only from `GlobalConfig.budgets`, which is deliberately not part of the
|
|
239
|
+
* config layers merged into `ctx.config`: an app must not be able to raise its
|
|
240
|
+
* own ceiling through its manifest defaults or `apps/<name>/config.json`.
|
|
241
|
+
*
|
|
242
|
+
* A hand-edited cap that is not a finite number >= 0 is ignored rather than
|
|
243
|
+
* treated as zero — a typo in config.json should not brick an app, and the
|
|
244
|
+
* dashboard/settings write path validates before it ever gets stored.
|
|
245
|
+
*/
|
|
246
|
+
declare function appBudget(config: GlobalConfig, name: string): number | undefined;
|
|
247
|
+
/** Combines an already-read usage summary with a cap; no I/O. */
|
|
248
|
+
declare function budgetStatusFor(summary: UsageSummary, dailyUsd: number | undefined): BudgetStatus;
|
|
249
|
+
/** Today's spend against the app's cap, for the gate, the dashboard, and settings. */
|
|
250
|
+
declare function budgetStatus(store: Store, usage: UsageRecorder, name: string, at?: Date): Promise<BudgetStatus>;
|
|
251
|
+
/**
|
|
252
|
+
* Fail-closed gate: throws `BudgetExceededError` when the app has already
|
|
253
|
+
* spent its daily cap. Evaluated fresh on every call, so a cap edit or a
|
|
254
|
+
* mid-day overspend takes effect without restarting the app.
|
|
255
|
+
*/
|
|
256
|
+
declare function assertWithinBudget(store: Store, usage: UsageRecorder, name: string, at?: Date): Promise<void>;
|
|
257
|
+
/**
|
|
258
|
+
* Sets (or, with `undefined`, clears) an app's daily cap in the host config.
|
|
259
|
+
* Rejects anything that is not a finite, non-negative dollar amount; `0` is a
|
|
260
|
+
* legitimate value meaning "no AI spend at all for this app".
|
|
261
|
+
*/
|
|
262
|
+
declare function setAppBudget(store: Store, name: string, dailyUsd: number | undefined): Promise<void>;
|
|
263
|
+
//#endregion
|
|
264
|
+
//#region src/compat.d.ts
|
|
265
|
+
/**
|
|
266
|
+
* Enforces the manifest's declared `sdkVersion` range against the SDK
|
|
267
|
+
* this host is built with. Apps that declare nothing are accepted.
|
|
268
|
+
*/
|
|
269
|
+
declare function checkSdkCompatibility(manifest: Pick<AppManifest, "name" | "sdkVersion">): void;
|
|
270
|
+
//#endregion
|
|
110
271
|
//#region src/installer.d.ts
|
|
272
|
+
/** Lifecycle scripts stay off: installing an app must not execute arbitrary code. */
|
|
273
|
+
declare function installDependencies(dir: string): Promise<void>;
|
|
111
274
|
interface InstallOptions {
|
|
112
275
|
/** Expected app name (enforced against the manifest when set). */
|
|
113
276
|
expectName?: string;
|
|
@@ -138,6 +301,50 @@ interface LoadedApp {
|
|
|
138
301
|
*/
|
|
139
302
|
declare function loadApp(repoDir: string): Promise<LoadedApp>;
|
|
140
303
|
//#endregion
|
|
304
|
+
//#region src/portability.d.ts
|
|
305
|
+
/**
|
|
306
|
+
* Container portability. An app is only half-portable on its own: the
|
|
307
|
+
* container holds sessionId pointers, but the conversation transcripts live
|
|
308
|
+
* with the engine (keyed by the app's data dir as cwd). An export therefore
|
|
309
|
+
* snapshots the container *plus* the transcripts, engine-neutrally, and the
|
|
310
|
+
* import re-keys them to this machine's data dir.
|
|
311
|
+
*
|
|
312
|
+
* Archive layout (tar.gz):
|
|
313
|
+
* alashi-export.json { formatVersion, exportedAt, app, transcripts }
|
|
314
|
+
* container/ the contents of $ALASHI_HOME/apps/<name>/
|
|
315
|
+
* transcripts/<sessionId>.jsonl
|
|
316
|
+
*/
|
|
317
|
+
declare const EXPORT_FORMAT_VERSION = 1;
|
|
318
|
+
interface ExportMetadata {
|
|
319
|
+
formatVersion: number;
|
|
320
|
+
exportedAt: string;
|
|
321
|
+
app: InstallRecord;
|
|
322
|
+
/** Session ids that have a transcripts/<id>.jsonl entry in the archive. */
|
|
323
|
+
transcripts: string[];
|
|
324
|
+
}
|
|
325
|
+
interface ExportOptions {
|
|
326
|
+
/** Archive path; defaults to ./<name>-<version>.tar.gz under the cwd. */
|
|
327
|
+
out?: string;
|
|
328
|
+
}
|
|
329
|
+
interface ExportResult {
|
|
330
|
+
archivePath: string;
|
|
331
|
+
record: InstallRecord;
|
|
332
|
+
transcriptCount: number;
|
|
333
|
+
warnings: string[];
|
|
334
|
+
}
|
|
335
|
+
interface ImportResult {
|
|
336
|
+
record: InstallRecord;
|
|
337
|
+
transcriptCount: number;
|
|
338
|
+
warnings: string[];
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Packs apps/<name>/ plus the transcripts of every conversation that ever had
|
|
342
|
+
* a turn into a single self-contained tar.gz.
|
|
343
|
+
*/
|
|
344
|
+
declare function exportApp(store: Store, provider: AIProvider, name: string, options?: ExportOptions): Promise<ExportResult>;
|
|
345
|
+
/** Restores a container exported by {@link exportApp}, plus its transcripts. */
|
|
346
|
+
declare function importApp(store: Store, provider: AIProvider, archivePath: string): Promise<ImportResult>;
|
|
347
|
+
//#endregion
|
|
141
348
|
//#region src/registry.d.ts
|
|
142
349
|
interface RegistryApp {
|
|
143
350
|
name: string;
|
|
@@ -175,8 +382,12 @@ declare function resolveApp(store: Store, appName: string): Promise<ResolvedApp>
|
|
|
175
382
|
* Setup state shapes the session defaults: unconfigured MCP servers are
|
|
176
383
|
* dropped (with their tools) so interpolation never hits a missing key, and
|
|
177
384
|
* while required keys are missing the scoped provider fails closed.
|
|
385
|
+
*
|
|
386
|
+
* When a `usage` recorder is supplied, every session opened through the
|
|
387
|
+
* returned context reports its per-turn cost to it, and the app's host-set
|
|
388
|
+
* daily spend cap (if any) is enforced on every createSession.
|
|
178
389
|
*/
|
|
179
|
-
declare function buildAppContext(store: Store, manifest: AppManifest, provider: AIProvider): Promise<AppContext>;
|
|
390
|
+
declare function buildAppContext(store: Store, manifest: AppManifest, provider: AIProvider, usage?: UsageRecorder): Promise<AppContext>;
|
|
180
391
|
//#endregion
|
|
181
392
|
//#region src/scheduler.d.ts
|
|
182
393
|
interface SchedulableApp {
|
|
@@ -235,10 +446,17 @@ interface AppServer {
|
|
|
235
446
|
* The host HTTP surface:
|
|
236
447
|
* GET / dashboard of installed apps
|
|
237
448
|
* GET /settings/:name per-app settings page (config form)
|
|
238
|
-
* GET /api/apps installed apps as JSON
|
|
449
|
+
* GET /api/apps installed apps as JSON, each with todayCostUsd
|
|
450
|
+
* and its daily spend cap / over-budget state
|
|
451
|
+
* GET /api/apps/:name/usage recorded AI spend: today's total, a daily series,
|
|
452
|
+
* and the budget status
|
|
453
|
+
* GET /api/apps/:name/budget today's spend against the app's daily cap
|
|
454
|
+
* PUT /api/apps/:name/budget { dailyUsd: number | null } -> set or clear the cap
|
|
455
|
+
* (a host setting; apps cannot set their own)
|
|
239
456
|
* POST /api/apps/:name/chat { message, conversationId? } -> SSE stream of AgentEvents
|
|
240
457
|
* (omit conversationId to start a new conversation; the
|
|
241
|
-
* id is echoed in the x-conversation-id response header
|
|
458
|
+
* id is echoed in the x-conversation-id response header;
|
|
459
|
+
* 402 when the app is over its daily spend cap)
|
|
242
460
|
* GET /api/apps/:name/chat/conversations conversations, most recent first
|
|
243
461
|
* POST /api/apps/:name/chat/conversations { sessionId?, title? } -> create a conversation,
|
|
244
462
|
* optionally adopting an existing provider session (e.g. an
|
|
@@ -260,5 +478,5 @@ interface AppServer {
|
|
|
260
478
|
*/
|
|
261
479
|
declare function createServer(store: Store, provider: AIProvider): AppServer;
|
|
262
480
|
//#endregion
|
|
263
|
-
export { type AppServer, type AppSourceSpec, type GlobalConfig, type InstallOptions, type InstallRecord, type LoadedApp, type RegistryApp, type RegistryIndex, type RegistryRef, type RemoveOptions, type ResolvedApp, type SourceArg, Store, addRegistry, buildAppContext, checkSdkCompatibility, createServer, fetchSource, installApp, loadApp, parseSourceArg, readCachedIndex, refreshRegistry, removeApp, removeRegistry, resolveApp, updateApp, updateSource };
|
|
481
|
+
export { type AppBudget, type AppServer, type AppSourceSpec, BUDGET_EXCEEDED_CODE, BudgetExceededError, type BudgetStatus, EXPORT_FORMAT_VERSION, type ExportMetadata, type ExportOptions, type ExportResult, type GlobalConfig, type ImportResult, type InstallOptions, type InstallRecord, type LoadedApp, type RegistryApp, type RegistryIndex, type RegistryRef, type RemoveOptions, type ResolvedApp, type SourceArg, Store, USAGE_RETENTION_DAYS, type UsageDay, UsageRecorder, type UsageState, type UsageSummary, addRegistry, appBudget, assertWithinBudget, budgetStatus, budgetStatusFor, buildAppContext, checkSdkCompatibility, createServer, exportApp, fetchSource, formatUsd, importApp, installApp, installDependencies, loadApp, parseSourceArg, readCachedIndex, refreshRegistry, removeApp, removeRegistry, resolveApp, setAppBudget, updateApp, updateSource, utcDay };
|
|
264
482
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/sources.ts","../src/store.ts","../src/usage.ts","../src/budget.ts","../src/compat.ts","../src/installer.ts","../src/loader.ts","../src/portability.ts","../src/registry.ts","../src/runtime.ts","../src/scheduler.ts","../src/server.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
export { Store, addRegistry, buildAppContext, checkSdkCompatibility, createServer, fetchSource, installApp, loadApp, parseSourceArg, readCachedIndex, refreshRegistry, removeApp, removeRegistry, resolveApp, updateApp, updateSource };
|
|
1
|
+
import { A as UsageRecorder, C as BudgetExceededError, D as budgetStatusFor, E as budgetStatus, M as utcDay, O as setAppBudget, S as BUDGET_EXCEEDED_CODE, T as assertWithinBudget, _ as fetchSource, a as EXPORT_FORMAT_VERSION, b as loadApp, c as installApp, d as updateApp, f as addRegistry, g as resolveApp, h as removeRegistry, j as formatUsd, k as USAGE_RETENTION_DAYS, l as installDependencies, m as refreshRegistry, n as createServer, o as exportApp, p as readCachedIndex, r as buildAppContext, s as importApp, t as Store, u as removeApp, v as parseSourceArg, w as appBudget, x as checkSdkCompatibility, y as updateSource } from "./store-VoJe_U1Q.mjs";
|
|
2
|
+
export { BUDGET_EXCEEDED_CODE, BudgetExceededError, EXPORT_FORMAT_VERSION, Store, USAGE_RETENTION_DAYS, UsageRecorder, addRegistry, appBudget, assertWithinBudget, budgetStatus, budgetStatusFor, buildAppContext, checkSdkCompatibility, createServer, exportApp, fetchSource, formatUsd, importApp, installApp, installDependencies, loadApp, parseSourceArg, readCachedIndex, refreshRegistry, removeApp, removeRegistry, resolveApp, setAppBudget, updateApp, updateSource, utcDay };
|