@numairbaseer/scifcode 0.1.2

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/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Proprietary
2
+
3
+ Copyright (c) 2026.
4
+
5
+ All rights reserved.
package/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # Scifcode
2
+
3
+ Scifcode is the user-facing configuration wrapper for the Scifcode coding agent powered by Poolside Laguna.
4
+
5
+ This v0.1 is intentionally small: it installs one global `scifcode` command, configures the Scifcode runtime to use Laguna, and launches the coding agent with Scifcode defaults.
6
+
7
+ ## Install
8
+
9
+ Scifcode requires Node.js `>=22.19.0`. It depends on `@numairbaseer/scifcode-runtime`,
10
+ which installs the compiled runtime for the current operating system and CPU.
11
+ Users do not need Bun or a source checkout.
12
+
13
+ ```bash
14
+ npm install -g @numairbaseer/scifcode
15
+ ```
16
+
17
+ Supported runtime packages cover:
18
+
19
+ - macOS: arm64 and x64
20
+ - Linux: arm64 and x64, with glibc and musl variants
21
+ - Windows: arm64 and x64
22
+ - baseline x64 builds for CPUs without AVX2
23
+
24
+ ## Configure
25
+
26
+ Scifcode does not store API keys in the repo. Set the API key or bearer token
27
+ for your configured endpoint in the current shell:
28
+
29
+ ```bash
30
+ export SCIFCODE_API_KEY="endpoint_or_modal_bearer_token"
31
+ ```
32
+
33
+ Scifcode uses the project's fixed Modal endpoint:
34
+
35
+ ```bash
36
+ https://danishshaik--reservoir-vllm-serve.modal.run/v1
37
+ ```
38
+
39
+ You usually only need to set `SCIFCODE_API_KEY`. The provider and model are
40
+ fixed to the Scifcode Modal deployment.
41
+
42
+ ## Run
43
+
44
+ ```bash
45
+ cd your-repo
46
+ scifcode
47
+ ```
48
+
49
+ ## One-shot prompt
50
+
51
+ ```bash
52
+ scifcode -p "Review the current diff for correctness and missing tests."
53
+ ```
54
+
55
+ ## Check your installation
56
+
57
+ ```bash
58
+ scifcode doctor
59
+ ```
60
+
61
+ `doctor` creates or updates Scifcode's provider config, checks whether the runtime can be resolved, and verifies that an API key is present.
62
+
63
+ ## Initialize config explicitly
64
+
65
+ ```bash
66
+ scifcode init
67
+ ```
68
+
69
+ `init` creates or updates:
70
+
71
+ ```text
72
+ ~/.config/scifcode/scifcode.json
73
+ ```
74
+
75
+ Normal `scifcode` and `scifcode doctor` commands also ensure this config exists so the first Laguna smoke test stays short.
76
+
77
+ ## Environment variables
78
+
79
+ | Variable | Required | Default | Description |
80
+ | --- | ---: | --- | --- |
81
+ | `SCIFCODE_API_KEY` | yes | none | API key or bearer token for the configured endpoint |
82
+ | `SCIFCODE_CONTEXT_WINDOW` | no | `32768` | Context window reported for `scifcode-1.0` |
83
+ | `SCIFCODE_MAX_TOKENS` | no | `8192` | Maximum output tokens reported for `scifcode-1.0` |
84
+ | `SCIFCODE_RUNTIME_BIN` | no | installed platform package | Override path to a compiled Scifcode runtime binary |
85
+ | `SCIFCODE_SOURCE_DIR` | no | adjacent `../scifcode` | Development-only path to the Scifcode source fork |
86
+ | `SCIFCODE_BUN_BIN` | no | auto-detected | Development-only Bun executable used to run the fork from source |
87
+
88
+ Scifcode also loads private env defaults from:
89
+
90
+ ```text
91
+ ~/.scifcode/env
92
+ ./.scifcode.env
93
+ ```
94
+
95
+ Shell environment variables win over these files. Keep API keys in the global
96
+ file or an ignored project `.scifcode.env`, not in committed source.
97
+
98
+ ## Troubleshooting
99
+
100
+ ### `Scifcode requires SCIFCODE_API_KEY`
101
+
102
+ Set your key:
103
+
104
+ ```bash
105
+ export SCIFCODE_API_KEY="endpoint_or_modal_bearer_token"
106
+ ```
107
+
108
+ Then retry:
109
+
110
+ ```bash
111
+ scifcode doctor
112
+ scifcode
113
+ ```
114
+
115
+ ### Invalid Scifcode config
116
+
117
+ Scifcode writes to:
118
+
119
+ ```text
120
+ ~/.config/scifcode/scifcode.json
121
+ ```
122
+
123
+ If this file contains invalid JSON, fix it or move it aside and rerun:
124
+
125
+ ```bash
126
+ scifcode init
127
+ ```
128
+
129
+ ### Modal A100 endpoint
130
+
131
+ Scifcode can also point at an OpenAI Chat Completions-compatible endpoint
132
+ served from Modal. See `docs/modal-a100.md` for a Laguna XS.2 vLLM scaffold.
133
+
134
+ ## Security
135
+
136
+ Scifcode v0.1 does not store API keys in the repo. It writes an environment
137
+ variable reference such as `{env:SCIFCODE_API_KEY}` into its runtime config and
138
+ passes the current process environment to the agent process. `LAGUNA_API_KEY` is
139
+ still accepted as a legacy fallback.
140
+
141
+ Scifcode launches the installed platform runtime directly with argument arrays
142
+ and does not use shell string execution. Bun/source execution remains available
143
+ only as a local-development fallback.
144
+
145
+ ## Third-party notices
146
+
147
+ Scifcode uses third-party open-source software internally. See `THIRD_PARTY_NOTICES.md`.
@@ -0,0 +1,16 @@
1
+ # Third-Party Notices
2
+
3
+ Scifcode uses third-party open-source software.
4
+
5
+ ## Agent Runtime
6
+
7
+ Scifcode is based on a fork of the OpenCode agent runtime, which is licensed
8
+ under the MIT License.
9
+
10
+ See https://github.com/anomalyco/opencode for the upstream project and license.
11
+
12
+ ## Poolside Laguna
13
+
14
+ Scifcode is configured to use Poolside Laguna models through an API-compatible provider configuration.
15
+
16
+ Use of Laguna models is subject to the applicable Poolside, OpenRouter, or customer API-provider terms, depending on how the customer obtains access.
@@ -0,0 +1,16 @@
1
+ # Scifcode Agent Instructions
2
+
3
+ You are Scifcode, a software engineering assistant optimized for safe, validated code changes.
4
+
5
+ Default workflow:
6
+ 1. Inspect the repository before editing.
7
+ 2. Prefer small, reviewable changes.
8
+ 3. Before modifying files, explain the intended change.
9
+ 4. After editing, run the most relevant tests or checks.
10
+ 5. If tests cannot be run, explain exactly why.
11
+ 6. Summarize changed files and risks.
12
+
13
+ Safety rules:
14
+ - Do not run destructive shell commands without asking.
15
+ - Do not modify secrets, credentials, lockfiles, or deployment config unless directly requested.
16
+ - Do not invent APIs. Inspect existing code first.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/cli.js";
4
+
5
+ main();
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@numairbaseer/scifcode",
3
+ "version": "0.1.2",
4
+ "description": "Scifcode: a coding CLI agent powered by Poolside Laguna.",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/numairbaseer/reservoir.git"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public",
12
+ "registry": "https://registry.npmjs.org"
13
+ },
14
+ "bin": {
15
+ "scifcode": "./bin/scifcode.js"
16
+ },
17
+ "files": [
18
+ "bin",
19
+ "src",
20
+ "assets",
21
+ "README.md",
22
+ "LICENSE",
23
+ "THIRD_PARTY_NOTICES.md"
24
+ ],
25
+ "scripts": {
26
+ "test": "node --test",
27
+ "lint": "node --check bin/scifcode.js && node --check src/cli.js && node --check src/constants.js && node --check src/env-config.js && node --check src/ensure-scifcode-config.js && node --check src/paths.js && node --check src/run-runtime.js",
28
+ "prepack": "npm test && npm run lint"
29
+ },
30
+ "dependencies": {
31
+ "@numairbaseer/scifcode-runtime": "0.1.2"
32
+ },
33
+ "engines": {
34
+ "node": ">=22.19.0"
35
+ },
36
+ "license": "Proprietary"
37
+ }
package/src/cli.js ADDED
@@ -0,0 +1,176 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { ensureScifcodeConfig } from "./ensure-scifcode-config.js";
5
+ import { loadScifcodeEnv } from "./env-config.js";
6
+ import { resolveRuntimeBin, runRuntime } from "./run-runtime.js";
7
+ import {
8
+ API_KEY_ENV_NAME,
9
+ DEFAULT_BASE_URL,
10
+ DEFAULT_MODEL,
11
+ DEFAULT_PROVIDER_NAME,
12
+ LEGACY_API_KEY_ENV_NAME
13
+ } from "./constants.js";
14
+
15
+ export function printHelp(stdout = console.log) {
16
+ stdout(`Scifcode - coding CLI agent powered by Poolside Laguna
17
+
18
+ Usage:
19
+ scifcode Start interactive coding agent
20
+ scifcode -p "prompt" Run one-shot prompt
21
+ scifcode init Initialize local Scifcode runtime config
22
+ scifcode doctor Check installation and configuration
23
+
24
+ Required environment:
25
+ SCIFCODE_API_KEY API key or bearer token for the configured endpoint
26
+
27
+ Optional environment:
28
+ SCIFCODE_RUNTIME_BIN Path to a compiled Scifcode runtime binary
29
+ SCIFCODE_SOURCE_DIR Path to the Scifcode OpenCode fork
30
+ SCIFCODE_BUN_BIN Path to Bun when launching the fork from source
31
+
32
+ Private config files:
33
+ ~/.scifcode/env Default environment loaded before shell env
34
+ ./.scifcode.env Project override loaded before shell env
35
+ `);
36
+ }
37
+
38
+ export function getPackageVersion() {
39
+ const currentFile = fileURLToPath(import.meta.url);
40
+ const srcDir = path.dirname(currentFile);
41
+ const packagePath = path.join(srcDir, "..", "package.json");
42
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
43
+ return pkg.version;
44
+ }
45
+
46
+ function createConsoleWriters(stdout, stderr) {
47
+ return {
48
+ log(message = "") {
49
+ stdout(String(message));
50
+ },
51
+ error(message = "") {
52
+ stderr(String(message));
53
+ }
54
+ };
55
+ }
56
+
57
+ function printInitResult(result, log) {
58
+ log("Scifcode config initialized.");
59
+ log(`Scifcode config path: ${result.configPath}`);
60
+ log(`Provider: ${result.providerName}`);
61
+ log(`Base URL: ${result.baseUrl}`);
62
+ }
63
+
64
+ function printDoctorHeader(configResult, hasApiKey, runtimeResolved, env, log) {
65
+ log("Scifcode doctor");
66
+ log(`${API_KEY_ENV_NAME} configured: ${hasApiKey ? "yes" : "no"}`);
67
+ log(`Base URL: ${configResult.baseUrl}`);
68
+ log(`Scifcode runtime resolved: ${runtimeResolved ? "yes" : "no"}`);
69
+ log(`Scifcode config path: ${configResult.configPath}`);
70
+ log(`Scifcode provider configured: yes`);
71
+ log(`Provider: ${configResult.providerName}`);
72
+ log(`Default model: ${DEFAULT_MODEL}`);
73
+ }
74
+
75
+ export function main(options = {}) {
76
+ const args = options.args || process.argv.slice(2);
77
+ const command = args[0];
78
+ const baseEnv = options.env || process.env;
79
+ const shouldLoadEnv = options.loadEnvFile ?? !options.env;
80
+ const env = shouldLoadEnv
81
+ ? loadScifcodeEnv({
82
+ env: baseEnv,
83
+ cwd: options.cwd,
84
+ homeDir: options.homeDir
85
+ })
86
+ : baseEnv;
87
+ const exit = options.exit || process.exit;
88
+ const ensureScifcodeConfigFn =
89
+ options.ensureScifcodeConfigFn || ensureScifcodeConfig;
90
+ const resolveRuntimeBinFn = options.resolveRuntimeBinFn || resolveRuntimeBin;
91
+ const runRuntimeFn = options.runRuntimeFn || runRuntime;
92
+ const writers = createConsoleWriters(
93
+ options.stdout || console.log,
94
+ options.stderr || console.error
95
+ );
96
+
97
+ if (command === "--help" || command === "-h") {
98
+ printHelp(writers.log);
99
+ exit(0);
100
+ return;
101
+ }
102
+
103
+ if (command === "--version" || command === "-v") {
104
+ writers.log(getPackageVersion());
105
+ exit(0);
106
+ return;
107
+ }
108
+
109
+ if (command === "init") {
110
+ try {
111
+ const result = ensureScifcodeConfigFn({ env });
112
+ printInitResult(result, writers.log);
113
+ exit(0);
114
+ } catch (error) {
115
+ writers.error(error.message);
116
+ exit(1);
117
+ }
118
+ return;
119
+ }
120
+
121
+ if (command === "doctor") {
122
+ let configResult;
123
+ let runtimeResolved = false;
124
+ let runtimeError = null;
125
+
126
+ try {
127
+ configResult = ensureScifcodeConfigFn({ env });
128
+ } catch (error) {
129
+ writers.error("Scifcode doctor");
130
+ writers.error("Scifcode config: error");
131
+ writers.error(error.message);
132
+ exit(1);
133
+ return;
134
+ }
135
+
136
+ try {
137
+ resolveRuntimeBinFn({ env });
138
+ runtimeResolved = true;
139
+ } catch (error) {
140
+ runtimeError = error;
141
+ }
142
+
143
+ const hasApiKey = Boolean(env[API_KEY_ENV_NAME] || env[LEGACY_API_KEY_ENV_NAME]);
144
+ printDoctorHeader(configResult, hasApiKey, runtimeResolved, env, writers.log);
145
+
146
+ if (!runtimeResolved) {
147
+ writers.log("Status: Scifcode runtime resolution failed");
148
+ writers.error(runtimeError.message);
149
+ exit(1);
150
+ return;
151
+ }
152
+
153
+ if (!hasApiKey) {
154
+ writers.log(`Status: missing ${API_KEY_ENV_NAME}`);
155
+ writers.log("");
156
+ writers.log("Set it with:");
157
+ writers.log(` export ${API_KEY_ENV_NAME}="endpoint_or_modal_bearer_token"`);
158
+ exit(1);
159
+ return;
160
+ }
161
+
162
+ writers.log("Status: ok");
163
+ exit(0);
164
+ return;
165
+ }
166
+
167
+ try {
168
+ ensureScifcodeConfigFn({ env });
169
+ } catch (error) {
170
+ writers.error(error.message);
171
+ exit(1);
172
+ return;
173
+ }
174
+
175
+ runRuntimeFn(args, { env, exit, stderr: writers.error });
176
+ }
@@ -0,0 +1,8 @@
1
+ export const DEFAULT_PROVIDER_NAME = "scifcode";
2
+ export const DEFAULT_BASE_URL = "https://danishshaik--reservoir-vllm-serve.modal.run/v1";
3
+ export const DEFAULT_MODEL = "reservoir-modal";
4
+ export const DEFAULT_MODEL_NAME = "scifcode-1.0";
5
+ export const DEFAULT_CONTEXT_WINDOW = 32768;
6
+ export const DEFAULT_MAX_TOKENS = 8192;
7
+ export const API_KEY_ENV_NAME = "SCIFCODE_API_KEY";
8
+ export const LEGACY_API_KEY_ENV_NAME = "LAGUNA_API_KEY";
@@ -0,0 +1,71 @@
1
+ import fs from "node:fs";
2
+ import { API_KEY_ENV_NAME, DEFAULT_BASE_URL, DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_TOKENS, DEFAULT_MODEL, DEFAULT_MODEL_NAME, DEFAULT_PROVIDER_NAME, LEGACY_API_KEY_ENV_NAME } from "./constants.js";
3
+ import { getScifcodeConfigDir, getScifcodeConfigPath } from "./paths.js";
4
+
5
+ function positiveInteger(value, fallback) {
6
+ const parsed = Number.parseInt(value, 10);
7
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
8
+ }
9
+
10
+ export function buildScifcodeModels(env = process.env) {
11
+ return {
12
+ [DEFAULT_MODEL]: {
13
+ name: DEFAULT_MODEL_NAME,
14
+ limit: {
15
+ context: positiveInteger(env.SCIFCODE_CONTEXT_WINDOW, DEFAULT_CONTEXT_WINDOW),
16
+ output: positiveInteger(env.SCIFCODE_MAX_TOKENS, DEFAULT_MAX_TOKENS)
17
+ }
18
+ }
19
+ };
20
+ }
21
+
22
+ export function getApiKeyReference(env = process.env) {
23
+ return env[LEGACY_API_KEY_ENV_NAME] && !env[API_KEY_ENV_NAME]
24
+ ? `{env:${LEGACY_API_KEY_ENV_NAME}}`
25
+ : `{env:${API_KEY_ENV_NAME}}`;
26
+ }
27
+
28
+ export function buildScifcodeProvider(env = process.env) {
29
+ return {
30
+ npm: "@ai-sdk/openai-compatible",
31
+ name: "Scifcode",
32
+ options: {
33
+ baseURL: DEFAULT_BASE_URL,
34
+ apiKey: getApiKeyReference(env)
35
+ },
36
+ models: buildScifcodeModels(env)
37
+ };
38
+ }
39
+
40
+ function readConfig(filePath) {
41
+ if (!fs.existsSync(filePath)) return {};
42
+ try { return JSON.parse(fs.readFileSync(filePath, "utf8")); }
43
+ catch (error) { throw new Error(`Scifcode could not parse ${filePath}. Fix the JSON and retry.\n${error.message}`); }
44
+ }
45
+
46
+ export function ensureScifcodeConfig(options = {}) {
47
+ const env = options.env || process.env;
48
+ const configDir = getScifcodeConfigDir(options.homeDir);
49
+ const configPath = getScifcodeConfigPath(options.homeDir);
50
+ const providerName = DEFAULT_PROVIDER_NAME;
51
+ const existing = readConfig(configPath);
52
+ const provider = buildScifcodeProvider(env);
53
+ const merged = {
54
+ ...existing,
55
+ $schema: existing.$schema || "https://opencode.ai/config.json",
56
+ enabled_providers: [providerName],
57
+ model: `${providerName}/${DEFAULT_MODEL}`,
58
+ permission: {
59
+ ...(existing.permission && typeof existing.permission === "object" ? existing.permission : {}),
60
+ edit: "ask",
61
+ bash: "ask"
62
+ },
63
+ provider: {
64
+ ...(existing.provider && typeof existing.provider === "object" ? existing.provider : {}),
65
+ [providerName]: provider
66
+ }
67
+ };
68
+ fs.mkdirSync(configDir, { recursive: true });
69
+ fs.writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}\n`);
70
+ return { configPath, providerName, baseUrl: provider.options.baseURL };
71
+ }
@@ -0,0 +1,71 @@
1
+ import fs from "node:fs";
2
+ import {
3
+ getProjectScifcodeEnvPath,
4
+ getScifcodeEnvPath
5
+ } from "./paths.js";
6
+
7
+ function unquoteValue(value) {
8
+ const trimmed = value.trim();
9
+ const first = trimmed[0];
10
+ const last = trimmed[trimmed.length - 1];
11
+
12
+ if (
13
+ trimmed.length >= 2 &&
14
+ ((first === '"' && last === '"') || (first === "'" && last === "'"))
15
+ ) {
16
+ return trimmed.slice(1, -1);
17
+ }
18
+
19
+ return trimmed;
20
+ }
21
+
22
+ export function parseEnvContent(content) {
23
+ const parsed = {};
24
+
25
+ for (const line of content.split(/\r?\n/)) {
26
+ const trimmed = line.trim();
27
+
28
+ if (!trimmed || trimmed.startsWith("#")) {
29
+ continue;
30
+ }
31
+
32
+ const exportPrefix = "export ";
33
+ const normalized = trimmed.startsWith(exportPrefix)
34
+ ? trimmed.slice(exportPrefix.length).trim()
35
+ : trimmed;
36
+ const equalsIndex = normalized.indexOf("=");
37
+
38
+ if (equalsIndex <= 0) {
39
+ continue;
40
+ }
41
+
42
+ const key = normalized.slice(0, equalsIndex).trim();
43
+ const value = normalized.slice(equalsIndex + 1);
44
+
45
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
46
+ parsed[key] = unquoteValue(value);
47
+ }
48
+ }
49
+
50
+ return parsed;
51
+ }
52
+
53
+ function readEnvFile(filePath) {
54
+ if (!fs.existsSync(filePath)) {
55
+ return {};
56
+ }
57
+
58
+ return parseEnvContent(fs.readFileSync(filePath, "utf8"));
59
+ }
60
+
61
+ export function loadScifcodeEnv(options = {}) {
62
+ const env = options.env || process.env;
63
+ const globalEnv = readEnvFile(getScifcodeEnvPath(options.homeDir));
64
+ const projectEnv = readEnvFile(getProjectScifcodeEnvPath(options.cwd));
65
+
66
+ return {
67
+ ...globalEnv,
68
+ ...projectEnv,
69
+ ...env
70
+ };
71
+ }
package/src/paths.js ADDED
@@ -0,0 +1,8 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+
4
+ export function getScifcodeHomeDir(homeDir = os.homedir()) { return path.join(homeDir, ".scifcode"); }
5
+ export function getScifcodeEnvPath(homeDir = os.homedir()) { return path.join(getScifcodeHomeDir(homeDir), "env"); }
6
+ export function getProjectScifcodeEnvPath(cwd = process.cwd()) { return path.join(cwd, ".scifcode.env"); }
7
+ export function getScifcodeConfigDir(homeDir = os.homedir()) { return path.join(homeDir, ".config", "scifcode"); }
8
+ export function getScifcodeConfigPath(homeDir = os.homedir()) { return path.join(getScifcodeConfigDir(homeDir), "scifcode.json"); }
@@ -0,0 +1,115 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { spawn } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+ import { API_KEY_ENV_NAME, DEFAULT_MODEL, DEFAULT_PROVIDER_NAME, LEGACY_API_KEY_ENV_NAME } from "./constants.js";
7
+
8
+ function executableOnPath(name, env = process.env) {
9
+ return String(env.PATH || "").split(path.delimiter).map(dir => path.join(dir, name)).find(fs.existsSync);
10
+ }
11
+
12
+ function defaultSourceDir() {
13
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "scifcode");
14
+ }
15
+
16
+ function findInstalledRuntime(options = {}) {
17
+ let current = options.searchDir || path.dirname(fileURLToPath(import.meta.url));
18
+ for (;;) {
19
+ const candidate = path.join(current, "node_modules", "@numairbaseer", "scifcode-runtime", "bin", "scifcode.exe");
20
+ if (fs.existsSync(candidate)) return candidate;
21
+ const parent = path.dirname(current);
22
+ if (parent === current) return;
23
+ current = parent;
24
+ }
25
+ }
26
+
27
+ export function resolveRuntime(options = {}) {
28
+ const env = options.env || process.env;
29
+ if (env.SCIFCODE_RUNTIME_BIN) {
30
+ if (!fs.existsSync(env.SCIFCODE_RUNTIME_BIN)) throw new Error(`SCIFCODE_RUNTIME_BIN does not exist: ${env.SCIFCODE_RUNTIME_BIN}`);
31
+ return { command: env.SCIFCODE_RUNTIME_BIN, argsPrefix: [], source: "binary" };
32
+ }
33
+ const installedBin = findInstalledRuntime(options);
34
+ if (installedBin) return { command: installedBin, argsPrefix: [], source: "package" };
35
+ const sourceDir = env.SCIFCODE_SOURCE_DIR || defaultSourceDir();
36
+ const runtimePlatform = options.platform || process.platform;
37
+ const platform = runtimePlatform === "win32" ? "windows" : runtimePlatform;
38
+ const arch = options.arch || process.arch;
39
+ const binaryName = runtimePlatform === "win32" ? "scifcode.exe" : "scifcode";
40
+ const builtBin = path.join(sourceDir, "packages", "opencode", "dist", `scifcode-${platform}-${arch}`, "bin", binaryName);
41
+ if (fs.existsSync(builtBin)) return { command: builtBin, argsPrefix: [], source: "binary" };
42
+ const packageDir = path.join(sourceDir, "packages", "opencode");
43
+ const entrypoint = path.join(packageDir, "src", "index.ts");
44
+ const bunName = process.platform === "win32" ? "bun.exe" : "bun";
45
+ const bunInstall = env.BUN_INSTALL && path.join(env.BUN_INSTALL, "bin", bunName);
46
+ const standardBun = path.join(options.homeDir || os.homedir(), ".bun", "bin", bunName);
47
+ const bun = env.SCIFCODE_BUN_BIN
48
+ || executableOnPath("bun", env)
49
+ || (bunInstall && fs.existsSync(bunInstall) ? bunInstall : undefined)
50
+ || (fs.existsSync(standardBun) ? standardBun : undefined);
51
+ if (bun && fs.existsSync(entrypoint)) {
52
+ return {
53
+ command: bun,
54
+ argsPrefix: ["run", "--conditions=browser", entrypoint],
55
+ source: "source",
56
+ cwd: packageDir
57
+ };
58
+ }
59
+ throw new Error("Could not resolve the Scifcode runtime package. Install dependencies or set SCIFCODE_RUNTIME_BIN.");
60
+ }
61
+
62
+ export function resolveRuntimeBin(options = {}) { return resolveRuntime(options).command; }
63
+
64
+ export function buildRuntimeArgs(userArgs = [], env = process.env, runtime = resolveRuntime({ env }), projectDir = process.cwd()) {
65
+ const model = `${DEFAULT_PROVIDER_NAME}/${DEFAULT_MODEL}`;
66
+ const args = [...userArgs];
67
+ const promptIndex = args.findIndex(arg => arg === "-p" || arg === "--prompt");
68
+ const translated = promptIndex >= 0 ? ["run", args[promptIndex + 1] || "", ...args.slice(0, promptIndex), ...args.slice(promptIndex + 2)] : args;
69
+ const scoped = runtime.source !== "source"
70
+ ? translated
71
+ : translated[0] === "run"
72
+ ? [...translated, "--dir", projectDir]
73
+ : translated.length === 0
74
+ ? [projectDir]
75
+ : translated;
76
+ const withoutModelOverrides = scoped.filter((arg, index) => {
77
+ if (arg.startsWith("--model=")) return false;
78
+ if (arg === "--model" || arg === "-m") return false;
79
+ return scoped[index - 1] !== "--model" && scoped[index - 1] !== "-m";
80
+ });
81
+ return [...runtime.argsPrefix, "--model", model, ...withoutModelOverrides];
82
+ }
83
+
84
+ export function buildRuntimeEnv(env = process.env) {
85
+ const runtimeEnv = { ...env };
86
+ if (!runtimeEnv[API_KEY_ENV_NAME] && env[LEGACY_API_KEY_ENV_NAME]) runtimeEnv[API_KEY_ENV_NAME] = env[LEGACY_API_KEY_ENV_NAME];
87
+ return runtimeEnv;
88
+ }
89
+
90
+ export function validateLagunaApiKey(env = process.env) { return Boolean(env[API_KEY_ENV_NAME] || env[LEGACY_API_KEY_ENV_NAME]); }
91
+ export function printMissingApiKey(stderr = console.error) {
92
+ stderr(`Scifcode requires ${API_KEY_ENV_NAME}.`);
93
+ stderr("");
94
+ stderr(`Run:\n export ${API_KEY_ENV_NAME}="endpoint_or_modal_bearer_token"\n scifcode`);
95
+ }
96
+
97
+ export function runRuntime(userArgs = [], options = {}) {
98
+ const env = options.env || process.env;
99
+ const stderr = options.stderr || console.error;
100
+ const exit = options.exit || process.exit;
101
+ if (!validateLagunaApiKey(env)) { printMissingApiKey(stderr); exit(1); return null; }
102
+ let runtime;
103
+ try { runtime = (options.resolveRuntimeFn || resolveRuntime)({ env }); }
104
+ catch (error) { stderr("Failed to resolve the Scifcode agent runtime."); stderr(error.message); exit(1); return null; }
105
+ const runtimeEnv = buildRuntimeEnv(env);
106
+ const projectDir = options.cwd || process.cwd();
107
+ const child = (options.spawnFn || spawn)(runtime.command, buildRuntimeArgs(userArgs, runtimeEnv, runtime, projectDir), {
108
+ stdio: "inherit",
109
+ env: runtimeEnv,
110
+ cwd: runtime.cwd || projectDir
111
+ });
112
+ child.on("exit", code => exit(code ?? 0));
113
+ child.on("error", error => { stderr("Failed to launch Scifcode."); stderr(error.message); exit(1); });
114
+ return child;
115
+ }