@astrale-os/cli 1.0.0-beta.26 → 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 +31 -5
- package/dist/astrale.js +1201 -358
- package/package.json +3 -2
- 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/commands/update.ts +61 -40
- package/src/lib/__tests__/skills.test.ts +809 -0
- package/src/lib/skills/lock.ts +117 -0
- package/src/lib/skills/sync.ts +814 -0
- package/src/lib/skills.ts +1 -52
- package/src/program/__tests__/program.test.ts +1 -1
- package/src/setup/steps/skills.ts +40 -38
- 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/cli-consumers.test.ts +31 -2
- 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/server/workspace/updates.ts +23 -1
- package/studio/shared/contracts/runtime.ts +4 -0
- 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/tsconfig.json +1 -1
- package/studio/client/dist/assets/index-C0FAnwNw.css +0 -2
- package/studio/client/dist/assets/index-OeJK1TjG.js +0 -81
- package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +0 -1
- package/studio/client/dist/assets/schema-studio-DRdfu_cQ.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/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.28",
|
|
4
4
|
"description": "Astrale CLI — connect to existing Astrale kernels",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astrale",
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
"@astrale/commitlint-config": "npm:@jsr/astrale__commitlint-config@~2.0.1",
|
|
69
69
|
"@commitlint/cli": "21.2.2",
|
|
70
70
|
"@commitlint/config-conventional": "21.2.2",
|
|
71
|
-
"@types/bun": "^1.1.16",
|
|
72
71
|
"@types/node": "^22.0.0",
|
|
72
|
+
"bun-types": "1.4.0",
|
|
73
73
|
"husky": "~9.1.7",
|
|
74
74
|
"lint-staged": "17.3.0",
|
|
75
75
|
"msgpackr": "^2.0.5",
|
|
@@ -105,6 +105,7 @@
|
|
|
105
105
|
"format": "pnpm exec oxfmt --write .",
|
|
106
106
|
"format:check": "pnpm exec oxfmt --check .",
|
|
107
107
|
"test": "node scripts/qualification/source-boundary.mjs --target && bun test src studio/client/src studio/server studio/shared && node --test scripts/*.test.mjs",
|
|
108
|
+
"test:skills-e2e": "node scripts/qualification/skills-update-e2e.mjs",
|
|
108
109
|
"test:studio": "pnpm --dir studio test",
|
|
109
110
|
"test:watch": "bun test --watch src"
|
|
110
111
|
}
|
|
@@ -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 () => {
|
|
@@ -426,6 +426,7 @@ export async function installDirect(
|
|
|
426
426
|
target,
|
|
427
427
|
host,
|
|
428
428
|
opts.allowIdentityOverride ?? false,
|
|
429
|
+
isMachine(opts),
|
|
429
430
|
)
|
|
430
431
|
} catch (e) {
|
|
431
432
|
fatal(e, opts)
|
|
@@ -451,11 +452,13 @@ export async function installDirect(
|
|
|
451
452
|
// aliases the host and the pre-install gate never consented to THAT
|
|
452
453
|
// origin (lying or unavailable Publication), say so loudly after the fact.
|
|
453
454
|
if (isIdentityOverride(installed.origin, host) && installed.origin !== consentedOrigin) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
`
|
|
457
|
-
|
|
458
|
-
|
|
455
|
+
if (!isMachine(fmtOpts)) {
|
|
456
|
+
log.warn(
|
|
457
|
+
`Installed origin "${installed.origin}" differs from the serving host "${host}" ` +
|
|
458
|
+
`and was not confirmed before install (the worker Publication was unavailable). ` +
|
|
459
|
+
`Every ${installed.origin}/* call on this instance now routes to ${host}.`,
|
|
460
|
+
)
|
|
461
|
+
}
|
|
459
462
|
}
|
|
460
463
|
},
|
|
461
464
|
})
|
|
@@ -512,20 +515,25 @@ async function ensureIdentityOverrideConsent(
|
|
|
512
515
|
url: string,
|
|
513
516
|
host: string,
|
|
514
517
|
allow: boolean,
|
|
518
|
+
machine: boolean,
|
|
515
519
|
): Promise<string | undefined> {
|
|
516
520
|
const origin = await probeDeclaredOrigin(url)
|
|
517
521
|
if (origin === undefined) {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
522
|
+
if (!machine) {
|
|
523
|
+
log.warn(
|
|
524
|
+
`Could not read a declared origin from ` +
|
|
525
|
+
`${new URL('/.well-known/astrale/domain.json', url).href} — ` +
|
|
526
|
+
`skipping the pre-install identity check (the installed origin is verified after install).`,
|
|
527
|
+
)
|
|
528
|
+
}
|
|
523
529
|
return undefined
|
|
524
530
|
}
|
|
525
531
|
if (!isIdentityOverride(origin, host)) return undefined
|
|
526
532
|
|
|
527
533
|
if (allow) {
|
|
528
|
-
|
|
534
|
+
if (!machine) {
|
|
535
|
+
log.warn(`Identity override consented via --allow-identity-override: ${origin} ← ${host}`)
|
|
536
|
+
}
|
|
529
537
|
return origin
|
|
530
538
|
}
|
|
531
539
|
|
package/src/commands/get.ts
CHANGED
|
@@ -46,7 +46,7 @@ export default {
|
|
|
46
46
|
description: 'Get one canonical node by Path or ID',
|
|
47
47
|
afterHelpText: `
|
|
48
48
|
Behavior:
|
|
49
|
-
Resolves one exact Kernel
|
|
49
|
+
Resolves one exact Kernel Path and prints the canonical Node
|
|
50
50
|
{ id, class, props }. Missing and authorization-masked nodes remain
|
|
51
51
|
intentionally indistinguishable. @self is expanded before dispatch.
|
|
52
52
|
|
|
@@ -45,10 +45,9 @@ Behavior:
|
|
|
45
45
|
its result and stores the same target-bound registration. The callable owns
|
|
46
46
|
authorization; the CLI never receives installed Domain authority.
|
|
47
47
|
|
|
48
|
-
Kernel
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
the returned (issuer, subject) for subsequent calls.
|
|
48
|
+
The Kernel assigns each Node ID; callers do not choose a storage path. The
|
|
49
|
+
CLI binds the key proof to the exact provision fingerprint and target Kernel
|
|
50
|
+
audience, then caches the returned (issuer, subject) for subsequent calls.
|
|
52
51
|
|
|
53
52
|
Example:
|
|
54
53
|
$ astrale identity register alice --class /:accounts.example:class.User \
|
package/src/commands/logs.ts
CHANGED
|
@@ -341,11 +341,8 @@ Behavior:
|
|
|
341
341
|
Calls the public Kernel journal syscall and emits its { records, cursor }
|
|
342
342
|
page. Topic selection is exact or prefix-based; cursors and timestamps are
|
|
343
343
|
opaque strings owned by the journal backend. --follow reuses one Client Session
|
|
344
|
-
and advances only with the returned cursor.
|
|
345
|
-
one complete admitted record per line; YAML follow is unsupported.
|
|
346
|
-
|
|
347
|
-
Historical event-glob lowering and the application-specific services-domain
|
|
348
|
-
log buffer are not part of the Kernel V2 journal contract.
|
|
344
|
+
and advances only with the returned cursor. With --json, follow output is
|
|
345
|
+
NDJSON with one complete admitted record per line; YAML follow is unsupported.
|
|
349
346
|
|
|
350
347
|
Examples:
|
|
351
348
|
$ astrale logs -i staging --limit 50
|
|
@@ -4,13 +4,23 @@ import type { CommandDefinition } from '../../program/index'
|
|
|
4
4
|
|
|
5
5
|
import { log } from '../../lib/log'
|
|
6
6
|
import { analyzeSession } from '../../telemetry/analyze'
|
|
7
|
-
import {
|
|
7
|
+
import { sweepStore } from '../../telemetry/retention'
|
|
8
|
+
import { scanSessions } from '../../telemetry/store'
|
|
8
9
|
import { restampLock, releaseLock } from '../../telemetry/trigger'
|
|
9
10
|
|
|
10
11
|
export default {
|
|
11
12
|
name: 'analyze',
|
|
12
13
|
description: 'Analyze a recorded session for DX frictions (dry-run: writes report.md)',
|
|
13
|
-
arguments: [
|
|
14
|
+
arguments: [
|
|
15
|
+
// Optional: the action resolves the most recent closed, unanalyzed session
|
|
16
|
+
// when no id is given, which is how a human is meant to invoke this. The
|
|
17
|
+
// opportunistic trigger always passes an explicit id.
|
|
18
|
+
{
|
|
19
|
+
name: 'id',
|
|
20
|
+
description: 'Session id (default: most recent closed, unanalyzed)',
|
|
21
|
+
required: false,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
14
24
|
options: [
|
|
15
25
|
{ flags: '--file', description: 'File cleared findings as issues on the admin tracker' },
|
|
16
26
|
{ flags: '--model <model>', description: 'Model for the analyzer pass' },
|
|
@@ -22,10 +32,10 @@ export default {
|
|
|
22
32
|
opts: { file?: boolean; model?: string; force?: boolean; auto?: boolean },
|
|
23
33
|
) => {
|
|
24
34
|
if (opts.auto) restampLock()
|
|
35
|
+
let target = id
|
|
25
36
|
try {
|
|
26
|
-
let target = id
|
|
27
37
|
if (!target) {
|
|
28
|
-
const candidate =
|
|
38
|
+
const candidate = scanSessions().find((s) => s.closed && (opts.force || !s.analyzed))
|
|
29
39
|
if (!candidate) {
|
|
30
40
|
if (!opts.auto) log.dim('Nothing to analyze — no closed, unanalyzed session.')
|
|
31
41
|
return
|
|
@@ -45,6 +55,18 @@ export default {
|
|
|
45
55
|
}
|
|
46
56
|
} finally {
|
|
47
57
|
if (opts.auto) releaseLock()
|
|
58
|
+
// The size bound is enforced here, not on the CLI's critical path:
|
|
59
|
+
// measuring every file of every session is only affordable in this
|
|
60
|
+
// process, and this is precisely where the store just grew. The session
|
|
61
|
+
// just analyzed is protected — it is the freshest evidence on disk.
|
|
62
|
+
try {
|
|
63
|
+
const swept = sweepStore({ protect: new Set(target === undefined ? [] : [target]) })
|
|
64
|
+
if (!opts.auto && swept.removed.length > 0) {
|
|
65
|
+
log.dim(`retention: removed ${swept.removed.length} session(s)`)
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
/* retention must never fail the command that triggered it */
|
|
69
|
+
}
|
|
48
70
|
}
|
|
49
71
|
},
|
|
50
72
|
} satisfies CommandDefinition
|
package/src/commands/update.ts
CHANGED
|
@@ -11,7 +11,12 @@ import {
|
|
|
11
11
|
inDomainProject,
|
|
12
12
|
type SdkOutdated,
|
|
13
13
|
} from '../lib/sdk-deps'
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
checkAstraleSkills,
|
|
16
|
+
type SkillApplyResult,
|
|
17
|
+
type SkillCheckResult,
|
|
18
|
+
syncAstraleSkills,
|
|
19
|
+
} from '../lib/skills'
|
|
15
20
|
import { DEFAULT_UPDATE_CHANNEL, packageManagedUpdateError, updateAstrale } from '../lib/update'
|
|
16
21
|
|
|
17
22
|
type UpdateOpts = RawOutputOpts & {
|
|
@@ -23,26 +28,26 @@ type UpdateOpts = RawOutputOpts & {
|
|
|
23
28
|
yes?: boolean
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
log.
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
log.warn(`
|
|
31
|
+
async function refreshSkills(): Promise<SkillApplyResult> {
|
|
32
|
+
log.step('Ensuring Astrale agent skills are current and healthy')
|
|
33
|
+
const result = await syncAstraleSkills()
|
|
34
|
+
if (result.status === 'unchanged') log.success('Astrale skills already up to date')
|
|
35
|
+
else if (result.status === 'installed') log.success('Astrale skills installed')
|
|
36
|
+
else if (result.status === 'updated') log.success('Astrale skills updated')
|
|
37
|
+
else if (result.status === 'repaired') log.success('Astrale skills repaired and updated')
|
|
38
|
+
return result
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function skillCheckStale(skills: SkillCheckResult): boolean {
|
|
42
|
+
return skills.status === 'update-available' || skills.status === 'repair-needed'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function printSkillCheck(skills: SkillCheckResult): void {
|
|
46
|
+
if (skills.status === 'current') log.success('Astrale skills are up to date')
|
|
47
|
+
else if (skills.status === 'update-available') log.info('Astrale skills update available')
|
|
48
|
+
else if (skills.status === 'repair-needed') log.warn('Astrale skills need repair')
|
|
49
|
+
else if (skills.status === 'unavailable') {
|
|
50
|
+
log.warn(`Could not verify Astrale skills${skills.error ? `: ${skills.error}` : ''}`)
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
|
|
@@ -106,9 +111,8 @@ async function refreshSdkDeps(check: boolean, assumeYes = false): Promise<boolea
|
|
|
106
111
|
* domain-studio polls this on load to drive its "update available" badge. It is
|
|
107
112
|
* unified and NON-THROWING: an explicit package-managed result uses npm release
|
|
108
113
|
* identity, while script-install failures remain script failures in `error`
|
|
109
|
-
* instead of being recategorized. The SDK axis is already best-effort. Skills
|
|
110
|
-
*
|
|
111
|
-
* SDK is the only signal worth surfacing.
|
|
114
|
+
* instead of being recategorized. The SDK axis is already best-effort. Skills
|
|
115
|
+
* report meaningful health/freshness states without exposing installer metadata.
|
|
112
116
|
*/
|
|
113
117
|
export type StaleReport = {
|
|
114
118
|
stale: boolean
|
|
@@ -120,6 +124,7 @@ export type StaleReport = {
|
|
|
120
124
|
channel?: string
|
|
121
125
|
error?: string
|
|
122
126
|
}
|
|
127
|
+
skills: SkillCheckResult
|
|
123
128
|
sdk: { stale: boolean; inProject: boolean; outdated: SdkOutdated[] }
|
|
124
129
|
}
|
|
125
130
|
|
|
@@ -218,7 +223,7 @@ export default {
|
|
|
218
223
|
default: DEFAULT_UPDATE_CHANNEL,
|
|
219
224
|
},
|
|
220
225
|
{ flags: '--version <version>', description: 'Update to an exact version tag' },
|
|
221
|
-
{ flags: '--no-skills', description: 'Skip
|
|
226
|
+
{ flags: '--no-skills', description: 'Skip ensuring the Astrale agent skills' },
|
|
222
227
|
{ flags: '--no-deps', description: 'Skip checking @astrale-os SDK dependency versions' },
|
|
223
228
|
{
|
|
224
229
|
flags: '--yes',
|
|
@@ -231,23 +236,22 @@ Behavior:
|
|
|
231
236
|
Keeps three things current, in order. (1) The CLI binary: updates official
|
|
232
237
|
script installs only — if Astrale was installed by another package manager this
|
|
233
238
|
command refuses so that manager stays in charge; downloads are checksum-verified
|
|
234
|
-
before the binary is replaced. (2) The agent skills:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
239
|
+
before the binary is replaced. (2) The Astrale agent skills: installs every
|
|
240
|
+
top-level skill published from astrale-os/cli main, updates healthy older
|
|
241
|
+
installs, repairs inconsistent installs, and verifies the result before
|
|
242
|
+
reporting success. (3) SDK deps: inside a pnpm domain
|
|
238
243
|
project, proposes any @astrale-os/* dependency with a newer release and, on
|
|
239
244
|
confirm, runs "pnpm update --latest --lockfile-only" (updates package.json AND
|
|
240
245
|
the lockfile, honoring your registry + supply-chain age policy; run "pnpm
|
|
241
246
|
install" to materialize).
|
|
242
247
|
|
|
243
248
|
The default release channel is beta; --channel overrides it for one run.
|
|
244
|
-
--check is a dry run (binary + SDK deps; exit 10 if anything is available) and
|
|
249
|
+
--check is a dry run (binary + skills + SDK deps; exit 10 if anything is available) and
|
|
245
250
|
never writes. With --json it emits a unified staleness report
|
|
246
|
-
({ stale, cli, sdk }) for tooling
|
|
247
|
-
along with an update). --yes applies all three non-interactively (no prompts) and
|
|
251
|
+
({ stale, cli, skills, sdk }) for tooling. --yes applies all three non-interactively and
|
|
248
252
|
is resilient — a binary that can't self-update (package-managed) warns but never
|
|
249
|
-
blocks the skills/deps steps;
|
|
250
|
-
--no-skills / --no-deps skip those
|
|
253
|
+
blocks the skills/deps steps; a skill failure fails the command rather than
|
|
254
|
+
claiming a partial success. --no-skills / --no-deps explicitly skip those axes.
|
|
251
255
|
|
|
252
256
|
Examples:
|
|
253
257
|
$ astrale update
|
|
@@ -262,11 +266,18 @@ Examples:
|
|
|
262
266
|
try {
|
|
263
267
|
// Tooling path: a machine-readable `--check` (e.g. domain-studio's update
|
|
264
268
|
// badge polling `astrale update --check --json`) gets a unified,
|
|
265
|
-
// non-throwing staleness report
|
|
269
|
+
// non-throwing staleness report and exits.
|
|
266
270
|
if (opts.check && isMachine(opts)) {
|
|
267
271
|
const cli = await cliStale(opts)
|
|
272
|
+
const skills: SkillCheckResult =
|
|
273
|
+
opts.skills === false ? { status: 'skipped' } : await checkAstraleSkills()
|
|
268
274
|
const sdk = await sdkStale()
|
|
269
|
-
const report: StaleReport = {
|
|
275
|
+
const report: StaleReport = {
|
|
276
|
+
stale: cli.stale || skillCheckStale(skills) || sdk.stale,
|
|
277
|
+
cli,
|
|
278
|
+
skills,
|
|
279
|
+
sdk,
|
|
280
|
+
}
|
|
270
281
|
output(report, opts)
|
|
271
282
|
if (report.stale) process.exitCode = 10
|
|
272
283
|
return
|
|
@@ -316,9 +327,19 @@ Examples:
|
|
|
316
327
|
log.warn(`CLI self-update skipped: ${error.message}`)
|
|
317
328
|
}
|
|
318
329
|
|
|
319
|
-
// Axis B — agent skills.
|
|
320
|
-
//
|
|
321
|
-
if (
|
|
330
|
+
// Axis B — agent skills. A successful real update guarantees a verified
|
|
331
|
+
// latest cohort; --check remains read-only and reports its status.
|
|
332
|
+
if (opts.skills !== false) {
|
|
333
|
+
if (opts.check) {
|
|
334
|
+
const skills = await checkAstraleSkills()
|
|
335
|
+
printSkillCheck(skills)
|
|
336
|
+
if (skillCheckStale(skills)) anyAvailable = true
|
|
337
|
+
} else {
|
|
338
|
+
await refreshSkills()
|
|
339
|
+
}
|
|
340
|
+
} else if (!opts.check) {
|
|
341
|
+
log.dim(' Astrale skills skipped (--no-skills)')
|
|
342
|
+
}
|
|
322
343
|
|
|
323
344
|
// Axis C — first-party @astrale-os/* deps in the current domain project.
|
|
324
345
|
// Runs on --check too (reports availability); --yes applies without a prompt.
|
|
@@ -328,7 +349,7 @@ Examples:
|
|
|
328
349
|
|
|
329
350
|
if (opts.check && anyAvailable) process.exitCode = 10
|
|
330
351
|
} catch (e) {
|
|
331
|
-
fatal(e)
|
|
352
|
+
fatal(e, opts)
|
|
332
353
|
}
|
|
333
354
|
},
|
|
334
355
|
} satisfies CommandDefinition
|