@matterailab/orbcode 0.1.3
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 +21 -0
- package/README.md +471 -0
- package/bin/orbcode.js +2 -0
- package/dist/api/client.js +141 -0
- package/dist/api/headers.js +14 -0
- package/dist/api/models.js +49 -0
- package/dist/api/stream.js +1 -0
- package/dist/auth/auth.js +172 -0
- package/dist/branding.js +31 -0
- package/dist/config/promptHistory.js +33 -0
- package/dist/config/settings.js +112 -0
- package/dist/core/agent.js +459 -0
- package/dist/core/events.js +1 -0
- package/dist/core/sessions.js +44 -0
- package/dist/headless.js +64 -0
- package/dist/index.js +84 -0
- package/dist/prompts/system.js +379 -0
- package/dist/tools/executors/executeCommand.js +58 -0
- package/dist/tools/executors/files.js +197 -0
- package/dist/tools/executors/listFiles.js +65 -0
- package/dist/tools/executors/searchFiles.js +104 -0
- package/dist/tools/executors/web.js +72 -0
- package/dist/tools/index.js +85 -0
- package/dist/tools/schemas/ask_followup_question.js +40 -0
- package/dist/tools/schemas/attempt_completion.js +19 -0
- package/dist/tools/schemas/browser_action.js +60 -0
- package/dist/tools/schemas/check_past_chat_memories.js +23 -0
- package/dist/tools/schemas/codebase_search.js +23 -0
- package/dist/tools/schemas/execute_command.js +31 -0
- package/dist/tools/schemas/fetch_instructions.js +20 -0
- package/dist/tools/schemas/file_edit.js +31 -0
- package/dist/tools/schemas/file_write.js +27 -0
- package/dist/tools/schemas/generate_image.js +27 -0
- package/dist/tools/schemas/index.js +29 -0
- package/dist/tools/schemas/list_code_definition_names.js +19 -0
- package/dist/tools/schemas/list_files.js +23 -0
- package/dist/tools/schemas/lsp.js +46 -0
- package/dist/tools/schemas/multi_file_edit.js +42 -0
- package/dist/tools/schemas/new_task.js +27 -0
- package/dist/tools/schemas/read_file.js +27 -0
- package/dist/tools/schemas/run_slash_command.js +23 -0
- package/dist/tools/schemas/search_files.js +27 -0
- package/dist/tools/schemas/switch_mode.js +23 -0
- package/dist/tools/schemas/update_todo_list.js +19 -0
- package/dist/tools/schemas/use_skill.js +19 -0
- package/dist/tools/schemas/web_fetch.js +19 -0
- package/dist/tools/schemas/web_search.js +19 -0
- package/dist/tools/types.js +7 -0
- package/dist/ui/App.js +569 -0
- package/dist/ui/LoginView.js +82 -0
- package/dist/ui/components/ApprovalPrompt.js +21 -0
- package/dist/ui/components/FollowupPrompt.js +45 -0
- package/dist/ui/components/Header.js +12 -0
- package/dist/ui/components/InputBox.js +220 -0
- package/dist/ui/components/ModelPicker.js +51 -0
- package/dist/ui/components/SessionPicker.js +52 -0
- package/dist/ui/components/Spinner.js +19 -0
- package/dist/ui/components/StatusBar.js +18 -0
- package/dist/ui/components/rows.js +106 -0
- package/dist/ui/markdown.js +64 -0
- package/dist/utils/diff.js +144 -0
- package/dist/utils/shell.js +19 -0
- package/package.json +62 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const AXON_MODELS = {
|
|
2
|
+
"axon-code-2-5-mini": {
|
|
3
|
+
id: "axon-code-2-5-mini",
|
|
4
|
+
name: "Axon Code 2.5 Mini (free)",
|
|
5
|
+
description: "Axon Mini is an general purpose super intelligent LLM coding model for low-effort day-to-day tasks",
|
|
6
|
+
contextWindow: 400000,
|
|
7
|
+
maxOutputTokens: 64000,
|
|
8
|
+
supportsImages: true,
|
|
9
|
+
inputPrice: 0.0000005,
|
|
10
|
+
outputPrice: 0.0000015,
|
|
11
|
+
free: true,
|
|
12
|
+
},
|
|
13
|
+
"axon-code-2-5-pro": {
|
|
14
|
+
id: "axon-code-2-5-pro",
|
|
15
|
+
name: "Axon Code 2.5 Pro",
|
|
16
|
+
description: "Axon Code 2.5 Pro is the next-generation of Axon Code for coding tasks, currently in experimental stage.",
|
|
17
|
+
contextWindow: 400000,
|
|
18
|
+
maxOutputTokens: 64000,
|
|
19
|
+
supportsImages: true,
|
|
20
|
+
inputPrice: 0.000002,
|
|
21
|
+
outputPrice: 0.000006,
|
|
22
|
+
free: false,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export const DEFAULT_MODEL_ID = "axon-code-2-5-pro";
|
|
26
|
+
/** Add user-defined models (from settings.json) to the registry. */
|
|
27
|
+
export function registerCustomModels(models) {
|
|
28
|
+
for (const model of models) {
|
|
29
|
+
if (!model || typeof model.id !== "string" || !model.id)
|
|
30
|
+
continue;
|
|
31
|
+
AXON_MODELS[model.id] = {
|
|
32
|
+
id: model.id,
|
|
33
|
+
name: model.name ?? model.id,
|
|
34
|
+
description: model.description ?? "Custom model from settings.json",
|
|
35
|
+
contextWindow: model.contextWindow ?? 200_000,
|
|
36
|
+
maxOutputTokens: model.maxOutputTokens ?? 32_000,
|
|
37
|
+
supportsImages: model.supportsImages ?? false,
|
|
38
|
+
inputPrice: model.inputPrice ?? 0,
|
|
39
|
+
outputPrice: model.outputPrice ?? 0,
|
|
40
|
+
free: (model.inputPrice ?? 0) === 0 && (model.outputPrice ?? 0) === 0,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function isValidAxonModel(modelId) {
|
|
45
|
+
return modelId in AXON_MODELS;
|
|
46
|
+
}
|
|
47
|
+
export function getModel(modelId) {
|
|
48
|
+
return AXON_MODELS[modelId] ?? AXON_MODELS[DEFAULT_MODEL_ID];
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Auth ported from the Orbital extension: a MatterAI JWT acts as the API key.
|
|
2
|
+
// Backend URL resolution mirrors getKiloBaseUriFromToken / getKiloUrlFromToken.
|
|
3
|
+
export const DEFAULT_BACKEND_URL = "https://api.matterai.so";
|
|
4
|
+
export const API_GATEWAY_PATH = "https://api2.matterai.so/v1/web/";
|
|
5
|
+
export function getBaseUrlFromToken(token) {
|
|
6
|
+
if (token) {
|
|
7
|
+
try {
|
|
8
|
+
const payloadString = token.split(".")[1];
|
|
9
|
+
if (payloadString) {
|
|
10
|
+
const payload = JSON.parse(Buffer.from(payloadString, "base64").toString());
|
|
11
|
+
// UNTRUSTED payload: only used to detect the development environment,
|
|
12
|
+
// never to read URLs directly.
|
|
13
|
+
if (payload.env === "development")
|
|
14
|
+
return "http://localhost:3000";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// fall through to production URL
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return DEFAULT_BACKEND_URL;
|
|
22
|
+
}
|
|
23
|
+
/** Re-host targetUrl onto the backend resolved from the token. */
|
|
24
|
+
export function getUrlFromToken(targetUrl, token) {
|
|
25
|
+
const target = new URL(targetUrl);
|
|
26
|
+
const { protocol, host } = new URL(getBaseUrlFromToken(token));
|
|
27
|
+
Object.assign(target, { protocol, host });
|
|
28
|
+
return target.toString();
|
|
29
|
+
}
|
|
30
|
+
export const APP_URL = "https://app.matterai.so";
|
|
31
|
+
export function getSignInUrl() {
|
|
32
|
+
return `${APP_URL}/authentication/sign-in?loginType=extension&source=cli`;
|
|
33
|
+
}
|
|
34
|
+
function deviceAuthBaseUrl() {
|
|
35
|
+
return process.env.ORBCODE_BACKEND_URL || DEFAULT_BACKEND_URL;
|
|
36
|
+
}
|
|
37
|
+
function deviceAuthAppUrl() {
|
|
38
|
+
return process.env.ORBCODE_APP_URL || APP_URL;
|
|
39
|
+
}
|
|
40
|
+
export async function startDeviceAuth() {
|
|
41
|
+
const response = await fetch(`${deviceAuthBaseUrl()}/orbcode/auth/start`, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "Content-Type": "application/json" },
|
|
44
|
+
body: "{}",
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(`Could not start sign-in (${response.status}). Try again or paste a token manually.`);
|
|
48
|
+
}
|
|
49
|
+
const data = (await response.json());
|
|
50
|
+
if (!data.devicecode) {
|
|
51
|
+
throw new Error("Sign-in service returned no device code.");
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
devicecode: data.devicecode,
|
|
55
|
+
expiresIn: data.expiresIn ?? 600,
|
|
56
|
+
interval: data.interval ?? 3,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function getAuthorizeUrl(devicecode) {
|
|
60
|
+
return `${deviceAuthAppUrl()}/orbital?loginType=orbcode&devicecode=${encodeURIComponent(devicecode)}`;
|
|
61
|
+
}
|
|
62
|
+
export async function pollDeviceAuth(devicecode) {
|
|
63
|
+
const response = await fetch(`${deviceAuthBaseUrl()}/orbcode/auth/poll?devicecode=${encodeURIComponent(devicecode)}`);
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
// Transient server/network errors are treated as pending; the caller's
|
|
66
|
+
// overall deadline still bounds the wait.
|
|
67
|
+
return { status: "pending" };
|
|
68
|
+
}
|
|
69
|
+
const data = (await response.json());
|
|
70
|
+
if (data.status === "authorized" && data.token) {
|
|
71
|
+
return { status: "authorized", token: data.token };
|
|
72
|
+
}
|
|
73
|
+
if (data.status === "expired") {
|
|
74
|
+
return { status: "expired" };
|
|
75
|
+
}
|
|
76
|
+
return { status: "pending" };
|
|
77
|
+
}
|
|
78
|
+
export async function fetchProfile(token) {
|
|
79
|
+
const url = getUrlFromToken("https://api.matterai.so/axoncode/profile", token);
|
|
80
|
+
const response = await fetch(url, {
|
|
81
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw new Error(`Profile request failed (${response.status}). Your token may be invalid or expired.`);
|
|
85
|
+
}
|
|
86
|
+
return (await response.json());
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Extract a clean title from potentially malformed input: a plain string,
|
|
90
|
+
* a JSON object string ('{"title":"…"}'), or an object with a title field.
|
|
91
|
+
* Ported from the extension's taskMetadata sanitizeTitle.
|
|
92
|
+
*/
|
|
93
|
+
function sanitizeTitle(raw) {
|
|
94
|
+
if (raw == null)
|
|
95
|
+
return undefined;
|
|
96
|
+
if (typeof raw === "string") {
|
|
97
|
+
const trimmed = raw.trim();
|
|
98
|
+
if (!trimmed)
|
|
99
|
+
return undefined;
|
|
100
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
101
|
+
try {
|
|
102
|
+
const extracted = sanitizeTitle(JSON.parse(trimmed));
|
|
103
|
+
if (extracted)
|
|
104
|
+
return extracted;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// not JSON, use as plain string
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return trimmed;
|
|
111
|
+
}
|
|
112
|
+
if (typeof raw === "object") {
|
|
113
|
+
const maybe = raw["title"];
|
|
114
|
+
if (maybe != null)
|
|
115
|
+
return sanitizeTitle(maybe);
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Fetch the backend-generated task title (GET /axoncode/meta/<taskId>).
|
|
121
|
+
* The title only exists after the first response starts streaming, so this
|
|
122
|
+
* retries a few times. Ported from the extension's fetchTaskTitle.
|
|
123
|
+
*/
|
|
124
|
+
export async function fetchTaskTitle(taskId, token, maxRetries = 3, retryDelayMs = 2000) {
|
|
125
|
+
if (!token)
|
|
126
|
+
return null;
|
|
127
|
+
const url = getUrlFromToken(`https://api.matterai.so/axoncode/meta/${taskId}`, token);
|
|
128
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
129
|
+
try {
|
|
130
|
+
const response = await fetch(url, {
|
|
131
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
132
|
+
signal: AbortSignal.timeout(5000),
|
|
133
|
+
});
|
|
134
|
+
if (response.ok) {
|
|
135
|
+
const data = await response.json().catch(() => undefined);
|
|
136
|
+
const title = sanitizeTitle(data);
|
|
137
|
+
if (title)
|
|
138
|
+
return title;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// network/timeout: fall through to retry
|
|
143
|
+
}
|
|
144
|
+
if (attempt < maxRetries) {
|
|
145
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
export async function fetchBalance(token, organizationId) {
|
|
151
|
+
try {
|
|
152
|
+
const url = getUrlFromToken("https://api.matterai.so/api/profile/balance", token);
|
|
153
|
+
const headers = { Authorization: `Bearer ${token}` };
|
|
154
|
+
if (organizationId)
|
|
155
|
+
headers["X-KiloCode-OrganizationId"] = organizationId;
|
|
156
|
+
const response = await fetch(url, { headers });
|
|
157
|
+
if (!response.ok)
|
|
158
|
+
return undefined;
|
|
159
|
+
const data = (await response.json());
|
|
160
|
+
return data.balance;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Validate a pasted token: well-formed JWT + accepted by the profile endpoint. */
|
|
167
|
+
export async function verifyToken(token) {
|
|
168
|
+
if (token.split(".").length !== 3) {
|
|
169
|
+
throw new Error("That doesn't look like a valid token (expected a JWT).");
|
|
170
|
+
}
|
|
171
|
+
return fetchProfile(token);
|
|
172
|
+
}
|
package/dist/branding.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
export const PRODUCT_NAME = "OrbCode CLI";
|
|
3
|
+
export const BIN_NAME = "orbcode";
|
|
4
|
+
export const VERSION = (() => {
|
|
5
|
+
try {
|
|
6
|
+
// dist/branding.js -> ../package.json
|
|
7
|
+
const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
8
|
+
return pkg.version || "0.0.0";
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return "0.0.0";
|
|
12
|
+
}
|
|
13
|
+
})();
|
|
14
|
+
export const TAGLINE = "powered by Axon models by MatterAI";
|
|
15
|
+
export const COLORS = {
|
|
16
|
+
primary: "#c4fdff",
|
|
17
|
+
accent: "#4EC9B0",
|
|
18
|
+
dim: "gray",
|
|
19
|
+
error: "#e86464",
|
|
20
|
+
warning: "#e2ce76",
|
|
21
|
+
success: "#43df94",
|
|
22
|
+
thinking: "#8cd3de",
|
|
23
|
+
user: "#569CD6",
|
|
24
|
+
};
|
|
25
|
+
export const LOGO = `
|
|
26
|
+
___ _ ____ _
|
|
27
|
+
/ _ \\ _ __ | |__ / ___| ___ __| | ___
|
|
28
|
+
| | | || '__|| '_ \\ | | / _ \\ / _\` | / _ \\
|
|
29
|
+
| |_| || | | |_) || |___ | (_) || (_| || __/
|
|
30
|
+
\\___/ |_| |_.__/ \\____| \\___/ \\__,_| \\___|
|
|
31
|
+
`;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { getConfigDir } from "./settings.js";
|
|
4
|
+
const MAX_HISTORY = 200;
|
|
5
|
+
function historyPath() {
|
|
6
|
+
return path.join(getConfigDir(), "history.json");
|
|
7
|
+
}
|
|
8
|
+
/** Prompt history shared across sessions, oldest first (like shell history). */
|
|
9
|
+
export function loadPromptHistory() {
|
|
10
|
+
try {
|
|
11
|
+
const data = JSON.parse(fs.readFileSync(historyPath(), "utf8"));
|
|
12
|
+
return Array.isArray(data) ? data.filter((e) => typeof e === "string") : [];
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function appendPromptHistory(entry) {
|
|
19
|
+
try {
|
|
20
|
+
const history = loadPromptHistory();
|
|
21
|
+
// Like shells: skip consecutive duplicates.
|
|
22
|
+
if (history[history.length - 1] === entry)
|
|
23
|
+
return;
|
|
24
|
+
history.push(entry);
|
|
25
|
+
fs.mkdirSync(getConfigDir(), { recursive: true });
|
|
26
|
+
fs.writeFileSync(historyPath(), JSON.stringify(history.slice(-MAX_HISTORY), null, "\t") + "\n", {
|
|
27
|
+
mode: 0o600,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// history is best-effort
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { DEFAULT_MODEL_ID, isValidAxonModel, registerCustomModels } from "../api/models.js";
|
|
5
|
+
const DEFAULTS = {
|
|
6
|
+
model: DEFAULT_MODEL_ID,
|
|
7
|
+
autoApproveEdits: false,
|
|
8
|
+
autoApproveSafeCommands: false,
|
|
9
|
+
};
|
|
10
|
+
/** Keys that settings.json files may set, in increasing precedence order. */
|
|
11
|
+
const SETTINGS_KEYS = [
|
|
12
|
+
"model",
|
|
13
|
+
"organizationId",
|
|
14
|
+
"autoApproveEdits",
|
|
15
|
+
"autoApproveSafeCommands",
|
|
16
|
+
"baseUrl",
|
|
17
|
+
"apiKey",
|
|
18
|
+
"env",
|
|
19
|
+
];
|
|
20
|
+
export function getConfigDir() {
|
|
21
|
+
return process.env.ORBCODE_CONFIG_DIR || path.join(os.homedir(), ".orbcode");
|
|
22
|
+
}
|
|
23
|
+
function getConfigPath() {
|
|
24
|
+
return path.join(getConfigDir(), "config.json");
|
|
25
|
+
}
|
|
26
|
+
/** settings.json locations, lowest precedence first: user, then project. */
|
|
27
|
+
export function getSettingsPaths(cwd = process.cwd()) {
|
|
28
|
+
return [path.join(getConfigDir(), "settings.json"), path.join(cwd, ".orbcode", "settings.json")];
|
|
29
|
+
}
|
|
30
|
+
function readJson(filePath) {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Make sure ~/.orbcode/settings.json exists (empty JSON) so users can find it. */
|
|
39
|
+
function ensureUserSettingsFile() {
|
|
40
|
+
try {
|
|
41
|
+
const settingsPath = path.join(getConfigDir(), "settings.json");
|
|
42
|
+
if (!fs.existsSync(settingsPath)) {
|
|
43
|
+
fs.mkdirSync(getConfigDir(), { recursive: true });
|
|
44
|
+
fs.writeFileSync(settingsPath, "{}\n", { mode: 0o600 });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// best-effort; loadSettings works without the file
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function loadSettings() {
|
|
52
|
+
ensureUserSettingsFile();
|
|
53
|
+
const settings = { ...DEFAULTS, ...readJson(getConfigPath()) };
|
|
54
|
+
// Layer settings.json files on top (user config dir, then project).
|
|
55
|
+
const customModels = [];
|
|
56
|
+
for (const settingsPath of getSettingsPaths()) {
|
|
57
|
+
const fileSettings = readJson(settingsPath);
|
|
58
|
+
if (!fileSettings)
|
|
59
|
+
continue;
|
|
60
|
+
for (const key of SETTINGS_KEYS) {
|
|
61
|
+
if (fileSettings[key] !== undefined) {
|
|
62
|
+
;
|
|
63
|
+
settings[key] = fileSettings[key];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(fileSettings.customModels)) {
|
|
67
|
+
customModels.push(...fileSettings.customModels);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (customModels.length > 0) {
|
|
71
|
+
settings.customModels = customModels;
|
|
72
|
+
registerCustomModels(customModels);
|
|
73
|
+
}
|
|
74
|
+
// env block from settings.json applies to this process.
|
|
75
|
+
if (settings.env && typeof settings.env === "object") {
|
|
76
|
+
for (const [key, value] of Object.entries(settings.env)) {
|
|
77
|
+
if (typeof value === "string")
|
|
78
|
+
process.env[key] = value;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Environment variables take precedence over all files.
|
|
82
|
+
if (process.env.ORBCODE_BASE_URL)
|
|
83
|
+
settings.baseUrl = process.env.ORBCODE_BASE_URL;
|
|
84
|
+
if (process.env.ORBCODE_API_KEY)
|
|
85
|
+
settings.apiKey = process.env.ORBCODE_API_KEY;
|
|
86
|
+
if (process.env.ORBCODE_MODEL)
|
|
87
|
+
settings.model = process.env.ORBCODE_MODEL;
|
|
88
|
+
if (!isValidAxonModel(settings.model)) {
|
|
89
|
+
settings.model = DEFAULT_MODEL_ID;
|
|
90
|
+
}
|
|
91
|
+
return settings;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Effective credential: ORBCODE_TOKEN env > apiKey (settings.json / env) >
|
|
95
|
+
* stored login token.
|
|
96
|
+
*/
|
|
97
|
+
export function getAuthToken(settings) {
|
|
98
|
+
return process.env.ORBCODE_TOKEN || settings.apiKey || settings.token;
|
|
99
|
+
}
|
|
100
|
+
/** Persist app state to config.json. settings.json-only fields are skipped. */
|
|
101
|
+
export function saveSettings(settings) {
|
|
102
|
+
const dir = getConfigDir();
|
|
103
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
104
|
+
const toPersist = {
|
|
105
|
+
token: settings.token,
|
|
106
|
+
model: settings.model,
|
|
107
|
+
organizationId: settings.organizationId,
|
|
108
|
+
autoApproveEdits: settings.autoApproveEdits,
|
|
109
|
+
autoApproveSafeCommands: settings.autoApproveSafeCommands,
|
|
110
|
+
};
|
|
111
|
+
fs.writeFileSync(getConfigPath(), JSON.stringify(toPersist, null, "\t") + "\n", { mode: 0o600 });
|
|
112
|
+
}
|