@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/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 join2 } from "path";
320
- import { createWriteStream, existsSync, promises as fs2 } from "fs";
321
- import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
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 createHash2, timingSafeEqual } from "crypto";
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 = createPublicKey(publicKeyPem);
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 = createHash2("sha256");
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 = createHash2("sha256");
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(join(root, relative), { withFileTypes: true });
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 = join(installDir, MANIFEST_NAME);
861
- const signaturePath = join(installDir, MANIFEST_SIG_NAME);
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 = join(installDir, relPath);
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 join2(homedir(), "Library", "Caches", brand, "runtime");
1124
+ return join3(homedir2(), "Library", "Caches", brand, "runtime");
967
1125
  }
968
1126
  if (os === "win32") {
969
- const localAppData = process.env.LOCALAPPDATA ?? join2(homedir(), "AppData", "Local");
970
- return join2(localAppData, brand, "runtime");
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 ?? join2(homedir(), ".cache");
973
- return join2(xdgCache, brand.toLowerCase(), "runtime");
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 (!existsSync(current)) {
1135
+ if (!existsSync2(current)) {
978
1136
  const legacy = cacheDirFor("Algenta");
979
- if (legacy !== current && existsSync(legacy)) {
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 createHash3("sha256").update(`${canonical}|${platformKey}|${version}`).digest("hex").slice(0, 20);
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: join2(this.cacheDir(), `${cacheKey}.sock`), tcp: `127.0.0.1:${port}` };
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 = join2(installDir, ".runtime");
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 = join2(this.cacheDir(), installKey);
1427
+ const installDir = join3(this.cacheDir(), installKey);
1270
1428
  try {
1271
- await fs2.access(join2(installDir, MANIFEST_NAME));
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 = join2(cacheDir, `${installKey}.lock`);
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 = join2(cacheDir, `tmp-${token}.tgz`);
1375
- const extractDir = join2(cacheDir, `tmp-${token}`);
1376
- const installDir = join2(cacheDir, installKey);
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
- join2(installed.installDir, ...entryRelative.split("/")),
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
- join2(this.cacheDir(), "state.json"),
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