@astrale-os/cli 1.0.0-beta.28 → 1.0.0-beta.29
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/README.md +33 -0
- package/dist/astrale.js +1629 -1088
- package/dist/public/connect-core.js +86 -17
- package/dist/public/keys/index.js +69 -2
- package/dist/public/paths/index.js +67 -0
- package/dist/types/connection/session.d.ts +2 -2
- package/dist/types/lib/config.d.ts +6 -0
- package/dist/types/state/exchange-credentials.d.ts +6 -2
- package/dist/types/state/files.d.ts +2 -0
- package/dist/types/state/index.d.ts +3 -2
- package/dist/types/state/paths.d.ts +2 -0
- package/dist/types/state/session-routes.d.ts +10 -0
- package/package.json +2 -2
- package/src/commands/auth/logout.ts +2 -1
- package/src/commands/browser.ts +29 -0
- package/src/connection/.spec/architecture.md +9 -1
- package/src/connection/__tests__/auth.test.ts +1 -0
- package/src/connection/__tests__/credential.test.ts +1 -0
- package/src/connection/__tests__/exchange.test.ts +34 -10
- package/src/connection/__tests__/session.test.ts +5 -1
- package/src/connection/__tests__/target.test.ts +1 -0
- package/src/connection/exchange.ts +63 -38
- package/src/connection/session.ts +8 -1
- package/src/identity/__tests__/fixtures/registry-journey.ts +13 -1
- package/src/identity/__tests__/registry.test.ts +4 -0
- package/src/identity/registry.ts +2 -0
- package/src/lib/__tests__/browser-retention.test.ts +212 -0
- package/src/lib/__tests__/config.test.ts +27 -0
- package/src/lib/browser-retention.ts +210 -0
- package/src/lib/config.ts +12 -1
- package/src/state/.spec/api.d.ts +21 -2
- package/src/state/.spec/architecture.md +18 -4
- package/src/state/.spec/layout.ts +1 -0
- package/src/state/__tests__/exchange-credentials.test.ts +88 -42
- package/src/state/__tests__/files.test.ts +24 -1
- package/src/state/__tests__/fixtures/session-route-process.ts +93 -0
- package/src/state/__tests__/paths.test.ts +1 -0
- package/src/state/__tests__/session-routes.test.ts +138 -0
- package/src/state/exchange-credentials.ts +22 -9
- package/src/state/files.ts +41 -0
- package/src/state/index.ts +3 -1
- package/src/state/paths.ts +3 -0
- package/src/state/session-routes.ts +34 -0
- package/src/telemetry/__tests__/analyze-log.test.ts +38 -0
- package/src/telemetry/__tests__/retention.test.ts +88 -1
- package/src/telemetry/analyze.ts +24 -2
- package/src/telemetry/retention.ts +53 -6
- package/src/telemetry/store.ts +5 -0
- package/studio/package.json +1 -1
- package/viewer/dist/main.js +28 -28
|
@@ -8092,7 +8092,7 @@ async function generateKeyPair(alg, options) {
|
|
|
8092
8092
|
// src/keys/pair.ts
|
|
8093
8093
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8094
8094
|
import { access, mkdir as mkdir2, readFile as readFile3, unlink as unlink2 } from "node:fs/promises";
|
|
8095
|
-
import { dirname as
|
|
8095
|
+
import { dirname as dirname3, join as join2, resolve } from "node:path";
|
|
8096
8096
|
|
|
8097
8097
|
// src/errors.ts
|
|
8098
8098
|
class AstraleError extends Error {
|
|
@@ -8126,6 +8126,15 @@ class IdentityKeyMissingError extends AstraleError {
|
|
|
8126
8126
|
|
|
8127
8127
|
// src/state/files.ts
|
|
8128
8128
|
import { randomUUID } from "node:crypto";
|
|
8129
|
+
import {
|
|
8130
|
+
closeSync,
|
|
8131
|
+
fsyncSync,
|
|
8132
|
+
mkdirSync,
|
|
8133
|
+
openSync,
|
|
8134
|
+
renameSync,
|
|
8135
|
+
unlinkSync,
|
|
8136
|
+
writeFileSync
|
|
8137
|
+
} from "node:fs";
|
|
8129
8138
|
import { mkdir, open, readFile, rename, stat, unlink } from "node:fs/promises";
|
|
8130
8139
|
import { dirname } from "node:path";
|
|
8131
8140
|
async function atomicWrite(path, data) {
|
|
@@ -8156,6 +8165,33 @@ async function atomicWrite(path, data) {
|
|
|
8156
8165
|
throw error;
|
|
8157
8166
|
}
|
|
8158
8167
|
}
|
|
8168
|
+
function atomicWriteSync(path, data) {
|
|
8169
|
+
const directory = dirname(path);
|
|
8170
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
8171
|
+
mkdirSync(directory, { recursive: true });
|
|
8172
|
+
let descriptor;
|
|
8173
|
+
try {
|
|
8174
|
+
descriptor = openSync(temporary, "wx", 384);
|
|
8175
|
+
writeFileSync(descriptor, data);
|
|
8176
|
+
fsyncSync(descriptor);
|
|
8177
|
+
closeSync(descriptor);
|
|
8178
|
+
descriptor = undefined;
|
|
8179
|
+
renameSync(temporary, path);
|
|
8180
|
+
const directoryDescriptor = openSync(directory, "r");
|
|
8181
|
+
try {
|
|
8182
|
+
fsyncSync(directoryDescriptor);
|
|
8183
|
+
} finally {
|
|
8184
|
+
closeSync(directoryDescriptor);
|
|
8185
|
+
}
|
|
8186
|
+
} catch (error) {
|
|
8187
|
+
if (descriptor !== undefined)
|
|
8188
|
+
closeSync(descriptor);
|
|
8189
|
+
try {
|
|
8190
|
+
unlinkSync(temporary);
|
|
8191
|
+
} catch {}
|
|
8192
|
+
throw error;
|
|
8193
|
+
}
|
|
8194
|
+
}
|
|
8159
8195
|
async function withFileLock(lockPath, transition, options = {}) {
|
|
8160
8196
|
const pollIntervalMs = options.pollIntervalMs ?? 100;
|
|
8161
8197
|
const staleAfterMs = options.staleAfterMs ?? 30000;
|
|
@@ -8242,14 +8278,14 @@ function isPidAlive(pid) {
|
|
|
8242
8278
|
function sleep(milliseconds) {
|
|
8243
8279
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
8244
8280
|
}
|
|
8245
|
-
// node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.
|
|
8281
|
+
// node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.14/node_modules/@astrale-os/kernel-dsl/dist/value/limits.js
|
|
8246
8282
|
var defaultLimits = Object.freeze({
|
|
8247
8283
|
maxBytes: 16 * 1024 * 1024,
|
|
8248
8284
|
maxDepth: 64,
|
|
8249
8285
|
maxMembers: 1e5,
|
|
8250
8286
|
maxStringBytes: 1024 * 1024
|
|
8251
8287
|
});
|
|
8252
|
-
// node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.
|
|
8288
|
+
// node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.14/node_modules/@astrale-os/kernel-dsl/dist/value/canonical.js
|
|
8253
8289
|
var encoder2 = new TextEncoder;
|
|
8254
8290
|
// node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
8255
8291
|
var CharacterCodes;
|
|
@@ -8429,7 +8465,7 @@ var ParseErrorCode;
|
|
|
8429
8465
|
ParseErrorCode2[ParseErrorCode2["InvalidCharacter"] = 16] = "InvalidCharacter";
|
|
8430
8466
|
})(ParseErrorCode || (ParseErrorCode = {}));
|
|
8431
8467
|
|
|
8432
|
-
// node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.
|
|
8468
|
+
// node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.14/node_modules/@astrale-os/kernel-dsl/dist/value/decode.js
|
|
8433
8469
|
var decoder2 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
8434
8470
|
// node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
8435
8471
|
var exports_external = {};
|
|
@@ -22730,6 +22766,7 @@ function createPaths(home, environment) {
|
|
|
22730
22766
|
idps: join(idpsDir, "index.json"),
|
|
22731
22767
|
idpSessionsDir,
|
|
22732
22768
|
exchangeCredentials: join(base, "exchange", "credentials.json"),
|
|
22769
|
+
sessionRoutes: join(base, "session", "routes.json"),
|
|
22733
22770
|
idpDir: (name) => join(idpsDir, name),
|
|
22734
22771
|
idpSession: (identityName) => join(idpSessionsDir, `${identityName}.json`)
|
|
22735
22772
|
});
|
|
@@ -22745,6 +22782,7 @@ var INSTANCES_PATH = paths.instances;
|
|
|
22745
22782
|
var IDPS_PATH = paths.idps;
|
|
22746
22783
|
var IDP_SESSIONS_DIR = paths.idpSessionsDir;
|
|
22747
22784
|
var EXCHANGE_CREDENTIALS_PATH = paths.exchangeCredentials;
|
|
22785
|
+
var SESSION_ROUTES_PATH = paths.sessionRoutes;
|
|
22748
22786
|
// src/state/identities.ts
|
|
22749
22787
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
22750
22788
|
var IDENTITY_STORE_VERSION = 1;
|
|
@@ -22866,6 +22904,35 @@ async function preserveLegacyBackup(path, legacyBytes) {
|
|
|
22866
22904
|
function invalidState(path, cause) {
|
|
22867
22905
|
return new IdentityStateError("IDENTITY_STATE_INVALID", path, `Identity state at ${path} is malformed`, cause);
|
|
22868
22906
|
}
|
|
22907
|
+
// src/state/session-routes.ts
|
|
22908
|
+
import { chmodSync, mkdirSync as mkdirSync2, readFileSync, unlinkSync as unlinkSync2 } from "node:fs";
|
|
22909
|
+
import { dirname as dirname2 } from "node:path";
|
|
22910
|
+
class FileSessionRouteStore {
|
|
22911
|
+
path;
|
|
22912
|
+
constructor(path = SESSION_ROUTES_PATH) {
|
|
22913
|
+
this.path = path;
|
|
22914
|
+
}
|
|
22915
|
+
read() {
|
|
22916
|
+
return JSON.parse(readFileSync(this.path, "utf8"));
|
|
22917
|
+
}
|
|
22918
|
+
write(artifact) {
|
|
22919
|
+
const directory = dirname2(this.path);
|
|
22920
|
+
mkdirSync2(directory, { recursive: true, mode: 448 });
|
|
22921
|
+
chmodSync(directory, 448);
|
|
22922
|
+
atomicWriteSync(this.path, `${JSON.stringify(artifact)}
|
|
22923
|
+
`);
|
|
22924
|
+
chmodSync(this.path, 384);
|
|
22925
|
+
}
|
|
22926
|
+
clear() {
|
|
22927
|
+
try {
|
|
22928
|
+
unlinkSync2(this.path);
|
|
22929
|
+
} catch (error51) {
|
|
22930
|
+
if (error51.code !== "ENOENT")
|
|
22931
|
+
throw error51;
|
|
22932
|
+
}
|
|
22933
|
+
}
|
|
22934
|
+
}
|
|
22935
|
+
var SESSION_ROUTE_STORE = Object.freeze(new FileSessionRouteStore);
|
|
22869
22936
|
// src/keys/algorithm.ts
|
|
22870
22937
|
function inferAlg(privateJwk, keyPath) {
|
|
22871
22938
|
const explicit = privateJwk.alg;
|
|
@@ -22903,7 +22970,7 @@ function keypairPaths(subject, keysDir = KEYS_DIR) {
|
|
|
22903
22970
|
}
|
|
22904
22971
|
function confinedKeyPath(keysDir, filename, subject) {
|
|
22905
22972
|
const path = join2(keysDir, filename);
|
|
22906
|
-
if (
|
|
22973
|
+
if (dirname3(resolve(path)) !== resolve(keysDir))
|
|
22907
22974
|
throw invalidKeySubject(subject);
|
|
22908
22975
|
return path;
|
|
22909
22976
|
}
|
|
@@ -23106,7 +23173,7 @@ async function signAs(subject, keysDir = KEYS_DIR, opts) {
|
|
|
23106
23173
|
}
|
|
23107
23174
|
// src/lib/idp.ts
|
|
23108
23175
|
import { mkdir as mkdir3, readFile as readFile4, readdir, unlink as unlink3 } from "node:fs/promises";
|
|
23109
|
-
import { dirname as
|
|
23176
|
+
import { dirname as dirname4, join as join3 } from "node:path";
|
|
23110
23177
|
|
|
23111
23178
|
// node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
23112
23179
|
var ANSI_BACKGROUND_OFFSET = 10;
|
|
@@ -23659,7 +23726,7 @@ var log = {
|
|
|
23659
23726
|
dim: (msg) => console.log(source_default.dim(msg))
|
|
23660
23727
|
};
|
|
23661
23728
|
var IS_CI = !!(process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.NO_SPINNER);
|
|
23662
|
-
// node_modules/.pnpm/@astrale-os+kernel-core@0.9.0-beta.
|
|
23729
|
+
// node_modules/.pnpm/@astrale-os+kernel-core@0.9.0-beta.17_zod@4.4.3/node_modules/@astrale-os/kernel-core/dist/dns-label.js
|
|
23663
23730
|
var DNS_LABEL_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
23664
23731
|
function isDnsLabel(value) {
|
|
23665
23732
|
return DNS_LABEL_RE.test(value);
|
|
@@ -23830,7 +23897,7 @@ async function readIdpStore() {
|
|
|
23830
23897
|
}
|
|
23831
23898
|
}
|
|
23832
23899
|
async function writeIdpStore(store) {
|
|
23833
|
-
await mkdir3(
|
|
23900
|
+
await mkdir3(dirname4(IDPS_PATH), { recursive: true });
|
|
23834
23901
|
await atomicWrite(IDPS_PATH, JSON.stringify(store, null, 2) + `
|
|
23835
23902
|
`);
|
|
23836
23903
|
}
|
|
@@ -24040,7 +24107,7 @@ function identityNameFromClaims(claims, fallback) {
|
|
|
24040
24107
|
}
|
|
24041
24108
|
async function saveIdpSession(session) {
|
|
24042
24109
|
const path = idpSessionPath(session.identity);
|
|
24043
|
-
await mkdir3(
|
|
24110
|
+
await mkdir3(dirname4(path), { recursive: true });
|
|
24044
24111
|
await atomicWrite(path, JSON.stringify(session, null, 2) + `
|
|
24045
24112
|
`);
|
|
24046
24113
|
}
|
|
@@ -24300,7 +24367,7 @@ async function upsertIdpIdentity(name, options) {
|
|
|
24300
24367
|
}
|
|
24301
24368
|
// src/lib/instance.ts
|
|
24302
24369
|
import { readFile as readFile5, writeFile, mkdir as mkdir4 } from "node:fs/promises";
|
|
24303
|
-
import { dirname as
|
|
24370
|
+
import { dirname as dirname5 } from "node:path";
|
|
24304
24371
|
var InstanceKindSchema = exports_external.enum(["bookmark"]);
|
|
24305
24372
|
var InstanceEntrySchema = exports_external.object({
|
|
24306
24373
|
url: exports_external.string().optional(),
|
|
@@ -24385,7 +24452,7 @@ async function readInstances(_config, opts = {}) {
|
|
|
24385
24452
|
return store;
|
|
24386
24453
|
}
|
|
24387
24454
|
async function writeInstances(store) {
|
|
24388
|
-
await mkdir4(
|
|
24455
|
+
await mkdir4(dirname5(INSTANCES_PATH), { recursive: true });
|
|
24389
24456
|
await writeFile(INSTANCES_PATH, JSON.stringify(store, null, 2) + `
|
|
24390
24457
|
`);
|
|
24391
24458
|
instancesMemo = store;
|
|
@@ -24487,7 +24554,7 @@ import {
|
|
|
24487
24554
|
rm,
|
|
24488
24555
|
writeFile as writeFile2
|
|
24489
24556
|
} from "node:fs/promises";
|
|
24490
|
-
import { dirname as
|
|
24557
|
+
import { dirname as dirname6, join as join4 } from "node:path";
|
|
24491
24558
|
var DEFAULT_REPO = "astrale-os/cli";
|
|
24492
24559
|
var DEFAULT_UPDATE_CHANNEL = "beta";
|
|
24493
24560
|
var InstallMetadataSchema = exports_external.object({
|
|
@@ -24519,7 +24586,7 @@ function isMissingFile(error51) {
|
|
|
24519
24586
|
async function writeInstallMetadata(meta3, path = INSTALL_PATH, filesystem = { mkdir: mkdir5, rename: rename2, rm, writeFile: writeFile2 }) {
|
|
24520
24587
|
const staged = `${path}.next`;
|
|
24521
24588
|
const previous = `${path}.previous`;
|
|
24522
|
-
await filesystem.mkdir(
|
|
24589
|
+
await filesystem.mkdir(dirname6(path), { recursive: true });
|
|
24523
24590
|
await filesystem.rm(staged, { force: true });
|
|
24524
24591
|
await filesystem.rm(previous, { force: true });
|
|
24525
24592
|
await filesystem.writeFile(staged, JSON.stringify(meta3, null, 2) + `
|
|
@@ -24550,7 +24617,7 @@ async function writeInstallMetadata(meta3, path = INSTALL_PATH, filesystem = { m
|
|
|
24550
24617
|
var defaultCohortFilesystem = { chmod, copyFile, mkdir: mkdir5, rename: rename2, rm };
|
|
24551
24618
|
async function replaceStandaloneCohort(installedBinary, nextBinary, nextViewerDist, filesystem = {}) {
|
|
24552
24619
|
const fs = { ...defaultCohortFilesystem, ...filesystem };
|
|
24553
|
-
const binDirectory =
|
|
24620
|
+
const binDirectory = dirname6(installedBinary);
|
|
24554
24621
|
const previousBinary = `${installedBinary}.previous`;
|
|
24555
24622
|
const stagedBinary = `${installedBinary}.next`;
|
|
24556
24623
|
const viewer = join4(binDirectory, "viewer");
|
|
@@ -24925,10 +24992,12 @@ function isManagedInstanceNotFound(error51) {
|
|
|
24925
24992
|
}
|
|
24926
24993
|
// src/lib/config.ts
|
|
24927
24994
|
import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir6 } from "node:fs/promises";
|
|
24995
|
+
var bound = exports_external.number().positive().finite().optional().catch(undefined);
|
|
24928
24996
|
var AstraleConfigSchema = exports_external.object({
|
|
24929
24997
|
issuer: exports_external.string().url().default("https://unregistered.invalid"),
|
|
24930
24998
|
admin: AdminTargetConfigSchema.default(DEFAULT_ADMIN_TARGET_CONFIG),
|
|
24931
|
-
telemetry: exports_external.object({ enabled: exports_external.boolean().default(true) }).default({ enabled: true })
|
|
24999
|
+
telemetry: exports_external.object({ enabled: exports_external.boolean().default(true), maxAgeDays: bound, maxBytes: bound }).default({ enabled: true }),
|
|
25000
|
+
browser: exports_external.object({ maxCacheBytes: bound, maxProfileAgeDays: bound }).default({})
|
|
24932
25001
|
});
|
|
24933
25002
|
var DEFAULT_CONFIG = AstraleConfigSchema.parse({});
|
|
24934
25003
|
async function readConfig() {
|
|
@@ -25263,11 +25332,11 @@ async function obtainToken(idp, opts, scope) {
|
|
|
25263
25332
|
}
|
|
25264
25333
|
// src/lib/ca-fetch.ts
|
|
25265
25334
|
import { Buffer } from "node:buffer";
|
|
25266
|
-
import { readFileSync } from "node:fs";
|
|
25335
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
25267
25336
|
import { request as httpsRequest } from "node:https";
|
|
25268
25337
|
import { rootCertificates } from "node:tls";
|
|
25269
25338
|
function fetchWithCaFile(caFile, fallback = globalThis.fetch) {
|
|
25270
|
-
const ca =
|
|
25339
|
+
const ca = readFileSync2(caFile);
|
|
25271
25340
|
const fallbackFetch = fallback.bind(globalThis);
|
|
25272
25341
|
return async (input, init) => {
|
|
25273
25342
|
const url2 = requestUrl(input);
|
|
@@ -1151,7 +1151,7 @@ async function generateKeyPair(alg, options) {
|
|
|
1151
1151
|
// src/keys/pair.ts
|
|
1152
1152
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1153
1153
|
import { access, mkdir as mkdir2, readFile as readFile3, unlink as unlink2 } from "node:fs/promises";
|
|
1154
|
-
import { dirname as
|
|
1154
|
+
import { dirname as dirname3, join as join2, resolve } from "node:path";
|
|
1155
1155
|
|
|
1156
1156
|
// src/errors.ts
|
|
1157
1157
|
class AstraleError extends Error {
|
|
@@ -1185,6 +1185,15 @@ class IdentityKeyMissingError extends AstraleError {
|
|
|
1185
1185
|
|
|
1186
1186
|
// src/state/files.ts
|
|
1187
1187
|
import { randomUUID } from "node:crypto";
|
|
1188
|
+
import {
|
|
1189
|
+
closeSync,
|
|
1190
|
+
fsyncSync,
|
|
1191
|
+
mkdirSync,
|
|
1192
|
+
openSync,
|
|
1193
|
+
renameSync,
|
|
1194
|
+
unlinkSync,
|
|
1195
|
+
writeFileSync
|
|
1196
|
+
} from "node:fs";
|
|
1188
1197
|
import { mkdir, open, readFile, rename, stat, unlink } from "node:fs/promises";
|
|
1189
1198
|
import { dirname } from "node:path";
|
|
1190
1199
|
async function atomicWrite(path, data) {
|
|
@@ -1215,6 +1224,33 @@ async function atomicWrite(path, data) {
|
|
|
1215
1224
|
throw error;
|
|
1216
1225
|
}
|
|
1217
1226
|
}
|
|
1227
|
+
function atomicWriteSync(path, data) {
|
|
1228
|
+
const directory = dirname(path);
|
|
1229
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
1230
|
+
mkdirSync(directory, { recursive: true });
|
|
1231
|
+
let descriptor;
|
|
1232
|
+
try {
|
|
1233
|
+
descriptor = openSync(temporary, "wx", 384);
|
|
1234
|
+
writeFileSync(descriptor, data);
|
|
1235
|
+
fsyncSync(descriptor);
|
|
1236
|
+
closeSync(descriptor);
|
|
1237
|
+
descriptor = undefined;
|
|
1238
|
+
renameSync(temporary, path);
|
|
1239
|
+
const directoryDescriptor = openSync(directory, "r");
|
|
1240
|
+
try {
|
|
1241
|
+
fsyncSync(directoryDescriptor);
|
|
1242
|
+
} finally {
|
|
1243
|
+
closeSync(directoryDescriptor);
|
|
1244
|
+
}
|
|
1245
|
+
} catch (error) {
|
|
1246
|
+
if (descriptor !== undefined)
|
|
1247
|
+
closeSync(descriptor);
|
|
1248
|
+
try {
|
|
1249
|
+
unlinkSync(temporary);
|
|
1250
|
+
} catch {}
|
|
1251
|
+
throw error;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1218
1254
|
async function withFileLock(lockPath, transition, options = {}) {
|
|
1219
1255
|
const pollIntervalMs = options.pollIntervalMs ?? 100;
|
|
1220
1256
|
const staleAfterMs = options.staleAfterMs ?? 30000;
|
|
@@ -15600,6 +15636,7 @@ function createPaths(home, environment) {
|
|
|
15600
15636
|
idps: join(idpsDir, "index.json"),
|
|
15601
15637
|
idpSessionsDir,
|
|
15602
15638
|
exchangeCredentials: join(base, "exchange", "credentials.json"),
|
|
15639
|
+
sessionRoutes: join(base, "session", "routes.json"),
|
|
15603
15640
|
idpDir: (name) => join(idpsDir, name),
|
|
15604
15641
|
idpSession: (identityName) => join(idpSessionsDir, `${identityName}.json`)
|
|
15605
15642
|
});
|
|
@@ -15615,6 +15652,7 @@ var INSTANCES_PATH = paths.instances;
|
|
|
15615
15652
|
var IDPS_PATH = paths.idps;
|
|
15616
15653
|
var IDP_SESSIONS_DIR = paths.idpSessionsDir;
|
|
15617
15654
|
var EXCHANGE_CREDENTIALS_PATH = paths.exchangeCredentials;
|
|
15655
|
+
var SESSION_ROUTES_PATH = paths.sessionRoutes;
|
|
15618
15656
|
// src/state/identities.ts
|
|
15619
15657
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
15620
15658
|
var IDENTITY_STORE_VERSION = 1;
|
|
@@ -15736,6 +15774,35 @@ async function preserveLegacyBackup(path, legacyBytes) {
|
|
|
15736
15774
|
function invalidState(path, cause) {
|
|
15737
15775
|
return new IdentityStateError("IDENTITY_STATE_INVALID", path, `Identity state at ${path} is malformed`, cause);
|
|
15738
15776
|
}
|
|
15777
|
+
// src/state/session-routes.ts
|
|
15778
|
+
import { chmodSync, mkdirSync as mkdirSync2, readFileSync, unlinkSync as unlinkSync2 } from "node:fs";
|
|
15779
|
+
import { dirname as dirname2 } from "node:path";
|
|
15780
|
+
class FileSessionRouteStore {
|
|
15781
|
+
path;
|
|
15782
|
+
constructor(path = SESSION_ROUTES_PATH) {
|
|
15783
|
+
this.path = path;
|
|
15784
|
+
}
|
|
15785
|
+
read() {
|
|
15786
|
+
return JSON.parse(readFileSync(this.path, "utf8"));
|
|
15787
|
+
}
|
|
15788
|
+
write(artifact) {
|
|
15789
|
+
const directory = dirname2(this.path);
|
|
15790
|
+
mkdirSync2(directory, { recursive: true, mode: 448 });
|
|
15791
|
+
chmodSync(directory, 448);
|
|
15792
|
+
atomicWriteSync(this.path, `${JSON.stringify(artifact)}
|
|
15793
|
+
`);
|
|
15794
|
+
chmodSync(this.path, 384);
|
|
15795
|
+
}
|
|
15796
|
+
clear() {
|
|
15797
|
+
try {
|
|
15798
|
+
unlinkSync2(this.path);
|
|
15799
|
+
} catch (error51) {
|
|
15800
|
+
if (error51.code !== "ENOENT")
|
|
15801
|
+
throw error51;
|
|
15802
|
+
}
|
|
15803
|
+
}
|
|
15804
|
+
}
|
|
15805
|
+
var SESSION_ROUTE_STORE = Object.freeze(new FileSessionRouteStore);
|
|
15739
15806
|
// src/keys/algorithm.ts
|
|
15740
15807
|
function inferAlg(privateJwk, keyPath) {
|
|
15741
15808
|
const explicit = privateJwk.alg;
|
|
@@ -15773,7 +15840,7 @@ function keypairPaths(subject, keysDir = KEYS_DIR) {
|
|
|
15773
15840
|
}
|
|
15774
15841
|
function confinedKeyPath(keysDir, filename, subject) {
|
|
15775
15842
|
const path = join2(keysDir, filename);
|
|
15776
|
-
if (
|
|
15843
|
+
if (dirname3(resolve(path)) !== resolve(keysDir))
|
|
15777
15844
|
throw invalidKeySubject(subject);
|
|
15778
15845
|
return path;
|
|
15779
15846
|
}
|
|
@@ -18,6 +18,15 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
18
18
|
|
|
19
19
|
// src/state/files.ts
|
|
20
20
|
import { randomUUID } from "node:crypto";
|
|
21
|
+
import {
|
|
22
|
+
closeSync,
|
|
23
|
+
fsyncSync,
|
|
24
|
+
mkdirSync,
|
|
25
|
+
openSync,
|
|
26
|
+
renameSync,
|
|
27
|
+
unlinkSync,
|
|
28
|
+
writeFileSync
|
|
29
|
+
} from "node:fs";
|
|
21
30
|
import { mkdir, open, readFile, rename, stat, unlink } from "node:fs/promises";
|
|
22
31
|
import { dirname } from "node:path";
|
|
23
32
|
async function atomicWrite(path, data) {
|
|
@@ -48,6 +57,33 @@ async function atomicWrite(path, data) {
|
|
|
48
57
|
throw error;
|
|
49
58
|
}
|
|
50
59
|
}
|
|
60
|
+
function atomicWriteSync(path, data) {
|
|
61
|
+
const directory = dirname(path);
|
|
62
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
63
|
+
mkdirSync(directory, { recursive: true });
|
|
64
|
+
let descriptor;
|
|
65
|
+
try {
|
|
66
|
+
descriptor = openSync(temporary, "wx", 384);
|
|
67
|
+
writeFileSync(descriptor, data);
|
|
68
|
+
fsyncSync(descriptor);
|
|
69
|
+
closeSync(descriptor);
|
|
70
|
+
descriptor = undefined;
|
|
71
|
+
renameSync(temporary, path);
|
|
72
|
+
const directoryDescriptor = openSync(directory, "r");
|
|
73
|
+
try {
|
|
74
|
+
fsyncSync(directoryDescriptor);
|
|
75
|
+
} finally {
|
|
76
|
+
closeSync(directoryDescriptor);
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (descriptor !== undefined)
|
|
80
|
+
closeSync(descriptor);
|
|
81
|
+
try {
|
|
82
|
+
unlinkSync(temporary);
|
|
83
|
+
} catch {}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
51
87
|
async function withFileLock(lockPath, transition, options = {}) {
|
|
52
88
|
const pollIntervalMs = options.pollIntervalMs ?? 100;
|
|
53
89
|
const staleAfterMs = options.staleAfterMs ?? 30000;
|
|
@@ -14433,6 +14469,7 @@ function createPaths(home, environment) {
|
|
|
14433
14469
|
idps: join(idpsDir, "index.json"),
|
|
14434
14470
|
idpSessionsDir,
|
|
14435
14471
|
exchangeCredentials: join(base, "exchange", "credentials.json"),
|
|
14472
|
+
sessionRoutes: join(base, "session", "routes.json"),
|
|
14436
14473
|
idpDir: (name) => join(idpsDir, name),
|
|
14437
14474
|
idpSession: (identityName) => join(idpSessionsDir, `${identityName}.json`)
|
|
14438
14475
|
});
|
|
@@ -14448,6 +14485,7 @@ var INSTANCES_PATH = paths.instances;
|
|
|
14448
14485
|
var IDPS_PATH = paths.idps;
|
|
14449
14486
|
var IDP_SESSIONS_DIR = paths.idpSessionsDir;
|
|
14450
14487
|
var EXCHANGE_CREDENTIALS_PATH = paths.exchangeCredentials;
|
|
14488
|
+
var SESSION_ROUTES_PATH = paths.sessionRoutes;
|
|
14451
14489
|
// src/state/identities.ts
|
|
14452
14490
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
14453
14491
|
var IDENTITY_STORE_VERSION = 1;
|
|
@@ -14569,6 +14607,35 @@ async function preserveLegacyBackup(path, legacyBytes) {
|
|
|
14569
14607
|
function invalidState(path, cause) {
|
|
14570
14608
|
return new IdentityStateError("IDENTITY_STATE_INVALID", path, `Identity state at ${path} is malformed`, cause);
|
|
14571
14609
|
}
|
|
14610
|
+
// src/state/session-routes.ts
|
|
14611
|
+
import { chmodSync, mkdirSync as mkdirSync2, readFileSync, unlinkSync as unlinkSync2 } from "node:fs";
|
|
14612
|
+
import { dirname as dirname2 } from "node:path";
|
|
14613
|
+
class FileSessionRouteStore {
|
|
14614
|
+
path;
|
|
14615
|
+
constructor(path = SESSION_ROUTES_PATH) {
|
|
14616
|
+
this.path = path;
|
|
14617
|
+
}
|
|
14618
|
+
read() {
|
|
14619
|
+
return JSON.parse(readFileSync(this.path, "utf8"));
|
|
14620
|
+
}
|
|
14621
|
+
write(artifact) {
|
|
14622
|
+
const directory = dirname2(this.path);
|
|
14623
|
+
mkdirSync2(directory, { recursive: true, mode: 448 });
|
|
14624
|
+
chmodSync(directory, 448);
|
|
14625
|
+
atomicWriteSync(this.path, `${JSON.stringify(artifact)}
|
|
14626
|
+
`);
|
|
14627
|
+
chmodSync(this.path, 384);
|
|
14628
|
+
}
|
|
14629
|
+
clear() {
|
|
14630
|
+
try {
|
|
14631
|
+
unlinkSync2(this.path);
|
|
14632
|
+
} catch (error51) {
|
|
14633
|
+
if (error51.code !== "ENOENT")
|
|
14634
|
+
throw error51;
|
|
14635
|
+
}
|
|
14636
|
+
}
|
|
14637
|
+
}
|
|
14638
|
+
var SESSION_ROUTE_STORE = Object.freeze(new FileSessionRouteStore);
|
|
14572
14639
|
export {
|
|
14573
14640
|
ASTRALE_HOME,
|
|
14574
14641
|
CONFIG_PATH,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AuthApi } from '@astrale-os/sdk/auth';
|
|
2
2
|
import type { GraphApi } from '@astrale-os/sdk/client';
|
|
3
|
-
import type { ClientSessionOptions, SessionAuth } from '@astrale-os/sdk/client/session';
|
|
3
|
+
import type { ClientSessionOptions, SessionAuth, SessionRouteStore } from '@astrale-os/sdk/client/session';
|
|
4
4
|
import { ClientSession } from '@astrale-os/sdk/client/session';
|
|
5
5
|
import type { AstraleConfig } from '../lib/config';
|
|
6
6
|
import type { AdminConnectionOptions, ConnectionOptions, ConnectionTarget } from './target';
|
|
@@ -26,6 +26,6 @@ export declare function withAdminClientSession<Value>(options: AdminConnectionOp
|
|
|
26
26
|
export declare function withResolvedClientSession<Value>(target: ConnectionTarget, options: ConnectionOptions, config: AstraleConfig, action: (context: ConnectionContext) => Promise<Value>, open?: ConnectionFactory): Promise<Value>;
|
|
27
27
|
export declare function resolveTimeoutMs(raw: string | undefined): number;
|
|
28
28
|
/** Owner-private construction seam proving that transport and source identity stay distinct. */
|
|
29
|
-
export declare function createClientSessionOptions(target: ConnectionTarget, fetch: NonNullable<ClientSessionOptions['fetch']>, auth: SessionAuth | undefined, timeoutMs: number): ClientSessionOptions;
|
|
29
|
+
export declare function createClientSessionOptions(target: ConnectionTarget, fetch: NonNullable<ClientSessionOptions['fetch']>, auth: SessionAuth | undefined, timeoutMs: number, routeStore?: SessionRouteStore): ClientSessionOptions;
|
|
30
30
|
export declare function lookupManagedInstance(slug: string, options: ConnectionOptions, openAdmin?: typeof withAdminClientSession, connect?: typeof connectAdminInstances): Promise<InstanceInfo>;
|
|
31
31
|
export {};
|
|
@@ -10,6 +10,12 @@ export declare const AstraleConfigSchema: z.ZodObject<{
|
|
|
10
10
|
}, z.core.$strip>>;
|
|
11
11
|
telemetry: z.ZodDefault<z.ZodObject<{
|
|
12
12
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
13
|
+
maxAgeDays: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
|
|
14
|
+
maxBytes: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
|
|
15
|
+
}, z.core.$strip>>;
|
|
16
|
+
browser: z.ZodDefault<z.ZodObject<{
|
|
17
|
+
maxCacheBytes: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
|
|
18
|
+
maxProfileAgeDays: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
|
|
13
19
|
}, z.core.$strip>>;
|
|
14
20
|
}, z.core.$strip>;
|
|
15
21
|
export type AstraleConfig = z.infer<typeof AstraleConfigSchema>;
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
export declare namespace exchange {
|
|
2
2
|
interface Artifact {
|
|
3
|
-
readonly version:
|
|
3
|
+
readonly version: 2;
|
|
4
4
|
readonly entries: Record<string, Entry>;
|
|
5
5
|
}
|
|
6
6
|
interface Key {
|
|
7
7
|
readonly kernelIssuer: string;
|
|
8
8
|
readonly domainIssuer: string;
|
|
9
|
-
readonly
|
|
9
|
+
readonly sourceIssuer: string;
|
|
10
|
+
readonly sourceSubject: string;
|
|
10
11
|
}
|
|
11
12
|
interface Entry {
|
|
12
13
|
readonly credential: string;
|
|
13
14
|
readonly expiresAt: number;
|
|
15
|
+
readonly user: string;
|
|
16
|
+
readonly sourceIssuer: string;
|
|
17
|
+
readonly sourceSubject: string;
|
|
14
18
|
}
|
|
15
19
|
}
|
|
16
20
|
export declare class ExchangeCredentialCache {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Atomically publish one complete private state file through a same-directory temporary file. */
|
|
2
2
|
export declare function atomicWrite(path: string, data: string): Promise<void>;
|
|
3
|
+
/** Atomically publish one complete private state file for synchronous consumer capabilities. */
|
|
4
|
+
export declare function atomicWriteSync(path: string, data: string): void;
|
|
3
5
|
export interface FileLockOptions {
|
|
4
6
|
readonly pollIntervalMs?: number;
|
|
5
7
|
readonly staleAfterMs?: number;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export { atomicWrite, withFileLock } from './files';
|
|
1
|
+
export { atomicWrite, atomicWriteSync, withFileLock } from './files';
|
|
2
2
|
export type { FileLockOptions } from './files';
|
|
3
3
|
export { ExchangeCredentialCache } from './exchange-credentials';
|
|
4
4
|
export type { exchange } from './exchange-credentials';
|
|
5
5
|
export { IDENTITY_STORE_VERSION, readIdentityStore, updateIdentityStore } from './identities';
|
|
6
6
|
export type { Identity, IdentityMode, IdentitySource, IdentityStore, IdentityStoreOptions, IdentityUpdate, Registration, } from './identities';
|
|
7
|
-
export { ASTRALE_HOME, CONFIG_PATH, DATA_DIR, IDENTITIES_PATH, IDPS_PATH, IDP_SESSIONS_DIR, EXCHANGE_CREDENTIALS_PATH, INSTALL_PATH, INSTANCES_PATH, KEYS_DIR, createPaths, paths, } from './paths';
|
|
7
|
+
export { ASTRALE_HOME, CONFIG_PATH, DATA_DIR, IDENTITIES_PATH, IDPS_PATH, IDP_SESSIONS_DIR, EXCHANGE_CREDENTIALS_PATH, INSTALL_PATH, INSTANCES_PATH, KEYS_DIR, SESSION_ROUTES_PATH, createPaths, paths, } from './paths';
|
|
8
8
|
export type { PathEnvironment, Paths } from './paths';
|
|
9
|
+
export { FileSessionRouteStore, SESSION_ROUTE_STORE } from './session-routes';
|
|
@@ -14,6 +14,7 @@ export interface Paths {
|
|
|
14
14
|
readonly idps: string;
|
|
15
15
|
readonly idpSessionsDir: string;
|
|
16
16
|
readonly exchangeCredentials: string;
|
|
17
|
+
readonly sessionRoutes: string;
|
|
17
18
|
idpDir(name: string): string;
|
|
18
19
|
idpSession(identityName: string): string;
|
|
19
20
|
}
|
|
@@ -30,3 +31,4 @@ export declare const INSTANCES_PATH: string;
|
|
|
30
31
|
export declare const IDPS_PATH: string;
|
|
31
32
|
export declare const IDP_SESSIONS_DIR: string;
|
|
32
33
|
export declare const EXCHANGE_CREDENTIALS_PATH: string;
|
|
34
|
+
export declare const SESSION_ROUTES_PATH: string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SessionRouteArtifact, SessionRouteStore } from '@astrale-os/sdk/client/session';
|
|
2
|
+
/** CLI filesystem representation for Kernel Client's admitted confidential route artifact. */
|
|
3
|
+
export declare class FileSessionRouteStore implements SessionRouteStore {
|
|
4
|
+
private readonly path;
|
|
5
|
+
constructor(path?: string);
|
|
6
|
+
read(): unknown;
|
|
7
|
+
write(artifact: SessionRouteArtifact): void;
|
|
8
|
+
clear(): void;
|
|
9
|
+
}
|
|
10
|
+
export declare const SESSION_ROUTE_STORE: Readonly<FileSessionRouteStore>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrale-os/cli",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.29",
|
|
4
4
|
"description": "Astrale CLI — connect to existing Astrale kernels",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astrale",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@astrale-os/ox": ">=0.1.3 <1.0.0",
|
|
66
|
-
"@astrale-os/sdk": "0.5.0-beta.
|
|
66
|
+
"@astrale-os/sdk": "0.5.0-beta.54",
|
|
67
67
|
"@astrale-os/shell": "0.4.2-beta.6",
|
|
68
68
|
"@astrale/commitlint-config": "npm:@jsr/astrale__commitlint-config@~2.0.1",
|
|
69
69
|
"@commitlint/cli": "21.2.2",
|
|
@@ -4,7 +4,7 @@ import { getDefault, readIdentities } from '../../identity/index'
|
|
|
4
4
|
import { deleteIdpSession, listIdpSessions } from '../../lib/idp'
|
|
5
5
|
import { log } from '../../lib/log'
|
|
6
6
|
import { output, RAW_OUTPUT_OPTIONS } from '../../lib/output'
|
|
7
|
-
import { ExchangeCredentialCache } from '../../state/index'
|
|
7
|
+
import { ExchangeCredentialCache, SESSION_ROUTE_STORE } from '../../state/index'
|
|
8
8
|
|
|
9
9
|
export default {
|
|
10
10
|
name: 'logout',
|
|
@@ -30,6 +30,7 @@ export default {
|
|
|
30
30
|
|
|
31
31
|
for (const name of names) await deleteIdpSession(name)
|
|
32
32
|
await new ExchangeCredentialCache().clear()
|
|
33
|
+
SESSION_ROUTE_STORE.clear()
|
|
33
34
|
|
|
34
35
|
if (opts.raw || opts.json) {
|
|
35
36
|
output({ cleared: names }, opts)
|
package/src/commands/browser.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
profileDirFor,
|
|
14
14
|
saveSession,
|
|
15
15
|
} from '../lib/browser'
|
|
16
|
+
import { sweepBrowserProfiles } from '../lib/browser-retention'
|
|
16
17
|
import { readLocalStatus } from '../lib/local-status'
|
|
17
18
|
import { fatal, log } from '../lib/log'
|
|
18
19
|
import { isMachine, output, RAW_OUTPUT_OPTIONS, type RawOutputOpts } from '../lib/output'
|
|
@@ -25,6 +26,29 @@ type BrowserOpts = RawOutputOpts & {
|
|
|
25
26
|
check?: boolean
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Opportunistic profile retention, the same `git gc --auto` shape the session
|
|
31
|
+
* store uses: no daemon, no cron, it just rides on the command that created the
|
|
32
|
+
* mess. Silent when there is nothing to do, and never fatal — losing a sweep is
|
|
33
|
+
* strictly better than losing the browser command.
|
|
34
|
+
*/
|
|
35
|
+
async function reportSweep(machine: boolean): Promise<void> {
|
|
36
|
+
try {
|
|
37
|
+
const swept = await sweepBrowserProfiles()
|
|
38
|
+
if (machine || swept.bytesFreed === 0) return
|
|
39
|
+
const freed = `${(swept.bytesFreed / 1024 / 1024).toFixed(0)} MB`
|
|
40
|
+
const what = [
|
|
41
|
+
swept.removed.length > 0 ? `${swept.removed.length} dormant profile(s) removed` : null,
|
|
42
|
+
swept.purged.length > 0 ? `${swept.purged.length} cache(s) trimmed` : null,
|
|
43
|
+
]
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.join(', ')
|
|
46
|
+
log.dim(`retention: ${what} — ${freed} freed`)
|
|
47
|
+
} catch {
|
|
48
|
+
/* retention must never break the browser command */
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
28
52
|
const LOGIN_TIMEOUT_MS = 180_000
|
|
29
53
|
const POLL_INTERVAL_MS = 2500
|
|
30
54
|
|
|
@@ -125,6 +149,11 @@ Examples:
|
|
|
125
149
|
const host = new URL(gui).host
|
|
126
150
|
await requireAgentBrowser(machine, opts)
|
|
127
151
|
|
|
152
|
+
// Retention runs BEFORE anything launches, so no profile we touch can be in
|
|
153
|
+
// use by a browser this command started. Profiles held by someone else's
|
|
154
|
+
// live browser are skipped by the sweep itself.
|
|
155
|
+
await reportSweep(machine)
|
|
156
|
+
|
|
128
157
|
const usingCdp = !!opts.cdp
|
|
129
158
|
const profile = usingCdp ? null : (opts.profile ?? profileDirFor(host))
|
|
130
159
|
const target = usingCdp ? { cdp: opts.cdp } : { profile: profile! }
|