@instafy/cli 0.1.11 → 0.2.1
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 +661 -0
- package/README.md +81 -10
- package/bin/instafy.js +1 -1
- package/dist/cli.js +11389 -0
- package/package.json +22 -11
- package/dist/api.js +0 -166
- package/dist/auth.js +0 -578
- package/dist/config-command.js +0 -106
- package/dist/config.js +0 -292
- package/dist/controller-fetch.js +0 -33
- package/dist/errors.js +0 -63
- package/dist/git-credential.js +0 -205
- package/dist/git-setup.js +0 -56
- package/dist/git-wrapper.js +0 -502
- package/dist/git.js +0 -104
- package/dist/index.js +0 -636
- package/dist/org.js +0 -46
- package/dist/project-manifest.js +0 -24
- package/dist/project.js +0 -412
- package/dist/rathole.js +0 -216
- package/dist/runtime.js +0 -923
- package/dist/supabase-session.js +0 -89
- package/dist/tunnel.js +0 -484
package/dist/runtime.js
DELETED
|
@@ -1,923 +0,0 @@
|
|
|
1
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import kleur from "kleur";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { randomUUID } from "node:crypto";
|
|
7
|
-
import os from "node:os";
|
|
8
|
-
import { ensureRatholeBinary } from "./rathole.js";
|
|
9
|
-
import { resolveActiveProfileName, resolveConfiguredAccessToken } from "./config.js";
|
|
10
|
-
import { formatAuthRejectedError } from "./errors.js";
|
|
11
|
-
import { findProjectManifest } from "./project-manifest.js";
|
|
12
|
-
import { fetchWithControllerAuth } from "./controller-fetch.js";
|
|
13
|
-
const INSTAFY_DIR = path.join(os.homedir(), ".instafy");
|
|
14
|
-
const STATE_FILE = path.join(INSTAFY_DIR, "cli-runtime-state.json");
|
|
15
|
-
const LOG_DIR = path.join(INSTAFY_DIR, "cli-runtime-logs");
|
|
16
|
-
function resolveRepoRoot() {
|
|
17
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
return path.resolve(__dirname, "../../..");
|
|
19
|
-
}
|
|
20
|
-
function resolveRuntimeBinary() {
|
|
21
|
-
// Reuse existing runtime-agent binary from workspace target (assumes `cargo build` done).
|
|
22
|
-
const repoRoot = resolveRepoRoot();
|
|
23
|
-
const candidates = [
|
|
24
|
-
path.join(repoRoot, "target", "debug", "runtime-agent"),
|
|
25
|
-
path.join(repoRoot, "target", "release", "runtime-agent"),
|
|
26
|
-
path.join(repoRoot, "packages", "runtime-agent", "target", "debug", "runtime-agent"),
|
|
27
|
-
path.join(repoRoot, "packages", "runtime-agent", "target", "release", "runtime-agent"),
|
|
28
|
-
];
|
|
29
|
-
for (const candidate of candidates) {
|
|
30
|
-
if (existsSync(candidate)) {
|
|
31
|
-
return candidate;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
throw new Error("runtime-agent binary not found. If you're in the instafy repo, run `cargo build -p runtime-agent`. Otherwise install Docker and rerun `instafy runtime start`.");
|
|
35
|
-
}
|
|
36
|
-
function tryResolveRuntimeBinary() {
|
|
37
|
-
try {
|
|
38
|
-
return resolveRuntimeBinary();
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function isDockerAvailable() {
|
|
45
|
-
try {
|
|
46
|
-
const result = spawnSync("docker", ["version"], { stdio: "ignore" });
|
|
47
|
-
return result.status === 0;
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function normalizeControllerUrlForDocker(controllerUrl) {
|
|
54
|
-
const trimmed = controllerUrl.trim().replace(/\/$/, "");
|
|
55
|
-
if (!trimmed) {
|
|
56
|
-
return trimmed;
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
const url = new URL(trimmed);
|
|
60
|
-
const host = url.hostname.toLowerCase();
|
|
61
|
-
if (host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "0.0.0.0") {
|
|
62
|
-
url.hostname = "host.docker.internal";
|
|
63
|
-
}
|
|
64
|
-
return url.toString().replace(/\/$/, "");
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return trimmed
|
|
68
|
-
.replace("127.0.0.1", "host.docker.internal")
|
|
69
|
-
.replace("localhost", "host.docker.internal")
|
|
70
|
-
.replace("0.0.0.0", "host.docker.internal")
|
|
71
|
-
.replace(/\/$/, "");
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function resolveRuntimeAgentImage() {
|
|
75
|
-
const fromEnv = normalizeToken(process.env["INSTAFY_RUNTIME_AGENT_IMAGE"]) ??
|
|
76
|
-
normalizeToken(process.env["RUNTIME_AGENT_IMAGE"]) ??
|
|
77
|
-
null;
|
|
78
|
-
return fromEnv ?? "ghcr.io/instafy-dev/instafy-runtime-agent:latest";
|
|
79
|
-
}
|
|
80
|
-
function dockerContainerRunning(containerId) {
|
|
81
|
-
const id = containerId.trim();
|
|
82
|
-
if (!id) {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
const result = spawnSync("docker", ["inspect", "-f", "{{.State.Running}}", id], {
|
|
87
|
-
encoding: "utf8",
|
|
88
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
89
|
-
});
|
|
90
|
-
if (result.status !== 0) {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
return String(result.stdout ?? "").trim() === "true";
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function stopDockerContainer(containerId) {
|
|
100
|
-
const id = containerId.trim();
|
|
101
|
-
if (!id) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
try {
|
|
105
|
-
spawnSync("docker", ["rm", "-f", id], { stdio: "ignore" });
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
// ignore
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function printStatus(label, value) {
|
|
112
|
-
console.log(`${kleur.cyan(label)} ${value}`);
|
|
113
|
-
}
|
|
114
|
-
function ensureStateDir() {
|
|
115
|
-
mkdirSync(INSTAFY_DIR, { recursive: true });
|
|
116
|
-
}
|
|
117
|
-
function ensureLogDir() {
|
|
118
|
-
mkdirSync(LOG_DIR, { recursive: true });
|
|
119
|
-
}
|
|
120
|
-
function writeState(state) {
|
|
121
|
-
ensureStateDir();
|
|
122
|
-
writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
123
|
-
}
|
|
124
|
-
function readState() {
|
|
125
|
-
try {
|
|
126
|
-
const raw = readFileSync(STATE_FILE, "utf8");
|
|
127
|
-
return JSON.parse(raw);
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function clearState() {
|
|
134
|
-
try {
|
|
135
|
-
rmSync(STATE_FILE);
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
// ignore
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
function normalizeToken(value) {
|
|
142
|
-
if (typeof value !== "string") {
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
const trimmed = value.trim();
|
|
146
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
147
|
-
}
|
|
148
|
-
export { findProjectManifest };
|
|
149
|
-
function readTokenFromFile(filePath) {
|
|
150
|
-
const normalized = normalizeToken(filePath);
|
|
151
|
-
if (!normalized) {
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
const resolved = path.resolve(normalized);
|
|
155
|
-
const contents = readFileSync(resolved, "utf8").trim();
|
|
156
|
-
if (!contents) {
|
|
157
|
-
throw new Error(`token file ${resolved} was empty`);
|
|
158
|
-
}
|
|
159
|
-
return contents;
|
|
160
|
-
}
|
|
161
|
-
function resolveSupabaseAccessToken(options, env) {
|
|
162
|
-
const explicit = normalizeToken(options.supabaseAccessToken);
|
|
163
|
-
if (explicit) {
|
|
164
|
-
return explicit;
|
|
165
|
-
}
|
|
166
|
-
const fromFile = readTokenFromFile(options.supabaseAccessTokenFile);
|
|
167
|
-
if (fromFile) {
|
|
168
|
-
return fromFile;
|
|
169
|
-
}
|
|
170
|
-
const fromEnv = normalizeToken(env["SUPABASE_ACCESS_TOKEN"]);
|
|
171
|
-
if (fromEnv) {
|
|
172
|
-
return fromEnv;
|
|
173
|
-
}
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
function findRatholeOnPath() {
|
|
177
|
-
const names = process.platform === "win32" ? ["rathole.exe", "rathole"] : ["rathole"];
|
|
178
|
-
const pathEntries = (process.env.PATH ?? "")
|
|
179
|
-
.split(path.delimiter)
|
|
180
|
-
.map((entry) => entry.trim())
|
|
181
|
-
.filter(Boolean);
|
|
182
|
-
for (const entry of pathEntries) {
|
|
183
|
-
for (const name of names) {
|
|
184
|
-
const candidate = path.join(entry, name);
|
|
185
|
-
if (existsSync(candidate)) {
|
|
186
|
-
return candidate;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
function resolveControllerAccessTokenForCliWithSource(options, env, supabaseAccessToken, profile, cwd) {
|
|
193
|
-
const explicit = normalizeToken(options.controllerAccessToken);
|
|
194
|
-
if (explicit) {
|
|
195
|
-
return { token: explicit, source: "explicit" };
|
|
196
|
-
}
|
|
197
|
-
const envToken = normalizeToken(env["INSTAFY_ACCESS_TOKEN"]) ??
|
|
198
|
-
normalizeToken(env["CONTROLLER_ACCESS_TOKEN"]);
|
|
199
|
-
if (envToken) {
|
|
200
|
-
return { token: envToken, source: "env" };
|
|
201
|
-
}
|
|
202
|
-
if (supabaseAccessToken) {
|
|
203
|
-
return { token: supabaseAccessToken, source: "explicit" };
|
|
204
|
-
}
|
|
205
|
-
const supabaseEnvToken = normalizeToken(env["SUPABASE_ACCESS_TOKEN"]);
|
|
206
|
-
if (supabaseEnvToken) {
|
|
207
|
-
return { token: supabaseEnvToken, source: "env" };
|
|
208
|
-
}
|
|
209
|
-
const stored = resolveConfiguredAccessToken({ profile, cwd });
|
|
210
|
-
if (stored) {
|
|
211
|
-
return { token: stored, source: "config" };
|
|
212
|
-
}
|
|
213
|
-
return { token: null, source: "none" };
|
|
214
|
-
}
|
|
215
|
-
export async function resolveRatholeBinaryForCli(options) {
|
|
216
|
-
const warn = options.warn ??
|
|
217
|
-
((message) => {
|
|
218
|
-
console.warn(kleur.yellow(message));
|
|
219
|
-
});
|
|
220
|
-
const logger = options.logger ??
|
|
221
|
-
((message) => {
|
|
222
|
-
console.log(kleur.cyan(`[rathole] ${message}`));
|
|
223
|
-
});
|
|
224
|
-
const existing = normalizeToken(options.env["RATHOLE_BIN"]);
|
|
225
|
-
if (existing) {
|
|
226
|
-
options.env["RATHOLE_BIN"] = existing;
|
|
227
|
-
return existing;
|
|
228
|
-
}
|
|
229
|
-
const finder = options.findBinary ?? findRatholeOnPath;
|
|
230
|
-
const detected = finder();
|
|
231
|
-
if (detected) {
|
|
232
|
-
options.env["RATHOLE_BIN"] = detected;
|
|
233
|
-
return detected;
|
|
234
|
-
}
|
|
235
|
-
const download = options.downloadBinary ??
|
|
236
|
-
((params) => ensureRatholeBinary(params));
|
|
237
|
-
try {
|
|
238
|
-
const resolved = await download({
|
|
239
|
-
version: normalizeToken(options.version ?? undefined) ?? undefined,
|
|
240
|
-
cacheDir: normalizeToken(options.cacheDir ?? undefined) ?? undefined,
|
|
241
|
-
logger,
|
|
242
|
-
});
|
|
243
|
-
options.env["RATHOLE_BIN"] = resolved;
|
|
244
|
-
return resolved;
|
|
245
|
-
}
|
|
246
|
-
catch (error) {
|
|
247
|
-
const suffix = error instanceof Error
|
|
248
|
-
? error.message
|
|
249
|
-
: typeof error === "string"
|
|
250
|
-
? error
|
|
251
|
-
: JSON.stringify(error);
|
|
252
|
-
const osHint = process.platform === "darwin"
|
|
253
|
-
? "Install with cargo (`cargo install --locked rathole`) or set RATHOLE_BIN to a downloaded binary."
|
|
254
|
-
: process.platform === "linux"
|
|
255
|
-
? "Download from https://github.com/rapiz1/rathole/releases (matching your arch) or build via `cargo install --locked rathole`."
|
|
256
|
-
: "Download a rathole binary for your OS/arch and set RATHOLE_BIN.";
|
|
257
|
-
warn(`rathole unavailable (set RATHOLE_BIN or install on PATH). ${osHint} ${suffix}`);
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
export async function mintRuntimeAccessToken(params) {
|
|
262
|
-
const url = params.controllerUrl.replace(/\/$/, "");
|
|
263
|
-
const target = `${url}/projects/${encodeURIComponent(params.projectId)}/runtime/token`;
|
|
264
|
-
const { response } = await fetchWithControllerAuth({
|
|
265
|
-
url: target,
|
|
266
|
-
init: {
|
|
267
|
-
method: "POST",
|
|
268
|
-
headers: {
|
|
269
|
-
"content-type": "application/json",
|
|
270
|
-
},
|
|
271
|
-
body: JSON.stringify({
|
|
272
|
-
runtimeId: params.runtimeId,
|
|
273
|
-
leaseId: params.leaseId,
|
|
274
|
-
scopes: params.scopes,
|
|
275
|
-
}),
|
|
276
|
-
},
|
|
277
|
-
accessToken: params.controllerAccessToken,
|
|
278
|
-
tokenSource: params.tokenSource ?? "env",
|
|
279
|
-
profile: params.profile ?? null,
|
|
280
|
-
cwd: params.cwd ?? null,
|
|
281
|
-
});
|
|
282
|
-
if (!response.ok) {
|
|
283
|
-
const text = await response.text().catch(() => "");
|
|
284
|
-
if (response.status === 401 || response.status === 403) {
|
|
285
|
-
throw formatAuthRejectedError({
|
|
286
|
-
status: response.status,
|
|
287
|
-
responseBody: text,
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
throw new Error(`Instafy server rejected runtime token request (${response.status} ${response.statusText}): ${text}`);
|
|
291
|
-
}
|
|
292
|
-
const payload = (await response.json());
|
|
293
|
-
const token = typeof payload.token === "string" ? payload.token.trim() : "";
|
|
294
|
-
if (!token) {
|
|
295
|
-
throw new Error("Instafy server response missing token field while minting runtime token.");
|
|
296
|
-
}
|
|
297
|
-
return token;
|
|
298
|
-
}
|
|
299
|
-
async function requestRuntimeLease(params) {
|
|
300
|
-
const url = params.controllerUrl.replace(/\/$/, "");
|
|
301
|
-
const target = `${url}/projects/${encodeURIComponent(params.projectId)}/runtime/request`;
|
|
302
|
-
const { response, accessToken } = await fetchWithControllerAuth({
|
|
303
|
-
url: target,
|
|
304
|
-
init: {
|
|
305
|
-
method: "POST",
|
|
306
|
-
headers: {
|
|
307
|
-
"content-type": "application/json",
|
|
308
|
-
},
|
|
309
|
-
body: JSON.stringify({
|
|
310
|
-
provider: params.provider,
|
|
311
|
-
displayName: params.displayName,
|
|
312
|
-
idleTtlSeconds: params.idleTtlSeconds,
|
|
313
|
-
requestTunnel: params.requestTunnel,
|
|
314
|
-
runtimeId: params.runtimeId,
|
|
315
|
-
}),
|
|
316
|
-
},
|
|
317
|
-
accessToken: params.accessToken,
|
|
318
|
-
tokenSource: params.tokenSource,
|
|
319
|
-
profile: params.profile,
|
|
320
|
-
cwd: params.cwd,
|
|
321
|
-
});
|
|
322
|
-
if (!response.ok) {
|
|
323
|
-
const text = await response.text().catch(() => "");
|
|
324
|
-
if (response.status === 401 || response.status === 403) {
|
|
325
|
-
throw formatAuthRejectedError({
|
|
326
|
-
status: response.status,
|
|
327
|
-
responseBody: text,
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
throw new Error(`Instafy server rejected runtime request (${response.status} ${response.statusText}): ${text}`);
|
|
331
|
-
}
|
|
332
|
-
const payload = (await response.json());
|
|
333
|
-
const runtime = (payload["runtime"] ?? payload);
|
|
334
|
-
const runtimeId = (typeof runtime?.["runtimeId"] === "string" ? runtime["runtimeId"] : null) ??
|
|
335
|
-
(typeof runtime?.["runtime_id"] === "string" ? runtime["runtime_id"] : null) ??
|
|
336
|
-
null;
|
|
337
|
-
const leaseId = (typeof runtime?.["leaseId"] === "string" ? runtime["leaseId"] : null) ??
|
|
338
|
-
(typeof runtime?.["lease_id"] === "string" ? runtime["lease_id"] : null) ??
|
|
339
|
-
null;
|
|
340
|
-
if (!runtimeId || !runtimeId.trim()) {
|
|
341
|
-
throw new Error("Instafy server response missing runtimeId while requesting runtime lease.");
|
|
342
|
-
}
|
|
343
|
-
if (!leaseId || !leaseId.trim()) {
|
|
344
|
-
throw new Error("Instafy server response missing leaseId while requesting runtime lease.");
|
|
345
|
-
}
|
|
346
|
-
return { runtimeId: runtimeId.trim(), leaseId: leaseId.trim(), accessToken };
|
|
347
|
-
}
|
|
348
|
-
function isProcessAlive(pid) {
|
|
349
|
-
try {
|
|
350
|
-
process.kill(pid, 0);
|
|
351
|
-
return true;
|
|
352
|
-
}
|
|
353
|
-
catch {
|
|
354
|
-
return false;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
export async function runtimeStart(options) {
|
|
358
|
-
const env = { ...process.env };
|
|
359
|
-
const cwd = process.cwd();
|
|
360
|
-
const existing = readState();
|
|
361
|
-
if (existing) {
|
|
362
|
-
if (existing.runner === "docker" && existing.containerId && dockerContainerRunning(existing.containerId)) {
|
|
363
|
-
throw new Error(`Runtime already running (docker container ${existing.containerId}) for project ${existing.projectId}. Stop it first.`);
|
|
364
|
-
}
|
|
365
|
-
if ((!existing.runner || existing.runner === "process") && isProcessAlive(existing.pid)) {
|
|
366
|
-
throw new Error(`Runtime already running (pid ${existing.pid}) for project ${existing.projectId}. Stop it first.`);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
const manifestInfo = findProjectManifest(cwd);
|
|
370
|
-
const projectId = options.project ?? env["PROJECT_ID"] ?? manifestInfo.manifest?.projectId ?? null;
|
|
371
|
-
if (!projectId) {
|
|
372
|
-
throw new Error("No project configured. Run `instafy project init` in this folder (recommended) or pass --project.");
|
|
373
|
-
}
|
|
374
|
-
env["PROJECT_ID"] = projectId;
|
|
375
|
-
const supabaseAccessToken = resolveSupabaseAccessToken(options, env);
|
|
376
|
-
if (supabaseAccessToken) {
|
|
377
|
-
env["SUPABASE_ACCESS_TOKEN"] = supabaseAccessToken;
|
|
378
|
-
}
|
|
379
|
-
const profile = resolveActiveProfileName({ cwd });
|
|
380
|
-
const controllerAccessTokenResult = resolveControllerAccessTokenForCliWithSource(options, env, supabaseAccessToken, profile, cwd);
|
|
381
|
-
let controllerAccessToken = controllerAccessTokenResult.token;
|
|
382
|
-
const controllerAccessTokenSource = controllerAccessTokenResult.source;
|
|
383
|
-
let runtimeAccessToken = normalizeToken(options.runtimeToken) ?? normalizeToken(env["RUNTIME_ACCESS_TOKEN"]);
|
|
384
|
-
const agentKey = options.controllerToken ??
|
|
385
|
-
env["INSTAFY_SERVICE_TOKEN"] ??
|
|
386
|
-
env["AGENT_LOGIN_KEY"] ??
|
|
387
|
-
env["AGENT_KEY"];
|
|
388
|
-
if (!agentKey && !controllerAccessToken && !runtimeAccessToken) {
|
|
389
|
-
throw new Error("Login required. Run `instafy login`, or pass --access-token / --supabase-access-token, or provide --runtime-token.");
|
|
390
|
-
}
|
|
391
|
-
if (agentKey) {
|
|
392
|
-
env["AGENT_LOGIN_KEY"] = agentKey;
|
|
393
|
-
}
|
|
394
|
-
if (controllerAccessToken) {
|
|
395
|
-
env["CONTROLLER_ACCESS_TOKEN"] = controllerAccessToken;
|
|
396
|
-
}
|
|
397
|
-
env["CONTROLLER_BASE_URL"] =
|
|
398
|
-
options.controllerUrl ??
|
|
399
|
-
env["INSTAFY_SERVER_URL"] ??
|
|
400
|
-
env["CONTROLLER_BASE_URL"] ??
|
|
401
|
-
manifestInfo.manifest?.controllerUrl ??
|
|
402
|
-
"http://127.0.0.1:8788";
|
|
403
|
-
if (options.codexBin)
|
|
404
|
-
env["CODEX_BIN"] = options.codexBin;
|
|
405
|
-
if (options.proxyBaseUrl)
|
|
406
|
-
env["PROXY_BASE_URL"] = options.proxyBaseUrl;
|
|
407
|
-
if (!env["CODEX_REASONING_EFFORT"]) {
|
|
408
|
-
env["CODEX_REASONING_EFFORT"] = "low";
|
|
409
|
-
}
|
|
410
|
-
const workspace = path.resolve(options.workspace ?? env["WORKSPACE_DIR"] ?? path.join(process.cwd(), ".instafy", "workspace"));
|
|
411
|
-
mkdirSync(workspace, { recursive: true });
|
|
412
|
-
env["WORKSPACE_DIR"] = workspace;
|
|
413
|
-
const originId = options.originId ?? env["ORIGIN_ID"] ?? randomUUID();
|
|
414
|
-
env["ORIGIN_ID"] = originId;
|
|
415
|
-
env["ORIGIN_BIND_HOST"] = options.bindHost ?? env["ORIGIN_BIND_HOST"] ?? "127.0.0.1";
|
|
416
|
-
env["ORIGIN_BIND_PORT"] = String(options.bindPort ?? env["ORIGIN_BIND_PORT"] ?? 54332);
|
|
417
|
-
env["ORIGIN_PROTOCOLS"] = env["ORIGIN_PROTOCOLS"] ?? "http";
|
|
418
|
-
if (options.originEndpoint && options.originEndpoint.trim()) {
|
|
419
|
-
env["ORIGIN_ENDPOINT"] = options.originEndpoint.trim();
|
|
420
|
-
}
|
|
421
|
-
const skipAuthRaw = env["ORIGIN_SKIP_AUTH"]?.trim().toLowerCase();
|
|
422
|
-
const skipAuth = skipAuthRaw === "1" || skipAuthRaw === "true" || skipAuthRaw === "yes";
|
|
423
|
-
const usesTunnel = !env["ORIGIN_ENDPOINT"];
|
|
424
|
-
const usesExplicitEndpoint = Boolean(env["ORIGIN_ENDPOINT"]);
|
|
425
|
-
if (skipAuth && (usesTunnel || usesExplicitEndpoint)) {
|
|
426
|
-
console.warn(kleur.yellow("Warning: ORIGIN_SKIP_AUTH is enabled while the origin may be reachable remotely (tunnel or --origin-endpoint). " +
|
|
427
|
-
"This is unsafe; disable ORIGIN_SKIP_AUTH for any non-local usage."));
|
|
428
|
-
}
|
|
429
|
-
env["RUNTIME_DISPLAY_NAME"] =
|
|
430
|
-
options.displayName ?? env["RUNTIME_DISPLAY_NAME"] ?? "Instafy CLI Runtime";
|
|
431
|
-
env["RUNTIME_PROVIDER"] =
|
|
432
|
-
options.provider ?? env["RUNTIME_PROVIDER"] ?? "self-hosted";
|
|
433
|
-
env["RUNTIME_VERSION"] = env["RUNTIME_VERSION"] ?? "0.1.0";
|
|
434
|
-
env["RUNTIME_REQUIRE_CODEX_BIN"] = env["RUNTIME_REQUIRE_CODEX_BIN"] || "0";
|
|
435
|
-
env["RUNTIME_CAPABILITIES"] =
|
|
436
|
-
env["RUNTIME_CAPABILITIES"] ||
|
|
437
|
-
JSON.stringify({ fs: true, github: true, supabase: true, runs: true, agent: true, origin: true });
|
|
438
|
-
if (options.runtimeId && options.runtimeId.trim()) {
|
|
439
|
-
env["RUNTIME_ID"] = options.runtimeId.trim();
|
|
440
|
-
}
|
|
441
|
-
if (options.runtimeLeaseId && options.runtimeLeaseId.trim()) {
|
|
442
|
-
env["RUNTIME_LEASE_ID"] = options.runtimeLeaseId.trim();
|
|
443
|
-
}
|
|
444
|
-
const runtimeMode = (options.runtimeMode ?? "auto").toLowerCase();
|
|
445
|
-
const runtimeBin = runtimeMode === "docker" ? null : tryResolveRuntimeBinary();
|
|
446
|
-
const useDocker = runtimeMode === "docker" ||
|
|
447
|
-
(runtimeMode === "auto" && !runtimeBin);
|
|
448
|
-
if (useDocker && !isDockerAvailable()) {
|
|
449
|
-
throw new Error("runtime-agent is not available (no local binary and docker is not installed). Install Docker Desktop, or build runtime-agent from source.");
|
|
450
|
-
}
|
|
451
|
-
const originEnabledRaw = env["ORIGIN_ENABLED"]?.trim().toLowerCase();
|
|
452
|
-
const originEnabled = !originEnabledRaw ||
|
|
453
|
-
originEnabledRaw === "1" ||
|
|
454
|
-
originEnabledRaw === "true" ||
|
|
455
|
-
originEnabledRaw === "yes" ||
|
|
456
|
-
originEnabledRaw === "on";
|
|
457
|
-
if (originEnabled && usesTunnel && !useDocker) {
|
|
458
|
-
const ratholeResolved = await resolveRatholeBinaryForCli({
|
|
459
|
-
env,
|
|
460
|
-
version: process.env["RATHOLE_VERSION"] ?? null,
|
|
461
|
-
cacheDir: process.env["RATHOLE_CACHE_DIR"] ?? null,
|
|
462
|
-
logger: (message) => console.log(kleur.cyan(`[rathole] ${message}`)),
|
|
463
|
-
warn: (message) => console.warn(kleur.yellow(message)),
|
|
464
|
-
});
|
|
465
|
-
if (!ratholeResolved) {
|
|
466
|
-
throw new Error("Tunnel is required but rathole is unavailable. Set RATHOLE_BIN (recommended) or pass --origin-endpoint to use a reachable URL.");
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
if (originEnabled && usesTunnel && !env["RUNTIME_LEASE_ID"]) {
|
|
470
|
-
const bearer = controllerAccessToken ?? runtimeAccessToken;
|
|
471
|
-
const tokenSource = controllerAccessToken ? controllerAccessTokenSource : "env";
|
|
472
|
-
if (!bearer) {
|
|
473
|
-
throw new Error("Login required to request a tunnel-enabled runtime lease. Run `instafy login` or pass --controller-access-token/--runtime-token.");
|
|
474
|
-
}
|
|
475
|
-
const ttl = typeof env["RUNTIME_LEASE_SECONDS"] === "string" ? Number(env["RUNTIME_LEASE_SECONDS"]) : NaN;
|
|
476
|
-
const idleTtlSeconds = Number.isFinite(ttl) && ttl > 0 ? ttl : undefined;
|
|
477
|
-
const lease = await requestRuntimeLease({
|
|
478
|
-
controllerUrl: env["CONTROLLER_BASE_URL"],
|
|
479
|
-
accessToken: bearer,
|
|
480
|
-
tokenSource,
|
|
481
|
-
profile,
|
|
482
|
-
cwd,
|
|
483
|
-
projectId,
|
|
484
|
-
provider: env["RUNTIME_PROVIDER"],
|
|
485
|
-
displayName: env["RUNTIME_DISPLAY_NAME"],
|
|
486
|
-
idleTtlSeconds,
|
|
487
|
-
requestTunnel: false,
|
|
488
|
-
runtimeId: env["RUNTIME_ID"],
|
|
489
|
-
});
|
|
490
|
-
env["RUNTIME_ID"] = lease.runtimeId;
|
|
491
|
-
env["RUNTIME_LEASE_ID"] = lease.leaseId;
|
|
492
|
-
if (controllerAccessToken && bearer === controllerAccessToken) {
|
|
493
|
-
controllerAccessToken = lease.accessToken;
|
|
494
|
-
env["CONTROLLER_ACCESS_TOKEN"] = controllerAccessToken;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
if (!runtimeAccessToken && controllerAccessToken) {
|
|
498
|
-
runtimeAccessToken = await mintRuntimeAccessToken({
|
|
499
|
-
controllerUrl: env["CONTROLLER_BASE_URL"],
|
|
500
|
-
controllerAccessToken,
|
|
501
|
-
projectId,
|
|
502
|
-
runtimeId: env["RUNTIME_ID"],
|
|
503
|
-
leaseId: env["RUNTIME_LEASE_ID"],
|
|
504
|
-
tokenSource: controllerAccessTokenSource,
|
|
505
|
-
profile,
|
|
506
|
-
cwd,
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
if (runtimeAccessToken) {
|
|
510
|
-
env["RUNTIME_ACCESS_TOKEN"] = runtimeAccessToken;
|
|
511
|
-
}
|
|
512
|
-
let originToken = normalizeToken(options.originToken) ?? normalizeToken(env["ORIGIN_INTERNAL_TOKEN"]);
|
|
513
|
-
if (!originToken && runtimeAccessToken) {
|
|
514
|
-
originToken = runtimeAccessToken;
|
|
515
|
-
}
|
|
516
|
-
if (!originToken && controllerAccessToken) {
|
|
517
|
-
originToken = await mintRuntimeAccessToken({
|
|
518
|
-
controllerUrl: env["CONTROLLER_BASE_URL"],
|
|
519
|
-
controllerAccessToken,
|
|
520
|
-
projectId,
|
|
521
|
-
runtimeId: env["RUNTIME_ID"],
|
|
522
|
-
leaseId: env["RUNTIME_LEASE_ID"],
|
|
523
|
-
tokenSource: controllerAccessTokenSource,
|
|
524
|
-
profile,
|
|
525
|
-
cwd,
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
if (!originToken) {
|
|
529
|
-
throw new Error("Runtime/origin token is required (--origin-token or ORIGIN_INTERNAL_TOKEN), or provide --runtime-token/--controller-access-token to mint/use one");
|
|
530
|
-
}
|
|
531
|
-
env["ORIGIN_INTERNAL_TOKEN"] = originToken;
|
|
532
|
-
if (!useDocker) {
|
|
533
|
-
if (!env["ORIGIN_ENDPOINT"]) {
|
|
534
|
-
const ratholeResolved = await resolveRatholeBinaryForCli({
|
|
535
|
-
env,
|
|
536
|
-
version: process.env["RATHOLE_VERSION"] ?? null,
|
|
537
|
-
cacheDir: process.env["RATHOLE_CACHE_DIR"] ?? null,
|
|
538
|
-
logger: (message) => console.log(kleur.cyan(`[rathole] ${message}`)),
|
|
539
|
-
warn: (message) => console.warn(kleur.yellow(message)),
|
|
540
|
-
});
|
|
541
|
-
if (!ratholeResolved) {
|
|
542
|
-
throw new Error("Tunnel is required but rathole is unavailable. Set RATHOLE_BIN (recommended) or pass --origin-endpoint to use a reachable URL.");
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
const bin = runtimeBin ?? resolveRuntimeBinary();
|
|
546
|
-
printStatus("Starting runtime-agent:", bin);
|
|
547
|
-
printStatus("Project:", projectId);
|
|
548
|
-
printStatus("Workspace:", workspace);
|
|
549
|
-
printStatus("Origin:", originId);
|
|
550
|
-
const detached = Boolean(options.detach);
|
|
551
|
-
const logFile = options.logFile?.trim()
|
|
552
|
-
? path.resolve(options.logFile)
|
|
553
|
-
: detached
|
|
554
|
-
? path.join(LOG_DIR, `runtime-${Date.now()}.log`)
|
|
555
|
-
: undefined;
|
|
556
|
-
if (logFile) {
|
|
557
|
-
ensureLogDir();
|
|
558
|
-
}
|
|
559
|
-
let stdio;
|
|
560
|
-
if (!logFile && !detached) {
|
|
561
|
-
stdio = "inherit";
|
|
562
|
-
}
|
|
563
|
-
else {
|
|
564
|
-
const out = logFile ? openSync(logFile, "a") : "ignore";
|
|
565
|
-
stdio = ["ignore", out, out];
|
|
566
|
-
}
|
|
567
|
-
const spawnOptions = { env, stdio, detached };
|
|
568
|
-
const child = spawn(bin, spawnOptions);
|
|
569
|
-
if (detached) {
|
|
570
|
-
child.unref();
|
|
571
|
-
}
|
|
572
|
-
const startedAt = new Date().toISOString();
|
|
573
|
-
writeState({
|
|
574
|
-
runner: "process",
|
|
575
|
-
pid: child.pid ?? -1,
|
|
576
|
-
projectId,
|
|
577
|
-
workspace,
|
|
578
|
-
controllerUrl: env["CONTROLLER_BASE_URL"],
|
|
579
|
-
originId,
|
|
580
|
-
runtimeId: env["RUNTIME_ID"] ?? null,
|
|
581
|
-
displayName: env["RUNTIME_DISPLAY_NAME"],
|
|
582
|
-
startedAt,
|
|
583
|
-
logFile,
|
|
584
|
-
detached,
|
|
585
|
-
});
|
|
586
|
-
child.on("exit", (code, signal) => {
|
|
587
|
-
const msg = code !== null ? `code ${code}` : `signal ${signal}`;
|
|
588
|
-
console.log(kleur.yellow(`runtime-agent exited (${msg})`));
|
|
589
|
-
const current = readState();
|
|
590
|
-
if (current && current.pid === child.pid) {
|
|
591
|
-
clearState();
|
|
592
|
-
}
|
|
593
|
-
});
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
const image = resolveRuntimeAgentImage();
|
|
597
|
-
const containerName = `instafy-runtime-${originId}`;
|
|
598
|
-
const hostPort = String(options.bindPort ?? env["ORIGIN_BIND_PORT"] ?? 54332);
|
|
599
|
-
const hostBindHost = options.bindHost ?? "127.0.0.1";
|
|
600
|
-
const dockerEnv = { ...env };
|
|
601
|
-
dockerEnv["WORKSPACE_DIR"] = "/workspace";
|
|
602
|
-
dockerEnv["CODEX_HOME"] = "/workspace/.codex";
|
|
603
|
-
dockerEnv["TMPDIR"] = "/tmp";
|
|
604
|
-
dockerEnv["TMP"] = "/tmp";
|
|
605
|
-
dockerEnv["TEMP"] = "/tmp";
|
|
606
|
-
dockerEnv["ORIGIN_BIND_HOST"] = "0.0.0.0";
|
|
607
|
-
dockerEnv["CONTROLLER_BASE_URL"] = normalizeControllerUrlForDocker(env["CONTROLLER_BASE_URL"] ?? "");
|
|
608
|
-
const envArgs = [];
|
|
609
|
-
for (const [key, value] of Object.entries(dockerEnv)) {
|
|
610
|
-
if (typeof value !== "string" || value.length === 0)
|
|
611
|
-
continue;
|
|
612
|
-
envArgs.push("-e", `${key}=${value}`);
|
|
613
|
-
}
|
|
614
|
-
const runArgs = [
|
|
615
|
-
"run",
|
|
616
|
-
"--detach",
|
|
617
|
-
"--name",
|
|
618
|
-
containerName,
|
|
619
|
-
"--add-host",
|
|
620
|
-
"host.docker.internal:host-gateway",
|
|
621
|
-
"-p",
|
|
622
|
-
`${hostBindHost}:${hostPort}:${hostPort}`,
|
|
623
|
-
"-v",
|
|
624
|
-
`${workspace}:/workspace`,
|
|
625
|
-
...envArgs,
|
|
626
|
-
image,
|
|
627
|
-
];
|
|
628
|
-
printStatus("Starting runtime-agent (docker):", image);
|
|
629
|
-
printStatus("Project:", projectId);
|
|
630
|
-
printStatus("Workspace:", workspace);
|
|
631
|
-
printStatus("Origin:", originId);
|
|
632
|
-
const started = spawnSync("docker", runArgs, { encoding: "utf8" });
|
|
633
|
-
if (started.status !== 0) {
|
|
634
|
-
const stderr = String(started.stderr ?? "").trim();
|
|
635
|
-
const normalized = (stderr || "").toLowerCase();
|
|
636
|
-
const hints = [];
|
|
637
|
-
if (image.startsWith("ghcr.io/") && (normalized.includes("not found") || normalized.includes("denied") || normalized.includes("unauthorized") || normalized.includes("manifest unknown"))) {
|
|
638
|
-
hints.push("If this is a GHCR image, ensure it's published and you are authenticated (`docker login ghcr.io`).");
|
|
639
|
-
}
|
|
640
|
-
hints.push("Override the image with `INSTAFY_RUNTIME_AGENT_IMAGE=... instafy runtime start`.");
|
|
641
|
-
const suffix = hints.length > 0 ? `\n\nNext:\n- ${hints.join("\n- ")}` : "";
|
|
642
|
-
throw new Error(`docker run failed: ${stderr || "unknown error"}${suffix}`);
|
|
643
|
-
}
|
|
644
|
-
const containerId = String(started.stdout ?? "").trim();
|
|
645
|
-
if (!containerId) {
|
|
646
|
-
throw new Error("docker run did not return a container id");
|
|
647
|
-
}
|
|
648
|
-
const startedAt = new Date().toISOString();
|
|
649
|
-
writeState({
|
|
650
|
-
runner: "docker",
|
|
651
|
-
pid: -1,
|
|
652
|
-
projectId,
|
|
653
|
-
workspace,
|
|
654
|
-
controllerUrl: env["CONTROLLER_BASE_URL"],
|
|
655
|
-
originId,
|
|
656
|
-
runtimeId: env["RUNTIME_ID"] ?? null,
|
|
657
|
-
displayName: env["RUNTIME_DISPLAY_NAME"],
|
|
658
|
-
startedAt,
|
|
659
|
-
detached: Boolean(options.detach),
|
|
660
|
-
containerId,
|
|
661
|
-
containerName,
|
|
662
|
-
});
|
|
663
|
-
if (options.detach) {
|
|
664
|
-
printStatus("Container:", containerId);
|
|
665
|
-
console.log(kleur.gray(`Use: docker logs -f ${containerId}`));
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
const logs = spawn("docker", ["logs", "-f", containerId], { stdio: "inherit" });
|
|
669
|
-
const handleExit = async () => {
|
|
670
|
-
stopDockerContainer(containerId);
|
|
671
|
-
const current = readState();
|
|
672
|
-
if (current?.containerId === containerId) {
|
|
673
|
-
clearState();
|
|
674
|
-
}
|
|
675
|
-
};
|
|
676
|
-
process.once("SIGINT", () => {
|
|
677
|
-
void handleExit();
|
|
678
|
-
});
|
|
679
|
-
process.once("SIGTERM", () => {
|
|
680
|
-
void handleExit();
|
|
681
|
-
});
|
|
682
|
-
logs.on("exit", () => {
|
|
683
|
-
void handleExit();
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
function formatStatus(state, running) {
|
|
687
|
-
return {
|
|
688
|
-
running,
|
|
689
|
-
runner: state.runner ?? "process",
|
|
690
|
-
pid: state.pid,
|
|
691
|
-
containerId: state.containerId ?? null,
|
|
692
|
-
containerName: state.containerName ?? null,
|
|
693
|
-
projectId: state.projectId,
|
|
694
|
-
workspace: state.workspace,
|
|
695
|
-
controllerUrl: state.controllerUrl ?? null,
|
|
696
|
-
originId: state.originId ?? null,
|
|
697
|
-
runtimeId: state.runtimeId ?? null,
|
|
698
|
-
displayName: state.displayName ?? null,
|
|
699
|
-
startedAt: state.startedAt,
|
|
700
|
-
logFile: state.logFile ?? null,
|
|
701
|
-
detached: state.detached ?? false,
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
async function sendOfflineBeat(state) {
|
|
705
|
-
if (!state.controllerUrl || !state.projectId || !state.originId) {
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
|
-
const directToken = normalizeToken(process.env["RUNTIME_ACCESS_TOKEN"]) ??
|
|
709
|
-
normalizeToken(process.env["ORIGIN_INTERNAL_TOKEN"]);
|
|
710
|
-
let bearer = directToken;
|
|
711
|
-
if (!bearer) {
|
|
712
|
-
const controllerAccessToken = normalizeToken(process.env["CONTROLLER_ACCESS_TOKEN"]) ??
|
|
713
|
-
normalizeToken(process.env["SUPABASE_ACCESS_TOKEN"]) ??
|
|
714
|
-
resolveConfiguredAccessToken();
|
|
715
|
-
if (controllerAccessToken) {
|
|
716
|
-
try {
|
|
717
|
-
bearer = await mintRuntimeAccessToken({
|
|
718
|
-
controllerUrl: state.controllerUrl,
|
|
719
|
-
controllerAccessToken,
|
|
720
|
-
projectId: state.projectId,
|
|
721
|
-
runtimeId: state.runtimeId ?? undefined,
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
catch (error) {
|
|
725
|
-
const suffix = error instanceof Error ? `: ${error.message}` : error ? `: ${String(error)}` : "";
|
|
726
|
-
console.warn(kleur.yellow(`Could not mint runtime token for offline beat${suffix}`));
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
if (!bearer) {
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
const offlinePayload = {
|
|
734
|
-
projectId: state.projectId,
|
|
735
|
-
originId: state.originId,
|
|
736
|
-
status: "offline",
|
|
737
|
-
latencyMs: null,
|
|
738
|
-
region: "iad",
|
|
739
|
-
metadata: { cli: true },
|
|
740
|
-
};
|
|
741
|
-
try {
|
|
742
|
-
await fetch(`${state.controllerUrl.replace(/\/$/, "")}/projects/${encodeURIComponent(state.projectId)}/origin/presence/beat`, {
|
|
743
|
-
method: "POST",
|
|
744
|
-
headers: {
|
|
745
|
-
authorization: `Bearer ${bearer}`,
|
|
746
|
-
"content-type": "application/json",
|
|
747
|
-
},
|
|
748
|
-
body: JSON.stringify(offlinePayload),
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
catch {
|
|
752
|
-
// ignore failures
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
async function waitForExit(pid, timeoutMs) {
|
|
756
|
-
const start = Date.now();
|
|
757
|
-
while (Date.now() - start < timeoutMs) {
|
|
758
|
-
if (!isProcessAlive(pid))
|
|
759
|
-
return true;
|
|
760
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
761
|
-
}
|
|
762
|
-
return !isProcessAlive(pid);
|
|
763
|
-
}
|
|
764
|
-
export async function runtimeStatus(options) {
|
|
765
|
-
const state = readState();
|
|
766
|
-
if (!state) {
|
|
767
|
-
if (options?.json) {
|
|
768
|
-
console.log(JSON.stringify({ running: false }));
|
|
769
|
-
}
|
|
770
|
-
else {
|
|
771
|
-
console.log(kleur.yellow("No runtime state found."));
|
|
772
|
-
}
|
|
773
|
-
return;
|
|
774
|
-
}
|
|
775
|
-
const alive = state.runner === "docker"
|
|
776
|
-
? Boolean(state.containerId && dockerContainerRunning(state.containerId))
|
|
777
|
-
: isProcessAlive(state.pid);
|
|
778
|
-
const payload = formatStatus(state, alive);
|
|
779
|
-
if (options?.json) {
|
|
780
|
-
console.log(JSON.stringify(payload));
|
|
781
|
-
return;
|
|
782
|
-
}
|
|
783
|
-
console.log(alive ? kleur.green("Runtime is running") : kleur.red("Runtime is not running"));
|
|
784
|
-
printStatus("Runner:", state.runner ?? "process");
|
|
785
|
-
if (state.runner === "docker") {
|
|
786
|
-
if (state.containerId)
|
|
787
|
-
printStatus("Container:", state.containerId);
|
|
788
|
-
if (state.containerName)
|
|
789
|
-
printStatus("Name:", state.containerName);
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
printStatus("PID:", String(state.pid));
|
|
793
|
-
}
|
|
794
|
-
printStatus("Project:", state.projectId);
|
|
795
|
-
printStatus("Workspace:", state.workspace);
|
|
796
|
-
if (state.controllerUrl)
|
|
797
|
-
printStatus("Server:", state.controllerUrl);
|
|
798
|
-
if (state.originId)
|
|
799
|
-
printStatus("Origin:", state.originId);
|
|
800
|
-
if (state.runtimeId)
|
|
801
|
-
printStatus("Runtime:", state.runtimeId);
|
|
802
|
-
if (state.displayName)
|
|
803
|
-
printStatus("Display:", state.displayName);
|
|
804
|
-
printStatus("Started:", state.startedAt);
|
|
805
|
-
if (state.logFile)
|
|
806
|
-
printStatus("Log:", state.logFile);
|
|
807
|
-
printStatus("Detached:", state.detached ? "yes" : "no");
|
|
808
|
-
if (!alive) {
|
|
809
|
-
console.log(kleur.yellow(`State file exists but ${state.runner === "docker" ? "container" : "process"} is not alive.`));
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
export async function runtimeStop(options) {
|
|
813
|
-
const state = readState();
|
|
814
|
-
if (!state) {
|
|
815
|
-
if (options?.json) {
|
|
816
|
-
console.log(JSON.stringify({ stopped: false, reason: "not_running" }));
|
|
817
|
-
}
|
|
818
|
-
else {
|
|
819
|
-
console.log(kleur.yellow("Runtime not running."));
|
|
820
|
-
}
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
if (state.runner === "docker") {
|
|
824
|
-
const running = Boolean(state.containerId && dockerContainerRunning(state.containerId));
|
|
825
|
-
if (state.containerId) {
|
|
826
|
-
stopDockerContainer(state.containerId);
|
|
827
|
-
}
|
|
828
|
-
clearState();
|
|
829
|
-
await sendOfflineBeat(state);
|
|
830
|
-
const result = { stopped: running, containerId: state.containerId ?? null };
|
|
831
|
-
if (options?.json) {
|
|
832
|
-
console.log(JSON.stringify(result));
|
|
833
|
-
}
|
|
834
|
-
else {
|
|
835
|
-
console.log(running ? kleur.green("Runtime stopped.") : kleur.yellow("Runtime not running."));
|
|
836
|
-
}
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
if (!isProcessAlive(state.pid)) {
|
|
840
|
-
clearState();
|
|
841
|
-
if (options?.json) {
|
|
842
|
-
console.log(JSON.stringify({ stopped: false, reason: "not_running" }));
|
|
843
|
-
}
|
|
844
|
-
else {
|
|
845
|
-
console.log(kleur.yellow("Runtime process already stopped. State cleared."));
|
|
846
|
-
}
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
try {
|
|
850
|
-
process.kill(state.pid, "SIGINT");
|
|
851
|
-
}
|
|
852
|
-
catch (error) {
|
|
853
|
-
if (options?.json) {
|
|
854
|
-
console.log(JSON.stringify({ stopped: false, error: String(error) }));
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
857
|
-
throw error;
|
|
858
|
-
}
|
|
859
|
-
const exited = await waitForExit(state.pid, 5000);
|
|
860
|
-
if (!exited) {
|
|
861
|
-
try {
|
|
862
|
-
process.kill(state.pid, "SIGTERM");
|
|
863
|
-
}
|
|
864
|
-
catch {
|
|
865
|
-
// ignore
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
const alive = isProcessAlive(state.pid);
|
|
869
|
-
if (!alive) {
|
|
870
|
-
clearState();
|
|
871
|
-
}
|
|
872
|
-
await sendOfflineBeat(state);
|
|
873
|
-
const result = { stopped: !alive, pid: state.pid };
|
|
874
|
-
if (options?.json) {
|
|
875
|
-
console.log(JSON.stringify(result));
|
|
876
|
-
}
|
|
877
|
-
else {
|
|
878
|
-
console.log(!alive ? kleur.green("Runtime stopped.") : kleur.red("Failed to stop runtime."));
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
export async function runtimeToken(options) {
|
|
882
|
-
const cwd = process.cwd();
|
|
883
|
-
const controllerUrl = options.controllerUrl ??
|
|
884
|
-
process.env["INSTAFY_SERVER_URL"] ??
|
|
885
|
-
process.env["CONTROLLER_BASE_URL"] ??
|
|
886
|
-
"http://127.0.0.1:8788";
|
|
887
|
-
const profile = resolveActiveProfileName({ cwd });
|
|
888
|
-
const stored = resolveConfiguredAccessToken({ profile, cwd });
|
|
889
|
-
const tokenSource = options.controllerAccessToken
|
|
890
|
-
? "explicit"
|
|
891
|
-
: process.env["INSTAFY_ACCESS_TOKEN"] ||
|
|
892
|
-
process.env["CONTROLLER_ACCESS_TOKEN"] ||
|
|
893
|
-
process.env["SUPABASE_ACCESS_TOKEN"]
|
|
894
|
-
? "env"
|
|
895
|
-
: stored
|
|
896
|
-
? "config"
|
|
897
|
-
: "none";
|
|
898
|
-
const token = options.controllerAccessToken ??
|
|
899
|
-
process.env["INSTAFY_ACCESS_TOKEN"] ??
|
|
900
|
-
process.env["CONTROLLER_ACCESS_TOKEN"] ??
|
|
901
|
-
process.env["SUPABASE_ACCESS_TOKEN"] ??
|
|
902
|
-
stored;
|
|
903
|
-
if (!token) {
|
|
904
|
-
throw new Error("Login required. Run `instafy login` or pass --access-token / set SUPABASE_ACCESS_TOKEN.");
|
|
905
|
-
}
|
|
906
|
-
const minted = await mintRuntimeAccessToken({
|
|
907
|
-
controllerUrl,
|
|
908
|
-
controllerAccessToken: token,
|
|
909
|
-
projectId: options.project,
|
|
910
|
-
runtimeId: options.runtimeId,
|
|
911
|
-
scopes: options.scopes,
|
|
912
|
-
tokenSource: tokenSource === "none" ? "env" : tokenSource,
|
|
913
|
-
profile,
|
|
914
|
-
cwd,
|
|
915
|
-
});
|
|
916
|
-
if (options.json) {
|
|
917
|
-
console.log(JSON.stringify({ token: minted }));
|
|
918
|
-
}
|
|
919
|
-
else {
|
|
920
|
-
console.log(minted);
|
|
921
|
-
}
|
|
922
|
-
return minted;
|
|
923
|
-
}
|