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.
98 lines • 4.15 kB
JavaScript
import { platform } from 'node:os';
import { delimiter, join } from 'node:path';
/**
* The editors the picker can offer (#727), probed by their CLI launcher on PATH. Order is the
* display order. `$FRAMEWORK_EDITOR` and a hand-typed value stay valid beyond this list; this is
* only what auto-detection looks for.
*/
export const KNOWN_EDITORS = [
{ bin: 'code', label: 'VS Code' },
{ bin: 'code-insiders', label: 'VS Code Insiders' },
{ bin: 'cursor', label: 'Cursor' },
{ bin: 'windsurf', label: 'Windsurf' },
{ bin: 'zed', label: 'Zed' },
{ bin: 'subl', label: 'Sublime Text' },
{ bin: 'webstorm', label: 'WebStorm' },
{ bin: 'idea', label: 'IntelliJ IDEA' },
{ bin: 'nvim', label: 'Neovim' },
{ bin: 'vim', label: 'Vim' },
{ bin: 'emacs', label: 'Emacs' },
];
/**
* An {@link EditorProbe} that looks each launcher up on the real PATH: an executable match in any
* PATH dir (trying PATHEXT suffixes on Windows). Pure lookup, nothing is spawned.
*/
function nodeEditorProbe(env = process.env, os = platform()) {
const dirs = (env.PATH ?? '').split(delimiter).filter(Boolean);
const exts = os === 'win32' ? (env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean) : [''];
return async (bin) => {
const { access, constants } = await import('node:fs/promises');
const flag = os === 'win32' ? constants.F_OK : constants.X_OK;
for (const dir of dirs) {
for (const ext of exts) {
try {
await access(join(dir, bin + ext), flag);
return true;
}
catch {
// not here; keep looking
}
}
}
return false;
};
}
/** The installed subset of {@link KNOWN_EDITORS}, in display order (#727). */
export async function detectEditors(probe = nodeEditorProbe()) {
const hits = await Promise.all(KNOWN_EDITORS.map(async (e) => ((await probe(e.bin)) ? e : null)));
return hits.filter((e) => e !== null);
}
/**
* A {@link SpawnRunner} backed by `spawn`, detached. Resolves on the `spawn` event so a
* long-lived editor (or `explorer`, which exits non-zero even on success) does not block or
* error; rejects on the `error` event (e.g. ENOENT when the command is not on PATH).
*/
function nodeSpawnRunner() {
return (command, args) => new Promise((resolve, reject) => {
void import('node:child_process').then(({ spawn }) => {
const child = spawn(command, args, { stdio: 'ignore', detached: true });
child.on('error', reject);
child.on('spawn', () => {
child.unref();
resolve();
});
});
});
}
/** The OS command that reveals a path in the file manager. */
export function fileManagerCommand(path, os = platform()) {
if (os === 'darwin')
return { command: 'open', args: [path] };
if (os === 'win32')
return { command: 'explorer', args: [path] };
return { command: 'xdg-open', args: [path] };
}
/** The command to open a path in an editor: `$FRAMEWORK_EDITOR` when set, else the VS Code CLI. */
export function editorCommand(path, editor = process.env.FRAMEWORK_EDITOR) {
const bin = editor && editor.trim() ? editor.trim() : 'code';
return { command: bin, args: [path] };
}
/**
* Open the project at `cwd` in the file manager or an editor. Failures are values, never throws.
* `editor` (#727) is the stored preference; when unset {@link editorCommand} falls back to
* `$FRAMEWORK_EDITOR`, then `code`.
*/
export async function openInApp(cwd, target, agent = nodeSpawnRunner(), editor) {
const { command, args } = target === 'editor' ? editorCommand(cwd, editor) : fileManagerCommand(cwd);
try {
await agent(command, args);
return { ok: true };
}
catch (err) {
if (err?.code === 'ENOENT') {
return { ok: false, error: `"${command}" was not found on PATH` };
}
return { ok: false, error: err instanceof Error ? err.message : 'failed to open' };
}
}
//# sourceMappingURL=open-in-app.js.map