framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
157 lines • 7.37 kB
JavaScript
import { contextDiscord, contextPreferences, resolveProjectPath } from './context.js';
import { detectEditors } from '../dashboard/open-in-app.js';
import { readProjectPresets, writeProjectPresets } from '../project-presets.js';
/** The user's stored dashboard preferences, or `{}` on a host that has none / does not store them. */
export async function onPreferences() {
const store = contextPreferences();
if (!store)
return {};
return store.read().catch(() => ({}));
}
/** Persist the dashboard preferences (sanitized in the store). No-op-safe on a public host. */
export async function savePreferences(preferences) {
const store = contextPreferences();
if (!store)
return { ok: false, error: 'preferences are not enabled on this server' };
// A failed write returns the advertised typed error rather than rejecting the RPC,
// so the client handles it the same as the not-enabled case (both `{ ok: false }`).
try {
await store.save(preferences);
return { ok: true };
}
catch {
return { ok: false, error: 'failed to save preferences' };
}
}
/**
* Merge the keys the caller changed into the stored preferences (#1148), and hand back what is
* now stored. The write half of the fix for a stale tab reverting settings it never touched:
* {@link savePreferences} replaces the whole block, so a client's snapshot overwrote whatever
* anyone else had changed since it loaded. Returning the merged result also lets the caller
* adopt the truth it just wrote against, so a tab converges instead of staying stale.
*/
export async function patchPreferences(patch) {
const store = contextPreferences();
if (!store?.patch)
return { ok: false, error: 'preferences are not enabled on this server' };
// Typed error rather than a rejection, like `savePreferences` — one shape for both failures.
try {
return { ok: true, preferences: await store.patch(patch) };
}
catch {
return { ok: false, error: 'failed to save preferences' };
}
}
/**
* One project's own run options (#840), or `{}` when it overrides nothing / the host stores
* none. Separate from {@link onPreferences} rather than folded into it: the client needs the
* two tiers apart to know which one a toggle should write to.
*/
export async function onProjectPreferences(projectId) {
const store = contextPreferences();
if (!store?.readProject)
return {};
return store.readProject(projectId).catch(() => ({}));
}
/** Persist one project's run options (#840), sanitized in the store. No-op-safe on a public host. */
export async function saveProjectPreferences(projectId, preferences) {
const store = contextPreferences();
if (!store?.saveProject)
return { ok: false, error: 'preferences are not enabled on this server' };
try {
await store.saveProject(projectId, preferences);
return { ok: true };
}
catch {
return { ok: false, error: 'failed to save preferences' };
}
}
/** {@link patchPreferences} for one project's run options (#1148). */
export async function patchProjectPreferences(projectId, patch) {
const store = contextPreferences();
if (!store?.patchProject)
return { ok: false, error: 'preferences are not enabled on this server' };
try {
return { ok: true, preferences: await store.patchProject(projectId, patch) };
}
catch {
return { ok: false, error: 'failed to save preferences' };
}
}
/**
* A project's shared custom presets (#1025), committed into its `.the-framework/` so they travel
* with the repo — the team-shared counterpart to the user-tier {@link onPreferences} presets. Read
* from the project's own checkout, so this resolves the project id to its workspace path rather than
* touching the home registry. `[]` on a public host (no local checkout) or an unknown project.
*/
export async function onProjectPresets(projectId) {
if (!contextPreferences())
return [];
const cwd = await resolveProjectPath(projectId);
if (!cwd)
return [];
return readProjectPresets(cwd).catch(() => []);
}
/** Persist a project's shared custom presets into its `.the-framework/` (#1025). No-op-safe on a public host. */
export async function saveProjectPresets(projectId, presets) {
if (!contextPreferences())
return { ok: false, error: 'preferences are not enabled on this server' };
const cwd = await resolveProjectPath(projectId);
if (!cwd)
return { ok: false, error: 'unknown project' };
try {
await writeProjectPresets(cwd, presets);
return { ok: true };
}
catch {
return { ok: false, error: 'failed to save presets' };
}
}
/**
* The editors installed on this server (#727), for the "Preferred editor" picker. Detection reads
* the daemon's own PATH, so it is gated on the same store presence as the other preference RPCs:
* a public host (the relay) has no local checkout to open anyway, so it returns `[]`.
*/
export async function onEditors() {
if (!contextPreferences())
return [];
return detectEditors().catch(() => []);
}
/**
* Whether the daemon has the Discord credentials (#948/#1095). The toggles are per-user
* preferences, but delivery needs a webhook / a bot token on the daemon — without this read the
* dashboard let you switch on a channel that delivers nothing and lit the bell for it.
*
* Only presence is reported, never the values, and that is the whole contract: a credential set
* from the dashboard (#1095) lives daemon-side and is never read back to a browser, so this stays
* booleans plus where each came from. Gated like the other preference RPCs, so a public host
* reports both absent.
*/
export async function onNotifyChannels() {
const discord = contextDiscord();
if (!contextPreferences() || !discord)
return { discordWebhook: false, discordBot: false, sources: {}, editable: false };
const sources = await discord.status().catch(() => ({}));
return { discordWebhook: sources.webhook !== undefined, discordBot: sources.botToken !== undefined, sources, editable: true };
}
/**
* Store (or clear) the Discord credentials from the dashboard (#1095) — the step that used to
* need an edit to the daemon's environment and a restart, which made it the one onboarding step
* you could not finish in-product.
*
* Write-only on purpose: there is no companion read. The value goes daemon-side, and the browser
* only ever learns that it is there ({@link onNotifyChannels}). The store applies it live, so the
* bot connects and the watchers start on the save rather than at the next daemon start.
*
* The exposure is bounded by the guard the rest of this surface already sits behind: on a
* non-loopback bind every route requires the shared token (#1051), and anyone through that guard
* can start runs — strictly more than setting a webhook URL. A public host wires no store, so
* this refuses there.
*/
export async function saveDiscordCredentials(patch) {
const discord = contextDiscord();
if (!discord)
return { ok: false, error: 'Discord cannot be configured on this server.' };
return discord.save(patch).catch(() => ({ ok: false, error: 'failed to save' }));
}
//# sourceMappingURL=preferences.telefunc.js.map