@astrale-os/cli 1.0.0-beta.27 → 1.0.0-beta.28
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 +26 -3
- package/dist/astrale.js +243 -76
- package/package.json +1 -1
- package/src/commands/__tests__/domain-install-operation.test.ts +23 -0
- package/src/commands/__tests__/install-identity-override.test.ts +3 -3
- package/src/commands/domain/install.ts +19 -11
- package/src/commands/get.ts +1 -1
- package/src/commands/identity/register.ts +3 -4
- package/src/commands/logs.ts +2 -5
- package/src/commands/session/analyze.ts +26 -4
- package/src/lib/__tests__/skills.test.ts +8 -2
- package/src/program/__tests__/program.test.ts +1 -1
- package/src/telemetry/__tests__/retention.test.ts +180 -0
- package/src/telemetry/__tests__/settings.test.ts +102 -0
- package/src/telemetry/__tests__/store-scan.test.ts +128 -0
- package/src/telemetry/__tests__/trigger.test.ts +33 -4
- package/src/telemetry/recorder.ts +6 -3
- package/src/telemetry/retention.ts +129 -0
- package/src/telemetry/session.ts +18 -12
- package/src/telemetry/settings.ts +64 -11
- package/src/telemetry/store.ts +87 -9
- package/src/telemetry/trigger.ts +16 -28
- package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
- package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
- package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
- package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
- package/studio/client/dist/index.html +3 -3
- package/studio/server/agent/prompts/anchors.test.ts +17 -4
- package/studio/server/agent/prompts/anchors.ts +3 -2
- package/studio/server/agent/prompts/system.test.ts +2 -3
- package/studio/server/agent/prompts/system.ts +3 -3
- package/studio/server/agent/run/preparation.ts +1 -1
- package/studio/server/api/context.ts +1 -1
- package/studio/server/api/deployment.ts +1 -1
- package/studio/server/api/views.ts +2 -2
- package/studio/server/api/workspace.ts +1 -1
- package/studio/server/cache.test.ts +3 -8
- package/studio/server/cache.ts +4 -45
- package/studio/server/handoff/copy.ts +1 -1
- package/studio/server/introspect/canonical-schema.test.ts +154 -128
- package/studio/server/introspect/canonical-schema.ts +183 -302
- package/studio/server/introspect/core.ts +14 -21
- package/studio/server/introspect/extractor.ts +11 -15
- package/studio/server/introspect/overlay-tsmorph.test.ts +1 -5
- package/studio/server/introspect/overlay-tsmorph.ts +0 -1
- package/studio/server/introspect/overlay.ts +3 -24
- package/studio/server/introspect/revision.test.ts +24 -28
- package/studio/server/introspect/revision.ts +10 -21
- package/studio/server/introspect/runtime.test.ts +14 -14
- package/studio/server/introspect/runtime.ts +1 -46
- package/studio/server/lifecycle.ts +4 -1
- package/studio/server/state/documents.test.ts +69 -0
- package/studio/server/state/documents.ts +57 -10
- package/studio/shared/contracts/schema.ts +10 -26
- package/studio/shared/contracts/surface.test.ts +2 -2
- package/studio/shared/contracts/workspace.ts +3 -0
- package/studio/shared/schema/identity.ts +0 -1
- package/studio/client/dist/assets/index-C0FAnwNw.css +0 -2
- package/studio/client/dist/assets/index-CpBed0V1.js +0 -81
- package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +0 -1
- package/studio/client/dist/assets/schema-studio-DJm9guI8.js +0 -8
- package/studio/server/introspect/core-extractor.ts +0 -30
- package/studio/server/introspect/overlay.test.ts +0 -44
- package/studio/server/introspect/source-overlay/annotations.ts +0 -14
package/README.md
CHANGED
|
@@ -87,12 +87,12 @@ Install the browser driver once:
|
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
89
|
npm install -g agent-browser && agent-browser install
|
|
90
|
-
npx skills add astrale-os/cli # installs
|
|
90
|
+
npx skills add astrale-os/cli # installs every Astrale skill published by this repository
|
|
91
91
|
npx skills add vercel-labs/agent-browser
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
To install just one
|
|
95
|
-
`npx skills add astrale-os/cli@astrale-
|
|
94
|
+
To install just one, select `astrale-cli`, `astrale-domain`, `astrale-frontend-design`, or
|
|
95
|
+
`astrale-services`, for example: `npx skills add astrale-os/cli@astrale-frontend-design`.
|
|
96
96
|
|
|
97
97
|
## Updating
|
|
98
98
|
|
|
@@ -148,6 +148,29 @@ CLI state lives under `~/.astrale/`:
|
|
|
148
148
|
- `identities.json`
|
|
149
149
|
- keypairs
|
|
150
150
|
- cached IdP sessions
|
|
151
|
+
- `sessions/` — locally recorded work sessions (see below)
|
|
152
|
+
|
|
153
|
+
### Session retention
|
|
154
|
+
|
|
155
|
+
Each invocation appends one line to a local session record under
|
|
156
|
+
`~/.astrale/sessions/`; `astrale session list` shows them. The store is swept
|
|
157
|
+
automatically against two bounds — sessions idle past `maxAgeDays` are dropped,
|
|
158
|
+
then the store is trimmed to `maxBytes`, oldest first. Both are optional:
|
|
159
|
+
|
|
160
|
+
```jsonc
|
|
161
|
+
// ~/.astrale/config.json
|
|
162
|
+
{
|
|
163
|
+
"telemetry": {
|
|
164
|
+
"enabled": true, // false disables recording (the sweep still runs)
|
|
165
|
+
"maxAgeDays": 30, // default
|
|
166
|
+
"maxBytes": 52428800 // default: 50 MB
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`ASTRALE_TELEMETRY_MAX_AGE_DAYS` and `ASTRALE_TELEMETRY_MAX_BYTES` override the
|
|
172
|
+
config for one invocation. Values that are not positive numbers are ignored in
|
|
173
|
+
favour of the default, so a typo cannot leave the store unbounded.
|
|
151
174
|
|
|
152
175
|
## Development
|
|
153
176
|
|
package/dist/astrale.js
CHANGED
|
@@ -2578,7 +2578,7 @@ var package_default;
|
|
|
2578
2578
|
var init_package = __esm(() => {
|
|
2579
2579
|
package_default = {
|
|
2580
2580
|
name: "@astrale-os/cli",
|
|
2581
|
-
version: "1.0.0-beta.
|
|
2581
|
+
version: "1.0.0-beta.28",
|
|
2582
2582
|
description: "Astrale CLI — connect to existing Astrale kernels",
|
|
2583
2583
|
keywords: [
|
|
2584
2584
|
"astrale",
|
|
@@ -41751,7 +41751,7 @@ var require_node_gyp_build_optional_packages = __commonJS(function(exports, modu
|
|
|
41751
41751
|
|
|
41752
41752
|
// node_modules/.pnpm/msgpackr-extract@3.0.4/node_modules/msgpackr-extract/index.js
|
|
41753
41753
|
var require_msgpackr_extract = __commonJS(function(exports, module) {
|
|
41754
|
-
var __dirname = "/
|
|
41754
|
+
var __dirname = "/home/runner/work/cli/cli/node_modules/.pnpm/msgpackr-extract@3.0.4/node_modules/msgpackr-extract";
|
|
41755
41755
|
module.exports = require_node_gyp_build_optional_packages()(__dirname);
|
|
41756
41756
|
});
|
|
41757
41757
|
|
|
@@ -82639,7 +82639,7 @@ var init_get = __esm(() => {
|
|
|
82639
82639
|
description: "Get one canonical node by Path or ID",
|
|
82640
82640
|
afterHelpText: `
|
|
82641
82641
|
Behavior:
|
|
82642
|
-
Resolves one exact Kernel
|
|
82642
|
+
Resolves one exact Kernel Path and prints the canonical Node
|
|
82643
82643
|
{ id, class, props }. Missing and authorization-masked nodes remain
|
|
82644
82644
|
intentionally indistinguishable. @self is expanded before dispatch.
|
|
82645
82645
|
|
|
@@ -83452,11 +83452,8 @@ Behavior:
|
|
|
83452
83452
|
Calls the public Kernel journal syscall and emits its { records, cursor }
|
|
83453
83453
|
page. Topic selection is exact or prefix-based; cursors and timestamps are
|
|
83454
83454
|
opaque strings owned by the journal backend. --follow reuses one Client Session
|
|
83455
|
-
and advances only with the returned cursor.
|
|
83456
|
-
one complete admitted record per line; YAML follow is unsupported.
|
|
83457
|
-
|
|
83458
|
-
Historical event-glob lowering and the application-specific services-domain
|
|
83459
|
-
log buffer are not part of the Kernel V2 journal contract.
|
|
83455
|
+
and advances only with the returned cursor. With --json, follow output is
|
|
83456
|
+
NDJSON with one complete admitted record per line; YAML follow is unsupported.
|
|
83460
83457
|
|
|
83461
83458
|
Examples:
|
|
83462
83459
|
$ astrale logs -i staging --limit 50
|
|
@@ -90026,7 +90023,7 @@ async function installDirect(target2, opts, dependencies = {}) {
|
|
|
90026
90023
|
}
|
|
90027
90024
|
host = validateInstallUrl(target2);
|
|
90028
90025
|
operation = opts.operation === undefined ? direct.createOperationId() : direct.acceptOperationId(opts.operation);
|
|
90029
|
-
consentedOrigin = await ensureIdentityOverrideConsent(target2, host, opts.allowIdentityOverride ?? false);
|
|
90026
|
+
consentedOrigin = await ensureIdentityOverrideConsent(target2, host, opts.allowIdentityOverride ?? false, isMachine(opts));
|
|
90030
90027
|
} catch (e) {
|
|
90031
90028
|
fatal(e, opts);
|
|
90032
90029
|
}
|
|
@@ -90046,7 +90043,9 @@ async function installDirect(target2, opts, dependencies = {}) {
|
|
|
90046
90043
|
log.success(`Domain ${installed.status}: ${installed.origin}@${installed.revision}`);
|
|
90047
90044
|
log.dim(` operation: ${installed.operation}`);
|
|
90048
90045
|
if (isIdentityOverride(installed.origin, host) && installed.origin !== consentedOrigin) {
|
|
90049
|
-
|
|
90046
|
+
if (!isMachine(fmtOpts)) {
|
|
90047
|
+
log.warn(`Installed origin "${installed.origin}" differs from the serving host "${host}" ` + `and was not confirmed before install (the worker Publication was unavailable). ` + `Every ${installed.origin}/* call on this instance now routes to ${host}.`);
|
|
90048
|
+
}
|
|
90050
90049
|
}
|
|
90051
90050
|
}
|
|
90052
90051
|
});
|
|
@@ -90070,16 +90069,20 @@ function validateInstallUrl(value3) {
|
|
|
90070
90069
|
function isIdentityOverride(origin2, host) {
|
|
90071
90070
|
return origin2.toLowerCase() !== host.toLowerCase();
|
|
90072
90071
|
}
|
|
90073
|
-
async function ensureIdentityOverrideConsent(url3, host, allow) {
|
|
90072
|
+
async function ensureIdentityOverrideConsent(url3, host, allow, machine) {
|
|
90074
90073
|
const origin2 = await probeDeclaredOrigin(url3);
|
|
90075
90074
|
if (origin2 === undefined) {
|
|
90076
|
-
|
|
90075
|
+
if (!machine) {
|
|
90076
|
+
log.warn(`Could not read a declared origin from ` + `${new URL("/.well-known/astrale/domain.json", url3).href} — ` + `skipping the pre-install identity check (the installed origin is verified after install).`);
|
|
90077
|
+
}
|
|
90077
90078
|
return;
|
|
90078
90079
|
}
|
|
90079
90080
|
if (!isIdentityOverride(origin2, host))
|
|
90080
90081
|
return;
|
|
90081
90082
|
if (allow) {
|
|
90082
|
-
|
|
90083
|
+
if (!machine) {
|
|
90084
|
+
log.warn(`Identity override consented via --allow-identity-override: ${origin2} ← ${host}`);
|
|
90085
|
+
}
|
|
90083
90086
|
return origin2;
|
|
90084
90087
|
}
|
|
90085
90088
|
const banner = source_default.red.bold("⚠ DANGER — IDENTITY OVERRIDE") + `
|
|
@@ -90558,10 +90561,9 @@ Behavior:
|
|
|
90558
90561
|
its result and stores the same target-bound registration. The callable owns
|
|
90559
90562
|
authorization; the CLI never receives installed Domain authority.
|
|
90560
90563
|
|
|
90561
|
-
Kernel
|
|
90562
|
-
|
|
90563
|
-
|
|
90564
|
-
the returned (issuer, subject) for subsequent calls.
|
|
90564
|
+
The Kernel assigns each Node ID; callers do not choose a storage path. The
|
|
90565
|
+
CLI binds the key proof to the exact provision fingerprint and target Kernel
|
|
90566
|
+
audience, then caches the returned (issuer, subject) for subsequent calls.
|
|
90565
90567
|
|
|
90566
90568
|
Example:
|
|
90567
90569
|
$ astrale identity register alice --class /:accounts.example:class.User --props '{"accounts.example:class.User.property.name":"Alice"}' -i staging
|
|
@@ -91270,6 +91272,53 @@ function metaPath(id) {
|
|
|
91270
91272
|
function markerPath(id) {
|
|
91271
91273
|
return join19(sessionDir(id), ".analyzed");
|
|
91272
91274
|
}
|
|
91275
|
+
function sessionIds() {
|
|
91276
|
+
try {
|
|
91277
|
+
return readdirSync2(sessionsRoot()).filter((name) => !name.startsWith("."));
|
|
91278
|
+
} catch {
|
|
91279
|
+
return [];
|
|
91280
|
+
}
|
|
91281
|
+
}
|
|
91282
|
+
function scanSessions(now = Date.now()) {
|
|
91283
|
+
return sessionIds().map((id) => {
|
|
91284
|
+
let lastEventAt = null;
|
|
91285
|
+
try {
|
|
91286
|
+
lastEventAt = statSync3(eventsPath(id)).mtime;
|
|
91287
|
+
} catch {}
|
|
91288
|
+
return {
|
|
91289
|
+
id,
|
|
91290
|
+
lastEventAt,
|
|
91291
|
+
closed: lastEventAt !== null && now - lastEventAt.getTime() > IDLE_WINDOW_MS,
|
|
91292
|
+
analyzed: existsSync9(markerPath(id))
|
|
91293
|
+
};
|
|
91294
|
+
}).sort((a, b) => (b.lastEventAt?.getTime() ?? 0) - (a.lastEventAt?.getTime() ?? 0));
|
|
91295
|
+
}
|
|
91296
|
+
function directoryBytes(dir) {
|
|
91297
|
+
let entries;
|
|
91298
|
+
try {
|
|
91299
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
91300
|
+
} catch {
|
|
91301
|
+
return 0;
|
|
91302
|
+
}
|
|
91303
|
+
let total = 0;
|
|
91304
|
+
for (const entry2 of entries) {
|
|
91305
|
+
const path8 = join19(dir, entry2.name);
|
|
91306
|
+
if (entry2.isDirectory()) {
|
|
91307
|
+
total += directoryBytes(path8);
|
|
91308
|
+
} else if (entry2.isFile()) {
|
|
91309
|
+
try {
|
|
91310
|
+
total += statSync3(path8).size;
|
|
91311
|
+
} catch {}
|
|
91312
|
+
}
|
|
91313
|
+
}
|
|
91314
|
+
return total;
|
|
91315
|
+
}
|
|
91316
|
+
function sessionBytes(id) {
|
|
91317
|
+
return directoryBytes(sessionDir(id));
|
|
91318
|
+
}
|
|
91319
|
+
function readMeta(id) {
|
|
91320
|
+
return readJsonSafe(metaPath(id));
|
|
91321
|
+
}
|
|
91273
91322
|
function readJsonSafe(path8) {
|
|
91274
91323
|
try {
|
|
91275
91324
|
return JSON.parse(readFileSync3(path8, "utf-8"));
|
|
@@ -91294,13 +91343,7 @@ function inspectSession(id, now = Date.now()) {
|
|
|
91294
91343
|
};
|
|
91295
91344
|
}
|
|
91296
91345
|
function listSessions2(now = Date.now()) {
|
|
91297
|
-
|
|
91298
|
-
try {
|
|
91299
|
-
ids = readdirSync2(sessionsRoot()).filter((n) => !n.startsWith("."));
|
|
91300
|
-
} catch {
|
|
91301
|
-
return [];
|
|
91302
|
-
}
|
|
91303
|
-
return ids.map((id) => inspectSession(id, now)).filter((s) => s !== null).sort((a, b) => (b.lastEventAt?.getTime() ?? 0) - (a.lastEventAt?.getTime() ?? 0));
|
|
91346
|
+
return sessionIds().map((id) => inspectSession(id, now)).filter((s) => s !== null).sort((a, b) => (b.lastEventAt?.getTime() ?? 0) - (a.lastEventAt?.getTime() ?? 0));
|
|
91304
91347
|
}
|
|
91305
91348
|
var IDLE_WINDOW_MS;
|
|
91306
91349
|
var init_store = __esm(() => {
|
|
@@ -91816,25 +91859,108 @@ var init_analyze = __esm(() => {
|
|
|
91816
91859
|
|
|
91817
91860
|
// src/telemetry/settings.ts
|
|
91818
91861
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
91862
|
+
function readConfig2() {
|
|
91863
|
+
try {
|
|
91864
|
+
const parsed = JSON.parse(readFileSync5(createPaths().config, "utf-8"));
|
|
91865
|
+
const telemetry = parsed.telemetry;
|
|
91866
|
+
return typeof telemetry === "object" && telemetry !== null ? telemetry : {};
|
|
91867
|
+
} catch {
|
|
91868
|
+
return {};
|
|
91869
|
+
}
|
|
91870
|
+
}
|
|
91819
91871
|
function telemetryEnabled() {
|
|
91820
91872
|
const env2 = process.env.ASTRALE_TELEMETRY;
|
|
91821
91873
|
if (env2 !== undefined && OFF_VALUES.has(env2.trim().toLowerCase()))
|
|
91822
91874
|
return false;
|
|
91823
|
-
|
|
91824
|
-
|
|
91825
|
-
|
|
91826
|
-
|
|
91827
|
-
|
|
91828
|
-
|
|
91875
|
+
return readConfig2().enabled !== false;
|
|
91876
|
+
}
|
|
91877
|
+
function firstPositive(candidates) {
|
|
91878
|
+
for (const candidate2 of candidates) {
|
|
91879
|
+
if (candidate2 === undefined)
|
|
91880
|
+
continue;
|
|
91881
|
+
const n = typeof candidate2 === "string" ? Number(candidate2.trim()) : candidate2;
|
|
91882
|
+
if (Number.isFinite(n) && n > 0)
|
|
91883
|
+
return n;
|
|
91884
|
+
}
|
|
91885
|
+
return null;
|
|
91829
91886
|
}
|
|
91830
|
-
|
|
91887
|
+
function retentionBudget() {
|
|
91888
|
+
const config2 = readConfig2();
|
|
91889
|
+
const days = firstPositive([process.env.ASTRALE_TELEMETRY_MAX_AGE_DAYS, config2.maxAgeDays]) ?? DEFAULT_MAX_AGE_DAYS;
|
|
91890
|
+
const bytes = firstPositive([process.env.ASTRALE_TELEMETRY_MAX_BYTES, config2.maxBytes]) ?? DEFAULT_MAX_BYTES;
|
|
91891
|
+
return { maxAgeMs: days * 24 * 60 * 60 * 1000, maxBytes: bytes };
|
|
91892
|
+
}
|
|
91893
|
+
var OFF_VALUES, DEFAULT_MAX_AGE_DAYS = 30, DEFAULT_MAX_BYTES;
|
|
91831
91894
|
var init_settings = __esm(() => {
|
|
91832
91895
|
init_state();
|
|
91833
91896
|
OFF_VALUES = new Set(["0", "false", "off"]);
|
|
91897
|
+
DEFAULT_MAX_BYTES = 50 * 1024 * 1024;
|
|
91898
|
+
});
|
|
91899
|
+
|
|
91900
|
+
// src/telemetry/retention.ts
|
|
91901
|
+
import { rmSync as rmSync2 } from "node:fs";
|
|
91902
|
+
function remove(id, into) {
|
|
91903
|
+
try {
|
|
91904
|
+
rmSync2(sessionDir(id), { recursive: true, force: true });
|
|
91905
|
+
into.push(id);
|
|
91906
|
+
} catch {}
|
|
91907
|
+
}
|
|
91908
|
+
function sweepByAge(sessions, options = {}) {
|
|
91909
|
+
const { maxAgeMs } = options.budget ?? retentionBudget();
|
|
91910
|
+
const cutoff = (options.now ?? Date.now()) - maxAgeMs;
|
|
91911
|
+
const limit = options.limit ?? Number.POSITIVE_INFINITY;
|
|
91912
|
+
const protect = options.protect;
|
|
91913
|
+
const removed = [];
|
|
91914
|
+
for (const session3 of sessions) {
|
|
91915
|
+
if (removed.length >= limit)
|
|
91916
|
+
break;
|
|
91917
|
+
if (protect?.has(session3.id))
|
|
91918
|
+
continue;
|
|
91919
|
+
if (session3.lastEventAt !== null && session3.lastEventAt.getTime() < cutoff) {
|
|
91920
|
+
remove(session3.id, removed);
|
|
91921
|
+
}
|
|
91922
|
+
}
|
|
91923
|
+
return { removed };
|
|
91924
|
+
}
|
|
91925
|
+
function sweepToBudget(sessions, options = {}) {
|
|
91926
|
+
const { maxBytes } = options.budget ?? retentionBudget();
|
|
91927
|
+
const protect = options.protect;
|
|
91928
|
+
const removed = [];
|
|
91929
|
+
const sized = sessions.map((session3) => ({ session: session3, bytes: sessionBytes(session3.id) }));
|
|
91930
|
+
let total = sized.reduce((sum, entry2) => sum + entry2.bytes, 0);
|
|
91931
|
+
if (total <= maxBytes)
|
|
91932
|
+
return { removed };
|
|
91933
|
+
const evictable = sized.filter((entry2) => entry2.session.closed && !protect?.has(entry2.session.id)).sort((a, b) => (a.session.lastEventAt?.getTime() ?? 0) - (b.session.lastEventAt?.getTime() ?? 0));
|
|
91934
|
+
for (const analyzedFirst of [true, false]) {
|
|
91935
|
+
for (const entry2 of evictable) {
|
|
91936
|
+
if (total <= maxBytes)
|
|
91937
|
+
return { removed };
|
|
91938
|
+
if (entry2.session.analyzed !== analyzedFirst)
|
|
91939
|
+
continue;
|
|
91940
|
+
const before = removed.length;
|
|
91941
|
+
remove(entry2.session.id, removed);
|
|
91942
|
+
if (removed.length > before)
|
|
91943
|
+
total -= entry2.bytes;
|
|
91944
|
+
}
|
|
91945
|
+
}
|
|
91946
|
+
return { removed };
|
|
91947
|
+
}
|
|
91948
|
+
function sweepStore(options = {}) {
|
|
91949
|
+
const budget = options.budget ?? retentionBudget();
|
|
91950
|
+
const sessions = scanSessions();
|
|
91951
|
+
const byAge = sweepByAge(sessions, { ...options, budget });
|
|
91952
|
+
const gone = new Set(byAge.removed);
|
|
91953
|
+
const bySize = sweepToBudget(sessions.filter((session3) => !gone.has(session3.id)), { ...options, budget });
|
|
91954
|
+
return { removed: [...byAge.removed, ...bySize.removed] };
|
|
91955
|
+
}
|
|
91956
|
+
var MAX_REMOVALS_ON_START = 20;
|
|
91957
|
+
var init_retention = __esm(() => {
|
|
91958
|
+
init_settings();
|
|
91959
|
+
init_store();
|
|
91834
91960
|
});
|
|
91835
91961
|
|
|
91836
91962
|
// src/telemetry/trigger.ts
|
|
91837
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync2, readFileSync as readFileSync6,
|
|
91963
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync2, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
91838
91964
|
import { join as join23 } from "node:path";
|
|
91839
91965
|
function lockPath() {
|
|
91840
91966
|
return join23(sessionsRoot(), ".analyzer.lock");
|
|
@@ -91849,12 +91975,12 @@ function restampLock() {
|
|
|
91849
91975
|
writeFileSync3(lockPath(), JSON.stringify({ pid: process.pid, at: Date.now() }));
|
|
91850
91976
|
} catch {}
|
|
91851
91977
|
}
|
|
91852
|
-
var LOCK_STALE_MS
|
|
91978
|
+
var LOCK_STALE_MS;
|
|
91853
91979
|
var init_trigger = __esm(() => {
|
|
91980
|
+
init_retention();
|
|
91854
91981
|
init_settings();
|
|
91855
91982
|
init_store();
|
|
91856
91983
|
LOCK_STALE_MS = 30 * 60 * 1000;
|
|
91857
|
-
GC_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
91858
91984
|
});
|
|
91859
91985
|
|
|
91860
91986
|
// src/commands/session/analyze.ts
|
|
@@ -91867,12 +91993,19 @@ var init_analyze2 = __esm(() => {
|
|
|
91867
91993
|
init_source();
|
|
91868
91994
|
init_log();
|
|
91869
91995
|
init_analyze();
|
|
91996
|
+
init_retention();
|
|
91870
91997
|
init_store();
|
|
91871
91998
|
init_trigger();
|
|
91872
91999
|
analyze_default = {
|
|
91873
92000
|
name: "analyze",
|
|
91874
92001
|
description: "Analyze a recorded session for DX frictions (dry-run: writes report.md)",
|
|
91875
|
-
arguments: [
|
|
92002
|
+
arguments: [
|
|
92003
|
+
{
|
|
92004
|
+
name: "id",
|
|
92005
|
+
description: "Session id (default: most recent closed, unanalyzed)",
|
|
92006
|
+
required: false
|
|
92007
|
+
}
|
|
92008
|
+
],
|
|
91876
92009
|
options: [
|
|
91877
92010
|
{ flags: "--file", description: "File cleared findings as issues on the admin tracker" },
|
|
91878
92011
|
{ flags: "--model <model>", description: "Model for the analyzer pass" },
|
|
@@ -91882,10 +92015,10 @@ var init_analyze2 = __esm(() => {
|
|
|
91882
92015
|
action: async (id, opts) => {
|
|
91883
92016
|
if (opts.auto)
|
|
91884
92017
|
restampLock();
|
|
92018
|
+
let target2 = id;
|
|
91885
92019
|
try {
|
|
91886
|
-
let target2 = id;
|
|
91887
92020
|
if (!target2) {
|
|
91888
|
-
const candidate2 =
|
|
92021
|
+
const candidate2 = scanSessions().find((s) => s.closed && (opts.force || !s.analyzed));
|
|
91889
92022
|
if (!candidate2) {
|
|
91890
92023
|
if (!opts.auto)
|
|
91891
92024
|
log.dim("Nothing to analyze — no closed, unanalyzed session.");
|
|
@@ -91910,6 +92043,12 @@ var init_analyze2 = __esm(() => {
|
|
|
91910
92043
|
} finally {
|
|
91911
92044
|
if (opts.auto)
|
|
91912
92045
|
releaseLock();
|
|
92046
|
+
try {
|
|
92047
|
+
const swept = sweepStore({ protect: new Set(target2 === undefined ? [] : [target2]) });
|
|
92048
|
+
if (!opts.auto && swept.removed.length > 0) {
|
|
92049
|
+
log.dim(`retention: removed ${swept.removed.length} session(s)`);
|
|
92050
|
+
}
|
|
92051
|
+
} catch {}
|
|
91913
92052
|
}
|
|
91914
92053
|
}
|
|
91915
92054
|
};
|
|
@@ -92694,11 +92833,13 @@ function stamp(d = new Date) {
|
|
|
92694
92833
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
92695
92834
|
return `${p(d.getFullYear(), 4)}${p(d.getMonth() + 1)}${p(d.getDate())}${p(d.getHours())}${p(d.getMinutes())}`;
|
|
92696
92835
|
}
|
|
92697
|
-
function findOpenAmbient(root) {
|
|
92698
|
-
for (const s of
|
|
92699
|
-
if (s.
|
|
92836
|
+
function findOpenAmbient(root, sessions) {
|
|
92837
|
+
for (const s of sessions) {
|
|
92838
|
+
if (s.closed || s.analyzed)
|
|
92839
|
+
continue;
|
|
92840
|
+
const meta3 = readMeta(s.id);
|
|
92841
|
+
if (meta3?.root === root && meta3.explicit === false)
|
|
92700
92842
|
return s.id;
|
|
92701
|
-
}
|
|
92702
92843
|
}
|
|
92703
92844
|
return null;
|
|
92704
92845
|
}
|
|
@@ -92706,7 +92847,7 @@ function mintAmbientId(root) {
|
|
|
92706
92847
|
const hash8 = createHash4("sha256").update(root).digest("hex").slice(0, 8);
|
|
92707
92848
|
return `amb-${hash8}-${stamp()}`;
|
|
92708
92849
|
}
|
|
92709
|
-
function resolveSession2(cwd) {
|
|
92850
|
+
function resolveSession2(cwd, sessions) {
|
|
92710
92851
|
const root = findGitRoot(cwd) ?? cwd;
|
|
92711
92852
|
const pinned = process.env.ASTRALE_SESSION;
|
|
92712
92853
|
if (pinned) {
|
|
@@ -92714,10 +92855,11 @@ function resolveSession2(cwd) {
|
|
|
92714
92855
|
if (id.length > 0)
|
|
92715
92856
|
return { id, root, explicit: true };
|
|
92716
92857
|
}
|
|
92717
|
-
|
|
92858
|
+
const scan2 = sessions ?? scanSessions();
|
|
92859
|
+
return { id: findOpenAmbient(root, scan2) ?? mintAmbientId(root), root, explicit: false };
|
|
92718
92860
|
}
|
|
92719
|
-
function ensureSession(cwd) {
|
|
92720
|
-
const resolved = resolveSession2(cwd);
|
|
92861
|
+
function ensureSession(cwd, sessions) {
|
|
92862
|
+
const resolved = resolveSession2(cwd, sessions);
|
|
92721
92863
|
const dir = sessionDir(resolved.id);
|
|
92722
92864
|
if (!existsSync13(dir)) {
|
|
92723
92865
|
try {
|
|
@@ -92751,7 +92893,7 @@ var HELP_VERSION = new Set([
|
|
|
92751
92893
|
function isHelpOrVersion(args) {
|
|
92752
92894
|
return args.every((a) => HELP_VERSION.has(a));
|
|
92753
92895
|
}
|
|
92754
|
-
function beginInvocation(argv) {
|
|
92896
|
+
function beginInvocation(argv, sessions) {
|
|
92755
92897
|
try {
|
|
92756
92898
|
const args = argv.slice(2);
|
|
92757
92899
|
if (!telemetryEnabled() || isHelpOrVersion(args))
|
|
@@ -92759,7 +92901,7 @@ function beginInvocation(argv) {
|
|
|
92759
92901
|
const startMs = Date.now();
|
|
92760
92902
|
const ts = new Date(startMs).toISOString();
|
|
92761
92903
|
const cwd = process.cwd();
|
|
92762
|
-
const { id, root } = ensureSession(cwd);
|
|
92904
|
+
const { id, root } = ensureSession(cwd, sessions);
|
|
92763
92905
|
return (exitCode, errorName) => {
|
|
92764
92906
|
try {
|
|
92765
92907
|
const event = {
|
|
@@ -92783,37 +92925,61 @@ function beginInvocation(argv) {
|
|
|
92783
92925
|
}
|
|
92784
92926
|
}
|
|
92785
92927
|
|
|
92928
|
+
// src/telemetry/store.ts
|
|
92929
|
+
init_state();
|
|
92930
|
+
import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync7, statSync as statSync6 } from "node:fs";
|
|
92931
|
+
import { join as join25 } from "node:path";
|
|
92932
|
+
var IDLE_WINDOW_MS2 = 30 * 60 * 1000;
|
|
92933
|
+
function sessionsRoot2() {
|
|
92934
|
+
return join25(createPaths().home, "sessions");
|
|
92935
|
+
}
|
|
92936
|
+
function sessionDir2(id) {
|
|
92937
|
+
return join25(sessionsRoot2(), id);
|
|
92938
|
+
}
|
|
92939
|
+
function eventsPath2(id) {
|
|
92940
|
+
return join25(sessionDir2(id), "events.jsonl");
|
|
92941
|
+
}
|
|
92942
|
+
function markerPath2(id) {
|
|
92943
|
+
return join25(sessionDir2(id), ".analyzed");
|
|
92944
|
+
}
|
|
92945
|
+
function sessionIds2() {
|
|
92946
|
+
try {
|
|
92947
|
+
return readdirSync5(sessionsRoot2()).filter((name) => !name.startsWith("."));
|
|
92948
|
+
} catch {
|
|
92949
|
+
return [];
|
|
92950
|
+
}
|
|
92951
|
+
}
|
|
92952
|
+
function scanSessions2(now = Date.now()) {
|
|
92953
|
+
return sessionIds2().map((id) => {
|
|
92954
|
+
let lastEventAt = null;
|
|
92955
|
+
try {
|
|
92956
|
+
lastEventAt = statSync6(eventsPath2(id)).mtime;
|
|
92957
|
+
} catch {}
|
|
92958
|
+
return {
|
|
92959
|
+
id,
|
|
92960
|
+
lastEventAt,
|
|
92961
|
+
closed: lastEventAt !== null && now - lastEventAt.getTime() > IDLE_WINDOW_MS2,
|
|
92962
|
+
analyzed: existsSync14(markerPath2(id))
|
|
92963
|
+
};
|
|
92964
|
+
}).sort((a, b) => (b.lastEventAt?.getTime() ?? 0) - (a.lastEventAt?.getTime() ?? 0));
|
|
92965
|
+
}
|
|
92966
|
+
|
|
92786
92967
|
// src/telemetry/trigger.ts
|
|
92968
|
+
init_retention();
|
|
92787
92969
|
init_settings();
|
|
92788
92970
|
init_store();
|
|
92789
92971
|
import { spawn as spawn4 } from "node:child_process";
|
|
92790
|
-
import { existsSync as
|
|
92791
|
-
import { join as
|
|
92972
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync4, readFileSync as readFileSync8, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
92973
|
+
import { join as join26 } from "node:path";
|
|
92792
92974
|
var LOCK_STALE_MS2 = 30 * 60 * 1000;
|
|
92793
|
-
var GC_AGE_MS2 = 30 * 24 * 60 * 60 * 1000;
|
|
92794
|
-
var GC_MAX_PER_RUN = 20;
|
|
92795
|
-
function gcOldSessions(sessions) {
|
|
92796
|
-
try {
|
|
92797
|
-
const cutoff = Date.now() - GC_AGE_MS2;
|
|
92798
|
-
let removed = 0;
|
|
92799
|
-
for (const s of sessions) {
|
|
92800
|
-
if (removed >= GC_MAX_PER_RUN)
|
|
92801
|
-
break;
|
|
92802
|
-
if (s.analyzed !== null && s.lastEventAt !== null && s.lastEventAt.getTime() < cutoff) {
|
|
92803
|
-
rmSync3(sessionDir(s.id), { recursive: true, force: true });
|
|
92804
|
-
removed++;
|
|
92805
|
-
}
|
|
92806
|
-
}
|
|
92807
|
-
} catch {}
|
|
92808
|
-
}
|
|
92809
92975
|
function lockPath2() {
|
|
92810
|
-
return
|
|
92976
|
+
return join26(sessionsRoot(), ".analyzer.lock");
|
|
92811
92977
|
}
|
|
92812
92978
|
function claimLock() {
|
|
92813
92979
|
const path8 = lockPath2();
|
|
92814
92980
|
try {
|
|
92815
|
-
if (
|
|
92816
|
-
const lock = JSON.parse(
|
|
92981
|
+
if (existsSync15(path8)) {
|
|
92982
|
+
const lock = JSON.parse(readFileSync8(path8, "utf-8"));
|
|
92817
92983
|
const stale2 = Date.now() - lock.at > LOCK_STALE_MS2;
|
|
92818
92984
|
let alive = false;
|
|
92819
92985
|
try {
|
|
@@ -92831,17 +92997,17 @@ function claimLock() {
|
|
|
92831
92997
|
return false;
|
|
92832
92998
|
}
|
|
92833
92999
|
}
|
|
92834
|
-
function maybeTriggerAnalysis(argv) {
|
|
93000
|
+
function maybeTriggerAnalysis(argv, scan2) {
|
|
92835
93001
|
try {
|
|
92836
|
-
if (!telemetryEnabled())
|
|
92837
|
-
return;
|
|
92838
93002
|
if (process.env.ASTRALE_TELEMETRY_NO_TRIGGER === "1")
|
|
92839
93003
|
return;
|
|
93004
|
+
const sessions = scan2 ?? scanSessions();
|
|
93005
|
+
const swept = new Set(sweepByAge(sessions, { limit: MAX_REMOVALS_ON_START }).removed);
|
|
93006
|
+
if (!telemetryEnabled())
|
|
93007
|
+
return;
|
|
92840
93008
|
if (argv[2] === "session")
|
|
92841
93009
|
return;
|
|
92842
|
-
const
|
|
92843
|
-
gcOldSessions(sessions);
|
|
92844
|
-
const target2 = sessions.find((s) => s.closed && s.analyzed === null && s.lastEventAt !== null);
|
|
93010
|
+
const target2 = sessions.find((s) => s.closed && !s.analyzed && s.lastEventAt !== null && !swept.has(s.id));
|
|
92845
93011
|
if (!target2)
|
|
92846
93012
|
return;
|
|
92847
93013
|
if (!claimLock())
|
|
@@ -92864,11 +93030,12 @@ function overrideExits(command) {
|
|
|
92864
93030
|
for (const child3 of command.commands)
|
|
92865
93031
|
overrideExits(child3);
|
|
92866
93032
|
}
|
|
92867
|
-
var
|
|
93033
|
+
var sessions = scanSessions2();
|
|
93034
|
+
var finalize2 = process.argv[2] === "session" ? undefined : beginInvocation(process.argv, sessions);
|
|
92868
93035
|
var errorName;
|
|
92869
93036
|
if (finalize2)
|
|
92870
93037
|
process.on("exit", (code) => finalize2(code ?? 0, errorName));
|
|
92871
|
-
maybeTriggerAnalysis(process.argv);
|
|
93038
|
+
maybeTriggerAnalysis(process.argv, sessions);
|
|
92872
93039
|
var argv = normalizeRootVersionArgv(process.argv);
|
|
92873
93040
|
configureInvocation(argv);
|
|
92874
93041
|
var program4 = await buildProgram();
|
package/package.json
CHANGED
|
@@ -93,6 +93,29 @@ describe('direct domain install operation identity', () => {
|
|
|
93
93
|
])
|
|
94
94
|
})
|
|
95
95
|
|
|
96
|
+
test('keeps identity-override consent warnings out of machine output', async () => {
|
|
97
|
+
globalThis.fetch = mock(async () => publicationResponse()) as unknown as typeof fetch
|
|
98
|
+
const originalError = console.error
|
|
99
|
+
const warnings: unknown[][] = []
|
|
100
|
+
console.error = (...args: unknown[]) => {
|
|
101
|
+
warnings.push(args)
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
await installDirect(
|
|
105
|
+
'https://alias.test',
|
|
106
|
+
{ direct: true, json: true, allowIdentityOverride: true },
|
|
107
|
+
{
|
|
108
|
+
createOperationId: () => GENERATED,
|
|
109
|
+
runKernelCommand: async () => undefined,
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
} finally {
|
|
113
|
+
console.error = originalError
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
expect(warnings).toEqual([])
|
|
117
|
+
})
|
|
118
|
+
|
|
96
119
|
test('rejects a non-UUID operation before metadata or Kernel transport', async () => {
|
|
97
120
|
const originalExit = process.exit
|
|
98
121
|
const originalWrite = process.stderr.write
|
|
@@ -54,11 +54,11 @@ describe('declared-origin Publication probe', () => {
|
|
|
54
54
|
expect(await probeDeclaredOrigin(url)).toBe('crm.acme.dev')
|
|
55
55
|
})
|
|
56
56
|
|
|
57
|
-
test('does not accept the
|
|
58
|
-
const
|
|
57
|
+
test('does not accept a Publication without the canonical origin field', async () => {
|
|
58
|
+
const invalid = servePublication(() =>
|
|
59
59
|
Response.json({ iss: 'https://x', domainName: 'crm.acme.dev' }),
|
|
60
60
|
)
|
|
61
|
-
expect(await probeDeclaredOrigin(
|
|
61
|
+
expect(await probeDeclaredOrigin(invalid)).toBeUndefined()
|
|
62
62
|
})
|
|
63
63
|
|
|
64
64
|
test('degrades to undefined on invalid Publication, non-200, bad JSON, or dead host', async () => {
|