@astrale-os/cli 1.0.0-beta.26 → 1.0.0-beta.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/cli",
3
- "version": "1.0.0-beta.26",
3
+ "version": "1.0.0-beta.27",
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
  }
@@ -11,7 +11,12 @@ import {
11
11
  inDomainProject,
12
12
  type SdkOutdated,
13
13
  } from '../lib/sdk-deps'
14
- import { ASTRALE_CLI_SKILL, detectSkill, installSkills, SKILL_INSTALL_HINT } from '../lib/skills'
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
- * Refresh the astrale agent skills alongside the binary. Delegates to the same
28
- * `npx skills add` installer `astrale setup` uses; re-running it updates an
29
- * existing install to the latest published SKILL.md.
30
- *
31
- * Only refreshes skills the user already has — `astrale update` keeps an existing
32
- * install current, it does not foist skills on a non-agent user; fresh installs
33
- * go through `astrale setup`. Best-effort: a failure here never fails the update
34
- * (the binary is already swapped), so we warn and move on.
35
- */
36
- async function refreshSkills(): Promise<void> {
37
- if (!detectSkill(ASTRALE_CLI_SKILL).installed) {
38
- log.dim(` Agent skills not installed — get them with: ${SKILL_INSTALL_HINT}`)
39
- return
40
- }
41
- log.step(`Refreshing the astrale agent skills — ${SKILL_INSTALL_HINT}`)
42
- if (await installSkills()) {
43
- log.success('astrale agent skills up to date')
44
- } else {
45
- log.warn(`Skill refresh did not complete run it later: ${SKILL_INSTALL_HINT}`)
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 are
110
- * intentionally absent they ride along with `astrale update`, so a stale CLI or
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 refreshing the astrale agent skills' },
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: if the astrale skills (cli +
235
- domain) are already installed, refreshes them to the latest by delegating to
236
- "npx skills add astrale-os/cli -g" the same installer "astrale setup" uses;
237
- fresh installs go through "astrale setup". (3) SDK deps: inside a pnpm domain
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 non-throwing, skills omitted (they ride
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; this is what domain-studio's "Update now" runs.
250
- --no-skills / --no-deps skip those steps in a real run.
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 — CLI + SDK only — and exits.
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 = { stale: cli.stale || sdk.stale, cli, sdk }
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. Keep an existing install current with the CLI.
320
- // Skip on --check (a dry run) and when the user opted out with --no-skills.
321
- if (!opts.check && opts.skills !== false) await refreshSkills()
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