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.
72 lines • 3.55 kB
JavaScript
import { readFile, writeFile } from 'node:fs/promises';
import { homedir } from 'node:os';
import { join } from 'node:path';
/**
* Claude Code's folder trust: read it, and grant it for a web run (#1493).
*
* The CLI asks its one-time "do you trust this folder?" question interactively, which a
* `--cloud` start under the daemon's pty can never answer (#1314) — a web agent on an
* untrusted project was doomed before it started. Trust was read-only here at first
* (#1318: warn in the dashboard, name the one-time manual step), but any manual setup
* step breaks the "click and it works" story for web runs, so #1493 reversed that
* decision: the framework now writes the CLI's own trust record before the hand-off.
* Starting a web agent on a project is itself a trust decision by the user — the write
* automates consent already given, it does not invent it.
*
* Reading stays fail-quiet: a missing, unreadable or reshaped `~/.claude.json` answers
* "unknown". Writing is the opposite — an existing file that does not parse is left
* alone (throw, never destroy the CLI's config), and the caller falls back to the
* interactive-advice path.
*/
/** Where the Claude Code CLI records per-directory trust. */
function claudeConfigPath(home = homedir()) {
return join(home, '.claude.json');
}
/** Whether the CLI trusts `root`. Unknown when the config is missing or not understood. */
export async function readClaudeTrust(root, path = claudeConfigPath()) {
let config;
try {
config = JSON.parse(await readFile(path, 'utf8'));
}
catch {
return { known: false, trusted: false };
}
if (typeof config !== 'object' || config === null)
return { known: false, trusted: false };
const projects = config.projects;
if (typeof projects !== 'object' || projects === null || Array.isArray(projects))
return { known: false, trusted: false };
const entry = projects[root];
const trusted = typeof entry === 'object' && entry !== null && entry.hasTrustDialogAccepted === true;
return { known: true, trusted };
}
/**
* Record that the CLI can trust `root` (#1493): the exact record the CLI itself writes
* when the user accepts its dialog, so the dialog never fires. Everything else in the
* config — other projects, top-level settings, the entry's own sibling fields — is
* preserved. A missing file is created; an existing file that does not parse is refused
* with a throw rather than overwritten, since the file is the CLI's, not ours.
*/
export async function writeClaudeTrust(root, path = claudeConfigPath()) {
let raw;
try {
raw = await readFile(path, 'utf8');
}
catch {
// No file yet: create one carrying only the trust record; the CLI accepts and extends it.
}
let config = {};
if (raw !== undefined) {
const parsed = JSON.parse(raw);
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
throw new Error(`[framework] ${path} exists but is not a JSON object`);
config = parsed;
}
const projects = typeof config.projects === 'object' && config.projects !== null && !Array.isArray(config.projects)
? config.projects
: {};
const entry = typeof projects[root] === 'object' && projects[root] !== null ? projects[root] : {};
config.projects = { ...projects, [root]: { ...entry, hasTrustDialogAccepted: true } };
await writeFile(path, JSON.stringify(config, null, 2), 'utf8');
}
//# sourceMappingURL=claude-trust.js.map