@tryarcanist/cli 0.1.158 → 0.1.159
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/dist/index.js +102 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -140,11 +140,12 @@ async function apiFetchText(config, path, init) {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// src/config.ts
|
|
143
|
-
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
143
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
144
144
|
import { homedir } from "os";
|
|
145
|
-
import { join } from "path";
|
|
145
|
+
import { dirname, join } from "path";
|
|
146
146
|
var CONFIG_DIR = join(homedir(), ".arcanist");
|
|
147
147
|
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
148
|
+
var PROJECT_CONFIG_FILE = ".arcanist-cli.json";
|
|
148
149
|
var DEFAULT_API_URL = "https://app.tryarcanist.com";
|
|
149
150
|
function loadFileConfig() {
|
|
150
151
|
try {
|
|
@@ -155,12 +156,19 @@ function loadFileConfig() {
|
|
|
155
156
|
}
|
|
156
157
|
function loadConfig(overrides = {}) {
|
|
157
158
|
const fileConfig = loadFileConfig();
|
|
158
|
-
const
|
|
159
|
-
|
|
159
|
+
const completeEnvOrFlagConfig = resolveCompleteEnvOrFlagConfig(overrides);
|
|
160
|
+
if (completeEnvOrFlagConfig) return validateAndNormalizeConfig(completeEnvOrFlagConfig);
|
|
161
|
+
const projectConfig = loadProjectConfig();
|
|
162
|
+
const envOrFlagConfig = resolveEnvOrFlagConfig(overrides, projectConfig !== null);
|
|
163
|
+
const apiUrl = envOrFlagConfig?.apiUrl ?? projectConfig?.apiUrl ?? fileConfig?.apiUrl;
|
|
164
|
+
const token = envOrFlagConfig?.token ?? projectConfig?.token ?? fileConfig?.token;
|
|
160
165
|
if (!apiUrl || !token) return null;
|
|
161
|
-
const
|
|
162
|
-
if (
|
|
163
|
-
|
|
166
|
+
const config = validateAndNormalizeConfig({ apiUrl, token });
|
|
167
|
+
if (projectConfig && !envOrFlagConfig && !overrides.json) {
|
|
168
|
+
process.stderr.write(`using ${PROJECT_CONFIG_FILE} -> ${config.apiUrl}
|
|
169
|
+
`);
|
|
170
|
+
}
|
|
171
|
+
return config;
|
|
164
172
|
}
|
|
165
173
|
function saveConfig(config) {
|
|
166
174
|
const urlError = validateApiUrl(config.apiUrl);
|
|
@@ -183,6 +191,19 @@ function resolveLoginApiUrl(apiUrl) {
|
|
|
183
191
|
if (urlError) throw new CliError("user", urlError);
|
|
184
192
|
return normalizeBaseUrl(raw);
|
|
185
193
|
}
|
|
194
|
+
function loadProjectConfig(cwd = process.cwd()) {
|
|
195
|
+
const gitRoot = findGitRoot(cwd);
|
|
196
|
+
if (!gitRoot) return null;
|
|
197
|
+
let current = cwd;
|
|
198
|
+
while (true) {
|
|
199
|
+
const configPath = join(current, PROJECT_CONFIG_FILE);
|
|
200
|
+
if (existsSync(configPath)) return parseProjectConfig(configPath);
|
|
201
|
+
if (current === gitRoot) return null;
|
|
202
|
+
const parent = dirname(current);
|
|
203
|
+
if (parent === current) return null;
|
|
204
|
+
current = parent;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
186
207
|
function validateApiUrl(url) {
|
|
187
208
|
let parsed;
|
|
188
209
|
try {
|
|
@@ -194,12 +215,78 @@ function validateApiUrl(url) {
|
|
|
194
215
|
return "API URL must not include embedded credentials. Use --token or ARCANIST_TOKEN to authenticate.";
|
|
195
216
|
}
|
|
196
217
|
const host = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
197
|
-
|
|
198
|
-
if (parsed.protocol !== "https:" && !isLocal) {
|
|
218
|
+
if (parsed.protocol !== "https:" && !isLoopbackHost(parsed)) {
|
|
199
219
|
return "API URL must use HTTPS for non-local hosts";
|
|
200
220
|
}
|
|
201
221
|
return null;
|
|
202
222
|
}
|
|
223
|
+
function resolveEnvOrFlagConfig(overrides, projectActive) {
|
|
224
|
+
const apiUrl = overrides.apiUrl ?? process.env.ARCANIST_API_URL;
|
|
225
|
+
const token = overrides.token ?? process.env.ARCANIST_TOKEN;
|
|
226
|
+
if (!projectActive) {
|
|
227
|
+
if (!apiUrl && !token) return null;
|
|
228
|
+
return { apiUrl, token };
|
|
229
|
+
}
|
|
230
|
+
if (apiUrl && !token || !apiUrl && token) {
|
|
231
|
+
throw new CliError(
|
|
232
|
+
"user",
|
|
233
|
+
`${PROJECT_CONFIG_FILE} is active; set both ARCANIST_API_URL and ARCANIST_TOKEN, pass both --api-url and --token, or set neither.`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
if (!apiUrl || !token) return null;
|
|
237
|
+
return { apiUrl, token };
|
|
238
|
+
}
|
|
239
|
+
function resolveCompleteEnvOrFlagConfig(overrides) {
|
|
240
|
+
const apiUrl = overrides.apiUrl ?? process.env.ARCANIST_API_URL;
|
|
241
|
+
const token = overrides.token ?? process.env.ARCANIST_TOKEN;
|
|
242
|
+
if (!apiUrl || !token) return null;
|
|
243
|
+
return { apiUrl, token };
|
|
244
|
+
}
|
|
245
|
+
function validateAndNormalizeConfig(config) {
|
|
246
|
+
const urlError = validateApiUrl(config.apiUrl);
|
|
247
|
+
if (urlError) throw new CliError("user", urlError);
|
|
248
|
+
return { apiUrl: normalizeBaseUrl(config.apiUrl), token: config.token };
|
|
249
|
+
}
|
|
250
|
+
function findGitRoot(cwd) {
|
|
251
|
+
let current = cwd;
|
|
252
|
+
while (true) {
|
|
253
|
+
if (existsSync(join(current, ".git"))) return current;
|
|
254
|
+
const parent = dirname(current);
|
|
255
|
+
if (parent === current) return null;
|
|
256
|
+
current = parent;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function parseProjectConfig(configPath) {
|
|
260
|
+
let parsed;
|
|
261
|
+
try {
|
|
262
|
+
parsed = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
263
|
+
} catch {
|
|
264
|
+
throw new CliError("user", `${PROJECT_CONFIG_FILE} is not valid JSON.`);
|
|
265
|
+
}
|
|
266
|
+
if (!parsed || typeof parsed !== "object") {
|
|
267
|
+
throw new CliError("user", `${PROJECT_CONFIG_FILE} must contain both apiUrl and token.`);
|
|
268
|
+
}
|
|
269
|
+
const config = parsed;
|
|
270
|
+
if (typeof config.apiUrl !== "string" || typeof config.token !== "string" || !config.apiUrl || !config.token) {
|
|
271
|
+
throw new CliError("user", `${PROJECT_CONFIG_FILE} must contain both apiUrl and token.`);
|
|
272
|
+
}
|
|
273
|
+
let url;
|
|
274
|
+
try {
|
|
275
|
+
url = new URL(config.apiUrl);
|
|
276
|
+
} catch {
|
|
277
|
+
throw new CliError("user", `${PROJECT_CONFIG_FILE} apiUrl must be a valid URL.`);
|
|
278
|
+
}
|
|
279
|
+
if (!isLoopbackHost(url)) {
|
|
280
|
+
throw new CliError("user", `${PROJECT_CONFIG_FILE} apiUrl must be a loopback host.`);
|
|
281
|
+
}
|
|
282
|
+
const urlError = validateApiUrl(config.apiUrl);
|
|
283
|
+
if (urlError) throw new CliError("user", urlError);
|
|
284
|
+
return { apiUrl: normalizeBaseUrl(config.apiUrl), token: config.token };
|
|
285
|
+
}
|
|
286
|
+
function isLoopbackHost(parsed) {
|
|
287
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
288
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1";
|
|
289
|
+
}
|
|
203
290
|
|
|
204
291
|
// src/runtime.ts
|
|
205
292
|
import { randomUUID } from "crypto";
|
|
@@ -3056,8 +3143,8 @@ async function messageCommand(sessionId, promptArg, options = {}, command) {
|
|
|
3056
3143
|
|
|
3057
3144
|
// src/commands/sandbox.ts
|
|
3058
3145
|
import { execFileSync } from "child_process";
|
|
3059
|
-
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
3060
|
-
import { dirname } from "path";
|
|
3146
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
3147
|
+
import { dirname as dirname2 } from "path";
|
|
3061
3148
|
|
|
3062
3149
|
// ../../shared/sandbox-layer/parser.ts
|
|
3063
3150
|
import { isMap, isScalar, isSeq, parseDocument } from "yaml";
|
|
@@ -3616,7 +3703,7 @@ function assertCleanAndPushed() {
|
|
|
3616
3703
|
throw new CliError("user", "Current commit is not pushed; push before building the sandbox layer.");
|
|
3617
3704
|
}
|
|
3618
3705
|
function assertManifestExists(path) {
|
|
3619
|
-
if (!
|
|
3706
|
+
if (!existsSync2(path)) throw new CliError("user", `Missing sandbox manifest: ${path}`);
|
|
3620
3707
|
}
|
|
3621
3708
|
function parseRepoArg2(value, fallback) {
|
|
3622
3709
|
if (!value) return fallback();
|
|
@@ -3838,10 +3925,10 @@ function sourceBody(sourceRepo, manifestPath) {
|
|
|
3838
3925
|
}
|
|
3839
3926
|
async function sandboxInitCommand(options = {}, command) {
|
|
3840
3927
|
const runtime = getRuntimeOptions(command, options);
|
|
3841
|
-
if (
|
|
3928
|
+
if (existsSync2(DEFAULT_MANIFEST_PATH)) throw new CliError("conflict", `${DEFAULT_MANIFEST_PATH} already exists`);
|
|
3842
3929
|
const layerPath = ".arcanist/sandbox.layer.Dockerfile";
|
|
3843
|
-
if (
|
|
3844
|
-
mkdirSync2(
|
|
3930
|
+
if (existsSync2(layerPath)) throw new CliError("conflict", `${layerPath} already exists`);
|
|
3931
|
+
mkdirSync2(dirname2(DEFAULT_MANIFEST_PATH), { recursive: true });
|
|
3845
3932
|
writeFileSync2(
|
|
3846
3933
|
DEFAULT_MANIFEST_PATH,
|
|
3847
3934
|
[
|