@thyn-ai/sqai 0.1.4 → 0.1.5
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 +202 -0
- package/dist/index.cjs +241 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -1
- package/dist/index.d.ts +31 -1
- package/dist/index.js +201 -32
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.d.cts
CHANGED
|
@@ -575,6 +575,36 @@ declare class ResultStore {
|
|
|
575
575
|
private evictOldestForTenant;
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
+
/**
|
|
579
|
+
* `sqai login` — device onboarding for the SQAI runtime (JS/TS side; mirrors the Python sqai._login).
|
|
580
|
+
*
|
|
581
|
+
* Authenticates via the shared accounts portal (OAuth 2.0 device grant, RFC 8628), resolves-or-creates
|
|
582
|
+
* the caller's org on the free `sqai_developer` tier, and mints a control-plane API key. The key is
|
|
583
|
+
* cached under ~/.sqai and handed to the local runtime daemon (as ALGENTA_API_KEY), which exchanges it
|
|
584
|
+
* once for a platform-signed, device-bound offline license — then everything runs locally, no query-time
|
|
585
|
+
* network. Same device tech telys/algenta use; only the product literals differ.
|
|
586
|
+
*
|
|
587
|
+
* Free forever on 1 machine. Paid tiers (sqai_pro / sqai_team) add devices/seats/commercial use via the
|
|
588
|
+
* accounts billing portal — never a cloud service. Login is licensing-only; no data leaves the machine.
|
|
589
|
+
*/
|
|
590
|
+
declare class LoginError extends Error {
|
|
591
|
+
}
|
|
592
|
+
declare function sqaiHome(): string;
|
|
593
|
+
/** Resolve the SQAI API key for the runtime: $SQAI_API_KEY / $ALGENTA_API_KEY, else the cached key. */
|
|
594
|
+
declare function readCachedApiKey(): Promise<string | undefined>;
|
|
595
|
+
interface LoginResult {
|
|
596
|
+
device_id: string;
|
|
597
|
+
plan: string;
|
|
598
|
+
api_key_prefix: string;
|
|
599
|
+
}
|
|
600
|
+
declare function login(opts?: {
|
|
601
|
+
plan?: string;
|
|
602
|
+
accessToken?: string;
|
|
603
|
+
openBrowser?: boolean;
|
|
604
|
+
}): Promise<LoginResult>;
|
|
605
|
+
/** Remove cached token + API key (keeps the device identity so a re-login rebinds the same device). */
|
|
606
|
+
declare function logout(): void;
|
|
607
|
+
|
|
578
608
|
/** Computation-plane validation: dynamic, against the embedded capability
|
|
579
609
|
* contract. The zod wire schema (in @thyn-ai/sqai-ai-sdk) only transports values;
|
|
580
610
|
* everything here authorizes them. SQAI computes nothing — a validated
|
|
@@ -601,4 +631,4 @@ interface ValidatedComputation {
|
|
|
601
631
|
declare function lookupCapability(index: ContractIndex, module: string, functionName: string): CapabilityEntry;
|
|
602
632
|
declare function validateComputation(spec: ComputationSpec, index: ContractIndex, policy: SqaiPolicy | undefined, resolvedBindings: ResolvedBindingValue[]): ValidatedComputation;
|
|
603
633
|
|
|
604
|
-
export { type AlgentaWireValue, type AskOutcome, type BindingProvenance, type CapabilityContract, type CapabilityEntry, type CapabilityMatch, type ColumnExtract, type ComputationBinding, type ComputationSpec, type ComputeResult, ContractIndex, type DeterminismEnvelope, type InvocationIdentity, type QueryFilterCondition, type QueryFilterSpec, type QuerySpec, type ResolvedBindingValue, ResultStore, type ResultStoreConfig, RuntimeProvisioner, type RuntimeStatus, SQAI, SQAI_ERROR_CODES, type SqaiConfig, SqaiError, type SqaiErrorSource, type SqaiIntent, type SqaiOutcome, type SqaiPolicy, type SqaiResultRecord, type SqaiSource, type SqaiValue, type ValidatedComputation, computationHash, createSQAI, currentPlatformKey, invocationHash, isReadOnlyEligible, lookupCapability, runtimeCacheDir, validateComputation };
|
|
634
|
+
export { type AlgentaWireValue, type AskOutcome, type BindingProvenance, type CapabilityContract, type CapabilityEntry, type CapabilityMatch, type ColumnExtract, type ComputationBinding, type ComputationSpec, type ComputeResult, ContractIndex, type DeterminismEnvelope, type InvocationIdentity, LoginError, type LoginResult, type QueryFilterCondition, type QueryFilterSpec, type QuerySpec, type ResolvedBindingValue, ResultStore, type ResultStoreConfig, RuntimeProvisioner, type RuntimeStatus, SQAI, SQAI_ERROR_CODES, type SqaiConfig, SqaiError, type SqaiErrorSource, type SqaiIntent, type SqaiOutcome, type SqaiPolicy, type SqaiResultRecord, type SqaiSource, type SqaiValue, type ValidatedComputation, computationHash, createSQAI, currentPlatformKey, invocationHash, isReadOnlyEligible, login, logout, lookupCapability, readCachedApiKey, runtimeCacheDir, sqaiHome, validateComputation };
|
package/dist/index.d.ts
CHANGED
|
@@ -575,6 +575,36 @@ declare class ResultStore {
|
|
|
575
575
|
private evictOldestForTenant;
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
+
/**
|
|
579
|
+
* `sqai login` — device onboarding for the SQAI runtime (JS/TS side; mirrors the Python sqai._login).
|
|
580
|
+
*
|
|
581
|
+
* Authenticates via the shared accounts portal (OAuth 2.0 device grant, RFC 8628), resolves-or-creates
|
|
582
|
+
* the caller's org on the free `sqai_developer` tier, and mints a control-plane API key. The key is
|
|
583
|
+
* cached under ~/.sqai and handed to the local runtime daemon (as ALGENTA_API_KEY), which exchanges it
|
|
584
|
+
* once for a platform-signed, device-bound offline license — then everything runs locally, no query-time
|
|
585
|
+
* network. Same device tech telys/algenta use; only the product literals differ.
|
|
586
|
+
*
|
|
587
|
+
* Free forever on 1 machine. Paid tiers (sqai_pro / sqai_team) add devices/seats/commercial use via the
|
|
588
|
+
* accounts billing portal — never a cloud service. Login is licensing-only; no data leaves the machine.
|
|
589
|
+
*/
|
|
590
|
+
declare class LoginError extends Error {
|
|
591
|
+
}
|
|
592
|
+
declare function sqaiHome(): string;
|
|
593
|
+
/** Resolve the SQAI API key for the runtime: $SQAI_API_KEY / $ALGENTA_API_KEY, else the cached key. */
|
|
594
|
+
declare function readCachedApiKey(): Promise<string | undefined>;
|
|
595
|
+
interface LoginResult {
|
|
596
|
+
device_id: string;
|
|
597
|
+
plan: string;
|
|
598
|
+
api_key_prefix: string;
|
|
599
|
+
}
|
|
600
|
+
declare function login(opts?: {
|
|
601
|
+
plan?: string;
|
|
602
|
+
accessToken?: string;
|
|
603
|
+
openBrowser?: boolean;
|
|
604
|
+
}): Promise<LoginResult>;
|
|
605
|
+
/** Remove cached token + API key (keeps the device identity so a re-login rebinds the same device). */
|
|
606
|
+
declare function logout(): void;
|
|
607
|
+
|
|
578
608
|
/** Computation-plane validation: dynamic, against the embedded capability
|
|
579
609
|
* contract. The zod wire schema (in @thyn-ai/sqai-ai-sdk) only transports values;
|
|
580
610
|
* everything here authorizes them. SQAI computes nothing — a validated
|
|
@@ -601,4 +631,4 @@ interface ValidatedComputation {
|
|
|
601
631
|
declare function lookupCapability(index: ContractIndex, module: string, functionName: string): CapabilityEntry;
|
|
602
632
|
declare function validateComputation(spec: ComputationSpec, index: ContractIndex, policy: SqaiPolicy | undefined, resolvedBindings: ResolvedBindingValue[]): ValidatedComputation;
|
|
603
633
|
|
|
604
|
-
export { type AlgentaWireValue, type AskOutcome, type BindingProvenance, type CapabilityContract, type CapabilityEntry, type CapabilityMatch, type ColumnExtract, type ComputationBinding, type ComputationSpec, type ComputeResult, ContractIndex, type DeterminismEnvelope, type InvocationIdentity, type QueryFilterCondition, type QueryFilterSpec, type QuerySpec, type ResolvedBindingValue, ResultStore, type ResultStoreConfig, RuntimeProvisioner, type RuntimeStatus, SQAI, SQAI_ERROR_CODES, type SqaiConfig, SqaiError, type SqaiErrorSource, type SqaiIntent, type SqaiOutcome, type SqaiPolicy, type SqaiResultRecord, type SqaiSource, type SqaiValue, type ValidatedComputation, computationHash, createSQAI, currentPlatformKey, invocationHash, isReadOnlyEligible, lookupCapability, runtimeCacheDir, validateComputation };
|
|
634
|
+
export { type AlgentaWireValue, type AskOutcome, type BindingProvenance, type CapabilityContract, type CapabilityEntry, type CapabilityMatch, type ColumnExtract, type ComputationBinding, type ComputationSpec, type ComputeResult, ContractIndex, type DeterminismEnvelope, type InvocationIdentity, LoginError, type LoginResult, type QueryFilterCondition, type QueryFilterSpec, type QuerySpec, type ResolvedBindingValue, ResultStore, type ResultStoreConfig, RuntimeProvisioner, type RuntimeStatus, SQAI, SQAI_ERROR_CODES, type SqaiConfig, SqaiError, type SqaiErrorSource, type SqaiIntent, type SqaiOutcome, type SqaiPolicy, type SqaiResultRecord, type SqaiSource, type SqaiValue, type ValidatedComputation, computationHash, createSQAI, currentPlatformKey, invocationHash, isReadOnlyEligible, login, logout, lookupCapability, readCachedApiKey, runtimeCacheDir, sqaiHome, validateComputation };
|
package/dist/index.js
CHANGED
|
@@ -315,22 +315,180 @@ var ResultStore = class {
|
|
|
315
315
|
};
|
|
316
316
|
|
|
317
317
|
// src/runtime/provision.ts
|
|
318
|
-
import { homedir, platform as osPlatform, arch as osArch } from "os";
|
|
319
|
-
import { join as
|
|
320
|
-
import { createWriteStream, existsSync, promises as fs2 } from "fs";
|
|
321
|
-
import { createHash as
|
|
318
|
+
import { homedir as homedir2, platform as osPlatform, arch as osArch } from "os";
|
|
319
|
+
import { join as join3 } from "path";
|
|
320
|
+
import { createWriteStream, existsSync as existsSync2, promises as fs2 } from "fs";
|
|
321
|
+
import { createHash as createHash4, randomBytes as randomBytes2 } from "crypto";
|
|
322
322
|
import { Readable } from "stream";
|
|
323
323
|
import { pipeline } from "stream/promises";
|
|
324
324
|
import { MojoRuntime } from "algenta-sdk";
|
|
325
325
|
|
|
326
|
+
// src/runtime/auth.ts
|
|
327
|
+
import { createHash as createHash2, createPrivateKey, createPublicKey, generateKeyPairSync } from "crypto";
|
|
328
|
+
import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync, rmSync } from "fs";
|
|
329
|
+
import { homedir, hostname } from "os";
|
|
330
|
+
import { join } from "path";
|
|
331
|
+
var CLIENT_ID = "sqai-cli";
|
|
332
|
+
var SCOPE = "sqai";
|
|
333
|
+
var PRODUCT = "sqai";
|
|
334
|
+
var DEFAULT_PLAN = "sqai_developer";
|
|
335
|
+
var DEFAULT_ACCOUNTS_URL = "https://accounts.thyn.ai";
|
|
336
|
+
var DEFAULT_API_URL = "https://api.sqai.com";
|
|
337
|
+
var LoginError = class extends Error {
|
|
338
|
+
};
|
|
339
|
+
function sqaiHome() {
|
|
340
|
+
return process.env.SQAI_HOME ?? join(homedir(), ".sqai");
|
|
341
|
+
}
|
|
342
|
+
var p = (name) => join(sqaiHome(), name);
|
|
343
|
+
var apiKeyPath = () => p("api_key");
|
|
344
|
+
var tokenPath = () => p("token");
|
|
345
|
+
var deviceIdPath = () => p("device_id");
|
|
346
|
+
var accountsUrl = () => (process.env.SQAI_ACCOUNTS_URL ?? DEFAULT_ACCOUNTS_URL).replace(/\/$/, "");
|
|
347
|
+
var apiUrl = () => (process.env.SQAI_API_URL ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
348
|
+
function readSecret(path) {
|
|
349
|
+
try {
|
|
350
|
+
const v = readFileSync(path, "utf8").trim();
|
|
351
|
+
return v || void 0;
|
|
352
|
+
} catch {
|
|
353
|
+
return void 0;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
async function readCachedApiKey() {
|
|
357
|
+
return process.env.SQAI_API_KEY ?? process.env.ALGENTA_API_KEY ?? readSecret(apiKeyPath());
|
|
358
|
+
}
|
|
359
|
+
function cache(path, value) {
|
|
360
|
+
mkdirSync(sqaiHome(), { recursive: true });
|
|
361
|
+
writeFileSync(path, value.trim() + "\n", "utf8");
|
|
362
|
+
try {
|
|
363
|
+
chmodSync(path, 384);
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function deviceId() {
|
|
368
|
+
const existing = readSecret(deviceIdPath());
|
|
369
|
+
if (existing) return existing;
|
|
370
|
+
const value = createHash2("sha256").update(`${hostname()}:${Date.now()}:${Math.random()}`).digest("hex");
|
|
371
|
+
cache(deviceIdPath(), value);
|
|
372
|
+
return value;
|
|
373
|
+
}
|
|
374
|
+
async function request(method, url, opts = {}) {
|
|
375
|
+
const headers = { Accept: "application/json", "User-Agent": "sqai-cli" };
|
|
376
|
+
if (opts.body !== void 0) headers["Content-Type"] = "application/json";
|
|
377
|
+
if (opts.bearer) headers["Authorization"] = `Bearer ${opts.bearer}`;
|
|
378
|
+
let res;
|
|
379
|
+
try {
|
|
380
|
+
res = await fetch(url, {
|
|
381
|
+
method,
|
|
382
|
+
headers,
|
|
383
|
+
body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
|
|
384
|
+
});
|
|
385
|
+
} catch (e) {
|
|
386
|
+
throw new LoginError(`${method} ${url}: ${e instanceof Error ? e.message : String(e)}`);
|
|
387
|
+
}
|
|
388
|
+
let json = {};
|
|
389
|
+
try {
|
|
390
|
+
json = await res.json();
|
|
391
|
+
} catch {
|
|
392
|
+
json = {};
|
|
393
|
+
}
|
|
394
|
+
return { status: res.status, json };
|
|
395
|
+
}
|
|
396
|
+
async function deviceAuthorize(accounts, openBrowser, maxWaitMs = 3e5) {
|
|
397
|
+
const { json: start } = await request("POST", `${accounts}/oauth/device/code`, {
|
|
398
|
+
body: { client_id: CLIENT_ID, scope: SCOPE }
|
|
399
|
+
});
|
|
400
|
+
const deviceCode = start.device_code;
|
|
401
|
+
const userCode = start.user_code;
|
|
402
|
+
const verify = start.verification_uri_complete || start.verification_uri;
|
|
403
|
+
const interval = Number(start.interval ?? 5);
|
|
404
|
+
if (!deviceCode || !userCode || !verify) throw new LoginError("accounts device-code response missing fields");
|
|
405
|
+
process.stdout.write(`
|
|
406
|
+
Open ${verify}
|
|
407
|
+
and enter code: ${userCode}
|
|
408
|
+
|
|
409
|
+
`);
|
|
410
|
+
if (openBrowser) await tryOpenBrowser(verify);
|
|
411
|
+
const deadline = Date.now() + maxWaitMs;
|
|
412
|
+
while (Date.now() < deadline) {
|
|
413
|
+
await new Promise((r) => setTimeout(r, interval * 1e3));
|
|
414
|
+
const { status, json: tok } = await request("POST", `${accounts}/oauth/device/token`, {
|
|
415
|
+
body: {
|
|
416
|
+
client_id: CLIENT_ID,
|
|
417
|
+
device_code: deviceCode,
|
|
418
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
const access = tok.access_token;
|
|
422
|
+
if (access) return access;
|
|
423
|
+
const error = tok.error;
|
|
424
|
+
if (error === "authorization_pending" || error === "slow_down" || status === 400 || status === 428) continue;
|
|
425
|
+
throw new LoginError(`device authorization failed: ${error ?? `HTTP ${status}`}`);
|
|
426
|
+
}
|
|
427
|
+
throw new LoginError("timed out waiting for device authorization");
|
|
428
|
+
}
|
|
429
|
+
async function tryOpenBrowser(url) {
|
|
430
|
+
try {
|
|
431
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
432
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
433
|
+
spawn2(cmd, [url], { stdio: "ignore", detached: true, shell: process.platform === "win32" }).unref();
|
|
434
|
+
} catch {
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async function createApiKey(api, accessToken, plan) {
|
|
438
|
+
const { status, json } = await request("POST", `${api}/v1/products/${PRODUCT}/onboard`, {
|
|
439
|
+
bearer: accessToken,
|
|
440
|
+
body: { name: CLIENT_ID, product: PRODUCT, plan }
|
|
441
|
+
});
|
|
442
|
+
const key = json.raw_key || json.key || json.api_key;
|
|
443
|
+
if (status >= 400 || !key) {
|
|
444
|
+
const detail = json.error && typeof json.error === "object" ? json.error.message : void 0;
|
|
445
|
+
throw new LoginError(`could not create API key (HTTP ${status})${detail ? `: ${detail}` : ""}`);
|
|
446
|
+
}
|
|
447
|
+
return key;
|
|
448
|
+
}
|
|
449
|
+
async function login(opts = {}) {
|
|
450
|
+
const plan = opts.plan ?? DEFAULT_PLAN;
|
|
451
|
+
const accounts = accountsUrl();
|
|
452
|
+
const api = apiUrl();
|
|
453
|
+
let token = opts.accessToken ?? process.env.SQAI_TOKEN;
|
|
454
|
+
if (token) {
|
|
455
|
+
process.stdout.write("using supplied token (headless)\n");
|
|
456
|
+
} else {
|
|
457
|
+
process.stdout.write(`authenticating via ${accounts} \u2026
|
|
458
|
+
`);
|
|
459
|
+
token = await deviceAuthorize(accounts, opts.openBrowser ?? true);
|
|
460
|
+
}
|
|
461
|
+
cache(tokenPath(), token);
|
|
462
|
+
process.stdout.write("creating your free SQAI Developer API key \u2026\n");
|
|
463
|
+
const apiKey = await createApiKey(api, token, plan);
|
|
464
|
+
cache(apiKeyPath(), apiKey);
|
|
465
|
+
deviceId();
|
|
466
|
+
process.stdout.write(
|
|
467
|
+
"\n \u2713 sqai login complete \u2014 the local runtime will provision a device-bound offline license on first use.\n SQAI runs fully on your machine; no data leaves it.\n\n"
|
|
468
|
+
);
|
|
469
|
+
return {
|
|
470
|
+
device_id: deviceId(),
|
|
471
|
+
plan,
|
|
472
|
+
api_key_prefix: apiKey.includes("_") ? `${apiKey.split("_", 1)[0]}_\u2026` : `${apiKey.slice(0, 6)}\u2026`
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function logout() {
|
|
476
|
+
for (const path of [tokenPath(), apiKeyPath()]) {
|
|
477
|
+
try {
|
|
478
|
+
rmSync(path);
|
|
479
|
+
} catch {
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
326
484
|
// src/runtime/bundle.ts
|
|
327
485
|
import { spawn } from "child_process";
|
|
328
|
-
import { createHash as
|
|
486
|
+
import { createHash as createHash3, timingSafeEqual } from "crypto";
|
|
329
487
|
import { createReadStream, promises as fs } from "fs";
|
|
330
|
-
import { join } from "path";
|
|
488
|
+
import { join as join2 } from "path";
|
|
331
489
|
|
|
332
490
|
// src/runtime/trust.ts
|
|
333
|
-
import { createPublicKey, verify as cryptoVerify } from "crypto";
|
|
491
|
+
import { createPublicKey as createPublicKey2, verify as cryptoVerify } from "crypto";
|
|
334
492
|
var RELEASE_1_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
335
493
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7m4G1dEMi5FppLa5hu9N
|
|
336
494
|
4pUyoGELPaN5X7bO+9Qktgbv21+c8I4sQCufKouZEVNMNluKNrWcQ5dSwOuVxxS9
|
|
@@ -429,7 +587,7 @@ function keyValidNow(key, nowMs) {
|
|
|
429
587
|
}
|
|
430
588
|
function signatureVerifies(manifestBytes, signature, publicKeyPem) {
|
|
431
589
|
try {
|
|
432
|
-
const key =
|
|
590
|
+
const key = createPublicKey2(publicKeyPem);
|
|
433
591
|
if (key.asymmetricKeyType !== "rsa") {
|
|
434
592
|
return false;
|
|
435
593
|
}
|
|
@@ -613,7 +771,7 @@ async function hashTarMember(tarball, member, cap) {
|
|
|
613
771
|
const child = spawn("tar", ["-xOf", tarball, "--", member], {
|
|
614
772
|
stdio: ["ignore", "pipe", "pipe"]
|
|
615
773
|
});
|
|
616
|
-
const digest =
|
|
774
|
+
const digest = createHash3("sha256");
|
|
617
775
|
let total = 0;
|
|
618
776
|
let overflow = false;
|
|
619
777
|
child.stdout.on("data", (chunk) => {
|
|
@@ -673,7 +831,7 @@ async function extractTarball(tarball, destinationDir) {
|
|
|
673
831
|
}
|
|
674
832
|
function sha256File(path) {
|
|
675
833
|
return new Promise((resolve, reject) => {
|
|
676
|
-
const digest =
|
|
834
|
+
const digest = createHash3("sha256");
|
|
677
835
|
const stream = createReadStream(path);
|
|
678
836
|
stream.on("data", (chunk) => digest.update(chunk));
|
|
679
837
|
stream.on("error", reject);
|
|
@@ -833,7 +991,7 @@ async function verifyBundleTarball(tarball, expectations) {
|
|
|
833
991
|
return manifest;
|
|
834
992
|
}
|
|
835
993
|
async function listFilesRecursively(root, relative = "") {
|
|
836
|
-
const entries = await fs.readdir(
|
|
994
|
+
const entries = await fs.readdir(join2(root, relative), { withFileTypes: true });
|
|
837
995
|
const files = [];
|
|
838
996
|
for (const entry of entries) {
|
|
839
997
|
const relPath = relative === "" ? entry.name : `${relative}/${entry.name}`;
|
|
@@ -857,8 +1015,8 @@ async function listFilesRecursively(root, relative = "") {
|
|
|
857
1015
|
return files;
|
|
858
1016
|
}
|
|
859
1017
|
async function verifyInstalledBundleDir(installDir, expectations) {
|
|
860
|
-
const manifestPath =
|
|
861
|
-
const signaturePath =
|
|
1018
|
+
const manifestPath = join2(installDir, MANIFEST_NAME);
|
|
1019
|
+
const signaturePath = join2(installDir, MANIFEST_SIG_NAME);
|
|
862
1020
|
let manifestBytes;
|
|
863
1021
|
let signature;
|
|
864
1022
|
try {
|
|
@@ -898,7 +1056,7 @@ async function verifyInstalledBundleDir(installDir, expectations) {
|
|
|
898
1056
|
`installed file not listed in signed manifest: '${relPath}'`
|
|
899
1057
|
);
|
|
900
1058
|
}
|
|
901
|
-
const absolute =
|
|
1059
|
+
const absolute = join2(installDir, relPath);
|
|
902
1060
|
const stat = await fs.lstat(absolute);
|
|
903
1061
|
if (stat.nlink > 1) {
|
|
904
1062
|
throw new BundleVerificationError(
|
|
@@ -963,20 +1121,20 @@ function currentPlatformKey() {
|
|
|
963
1121
|
function cacheDirFor(brand) {
|
|
964
1122
|
const os = osPlatform();
|
|
965
1123
|
if (os === "darwin") {
|
|
966
|
-
return
|
|
1124
|
+
return join3(homedir2(), "Library", "Caches", brand, "runtime");
|
|
967
1125
|
}
|
|
968
1126
|
if (os === "win32") {
|
|
969
|
-
const localAppData = process.env.LOCALAPPDATA ??
|
|
970
|
-
return
|
|
1127
|
+
const localAppData = process.env.LOCALAPPDATA ?? join3(homedir2(), "AppData", "Local");
|
|
1128
|
+
return join3(localAppData, brand, "runtime");
|
|
971
1129
|
}
|
|
972
|
-
const xdgCache = process.env.XDG_CACHE_HOME ??
|
|
973
|
-
return
|
|
1130
|
+
const xdgCache = process.env.XDG_CACHE_HOME ?? join3(homedir2(), ".cache");
|
|
1131
|
+
return join3(xdgCache, brand.toLowerCase(), "runtime");
|
|
974
1132
|
}
|
|
975
1133
|
function runtimeCacheDir() {
|
|
976
1134
|
const current = cacheDirFor("SQAI");
|
|
977
|
-
if (!
|
|
1135
|
+
if (!existsSync2(current)) {
|
|
978
1136
|
const legacy = cacheDirFor("Algenta");
|
|
979
|
-
if (legacy !== current &&
|
|
1137
|
+
if (legacy !== current && existsSync2(legacy)) {
|
|
980
1138
|
return legacy;
|
|
981
1139
|
}
|
|
982
1140
|
}
|
|
@@ -1125,7 +1283,7 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1125
1283
|
localCacheKey(modules, platformKey) {
|
|
1126
1284
|
const canonical = modules.join(",");
|
|
1127
1285
|
const version = this.options.contract.runtime_bundle.version;
|
|
1128
|
-
return
|
|
1286
|
+
return createHash4("sha256").update(`${canonical}|${platformKey}|${version}`).digest("hex").slice(0, 20);
|
|
1129
1287
|
}
|
|
1130
1288
|
/**
|
|
1131
1289
|
* Deterministic per-filter daemon endpoint so a filtered runtime never collides
|
|
@@ -1134,7 +1292,7 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1134
1292
|
filterEndpoint(cacheKey) {
|
|
1135
1293
|
const seed = parseInt(cacheKey.slice(0, 6), 16) || 0;
|
|
1136
1294
|
const port = 50100 + seed % 800;
|
|
1137
|
-
return { sock:
|
|
1295
|
+
return { sock: join3(this.cacheDir(), `${cacheKey}.sock`), tcp: `127.0.0.1:${port}` };
|
|
1138
1296
|
}
|
|
1139
1297
|
static DAEMON_ENDPOINT_ENV = [
|
|
1140
1298
|
"ALGENTA_DAEMON_SOCK",
|
|
@@ -1151,7 +1309,7 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1151
1309
|
process.env.ALGENTA_DAEMON_SOCK = endpoint.sock;
|
|
1152
1310
|
process.env.ALGENTA_DAEMON_TCP = endpoint.tcp;
|
|
1153
1311
|
process.env.ALGENTA_MOJO_SOCK = `${endpoint.sock}.worker`;
|
|
1154
|
-
process.env.ALGENTA_RUNTIME_DIR =
|
|
1312
|
+
process.env.ALGENTA_RUNTIME_DIR = join3(installDir, ".runtime");
|
|
1155
1313
|
return saved;
|
|
1156
1314
|
}
|
|
1157
1315
|
restoreEnv(saved) {
|
|
@@ -1266,9 +1424,9 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1266
1424
|
}
|
|
1267
1425
|
/** Verify an existing install if present; returns null when absent/invalid. */
|
|
1268
1426
|
async verifiedInstallOrNull(platformKey, version, removeOnFailure, installKey = version) {
|
|
1269
|
-
const installDir =
|
|
1427
|
+
const installDir = join3(this.cacheDir(), installKey);
|
|
1270
1428
|
try {
|
|
1271
|
-
await fs2.access(
|
|
1429
|
+
await fs2.access(join3(installDir, MANIFEST_NAME));
|
|
1272
1430
|
} catch {
|
|
1273
1431
|
return null;
|
|
1274
1432
|
}
|
|
@@ -1325,7 +1483,7 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1325
1483
|
if (existing) {
|
|
1326
1484
|
return existing;
|
|
1327
1485
|
}
|
|
1328
|
-
const lockPath =
|
|
1486
|
+
const lockPath = join3(cacheDir, `${installKey}.lock`);
|
|
1329
1487
|
const waitTimeoutMs = this.options.lockWaitTimeoutMs ?? LOCK_WAIT_TIMEOUT_MS;
|
|
1330
1488
|
const pollIntervalMs = this.options.lockPollIntervalMs ?? LOCK_POLL_INTERVAL_MS;
|
|
1331
1489
|
const deadline = Date.now() + waitTimeoutMs;
|
|
@@ -1371,9 +1529,9 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1371
1529
|
async downloadVerifyExtract(platformKey, version, platformBundle, installKey = version) {
|
|
1372
1530
|
const cacheDir = this.cacheDir();
|
|
1373
1531
|
const token = randomBytes2(6).toString("hex");
|
|
1374
|
-
const tarballPath =
|
|
1375
|
-
const extractDir =
|
|
1376
|
-
const installDir =
|
|
1532
|
+
const tarballPath = join3(cacheDir, `tmp-${token}.tgz`);
|
|
1533
|
+
const extractDir = join3(cacheDir, `tmp-${token}`);
|
|
1534
|
+
const installDir = join3(cacheDir, installKey);
|
|
1377
1535
|
const url = resolveBundleUrl(platformBundle.url, version, platformKey);
|
|
1378
1536
|
try {
|
|
1379
1537
|
const download = this.options.downloadBundle ?? defaultDownloadBundle;
|
|
@@ -1448,10 +1606,16 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1448
1606
|
throw this.wrapVerificationError(error, platformKey, version);
|
|
1449
1607
|
}
|
|
1450
1608
|
const daemonCommand = [
|
|
1451
|
-
|
|
1609
|
+
join3(installed.installDir, ...entryRelative.split("/")),
|
|
1452
1610
|
"start",
|
|
1453
1611
|
"--foreground"
|
|
1454
1612
|
];
|
|
1613
|
+
if (!process.env.ALGENTA_API_KEY && !process.env.DE_API_KEY) {
|
|
1614
|
+
const key = await readCachedApiKey();
|
|
1615
|
+
if (key) {
|
|
1616
|
+
process.env.ALGENTA_API_KEY = key;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1455
1619
|
const managed = this.newRuntime(
|
|
1456
1620
|
endpoint ? { autoStart: true, daemonCommand, tcpAddress: endpoint.tcp } : { autoStart: true, daemonCommand }
|
|
1457
1621
|
);
|
|
@@ -1486,7 +1650,7 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1486
1650
|
}
|
|
1487
1651
|
try {
|
|
1488
1652
|
await fs2.writeFile(
|
|
1489
|
-
|
|
1653
|
+
join3(this.cacheDir(), "state.json"),
|
|
1490
1654
|
JSON.stringify(
|
|
1491
1655
|
{ pid: null, version, started_at: (/* @__PURE__ */ new Date()).toISOString() },
|
|
1492
1656
|
null,
|
|
@@ -2051,6 +2215,7 @@ function mapSource(registration) {
|
|
|
2051
2215
|
}
|
|
2052
2216
|
export {
|
|
2053
2217
|
ContractIndex,
|
|
2218
|
+
LoginError,
|
|
2054
2219
|
ResultStore,
|
|
2055
2220
|
RuntimeProvisioner,
|
|
2056
2221
|
SQAI,
|
|
@@ -2061,8 +2226,12 @@ export {
|
|
|
2061
2226
|
currentPlatformKey,
|
|
2062
2227
|
invocationHash,
|
|
2063
2228
|
isReadOnlyEligible,
|
|
2229
|
+
login,
|
|
2230
|
+
logout,
|
|
2064
2231
|
lookupCapability,
|
|
2232
|
+
readCachedApiKey,
|
|
2065
2233
|
runtimeCacheDir,
|
|
2234
|
+
sqaiHome,
|
|
2066
2235
|
validateComputation
|
|
2067
2236
|
};
|
|
2068
2237
|
//# sourceMappingURL=index.js.map
|