@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/src/lib/skills.ts CHANGED
@@ -3,62 +3,11 @@ import { homedir } from 'node:os'
3
3
  import { dirname, join } from 'node:path'
4
4
 
5
5
  import { findAgentBrowser } from './browser'
6
- import { run } from './proc'
7
6
 
8
- /**
9
- * Agent skills and the agent-browser tool are owned by the coding-agent harness
10
- * (Claude Code et al.) and by npm — not by this CLI. `astrale setup` and
11
- * `astrale update` only *detect* them and delegate installation to their real
12
- * installers (`npx skills add`, `npm i -g agent-browser`). This module is that
13
- * detection + delegation layer.
14
- */
7
+ export * from './skills/sync'
15
8
 
16
- /** The skill name the agent harness looks up under `<dir>/<name>/SKILL.md`. */
17
- export const ASTRALE_CLI_SKILL = 'astrale-cli'
18
- export const ASTRALE_DOMAIN_SKILL = 'astrale-domain'
19
9
  export const AGENT_BROWSER_SKILL = 'agent-browser'
20
10
 
21
- /**
22
- * Published skill source consumed by `npx skills add`. The public `astrale-os/cli`
23
- * repo hosts BOTH the astrale-cli and astrale-domain skills (under `skills/`), so
24
- * one `npx skills add astrale-os/cli` installs both; address one with
25
- * `astrale-os/cli@astrale-cli` or `astrale-os/cli@astrale-domain`.
26
- */
27
- export const ASTRALE_CLI_SKILL_SOURCE = 'astrale-os/cli'
28
-
29
- /** Human-facing command that installs (or refreshes) both astrale skills, globally. */
30
- export const SKILL_INSTALL_HINT = `npx skills add ${ASTRALE_CLI_SKILL_SOURCE} -g`
31
-
32
- /**
33
- * Install or refresh the astrale agent skills by delegating to the skill package
34
- * manager (`npx skills add`). We install GLOBALLY (`-g`, user-level): one run
35
- * equips every project on the machine — the harness resolves the skills from the
36
- * user Claude dir (see {@link detectSkill}) regardless of cwd, and installs land
37
- * in `~/.agents/skills` rather than clobbering any project's own `.agents/skills`
38
- * (notably this repo's symlinked source). We don't reimplement skill installation
39
- * — re-running the real installer is also how an existing install updates to the
40
- * latest published SKILL.md, so this doubles as the update path.
41
- *
42
- * We CAPTURE the installer's output rather than stream it, and surface it only on
43
- * failure. A global install reports a benign per-agent note for any agent format
44
- * that can't go user-level (e.g. PromptScript: "does not support global skill
45
- * installation") as a "Failed to install N" banner — even though `npx skills`
46
- * still exits 0 and the skills land for every other agent. Streaming that made a
47
- * successful refresh look broken; suppressing it on success lets the caller print
48
- * a clean line. Resolves true on exit 0. Requires Node/`npx` on PATH + network.
49
- */
50
- export async function installSkills(): Promise<boolean> {
51
- const { code, stdout, stderr } = await run('npx', [
52
- 'skills',
53
- 'add',
54
- ASTRALE_CLI_SKILL_SOURCE,
55
- '-g',
56
- '-y',
57
- ])
58
- if (code !== 0) process.stderr.write(stdout + stderr)
59
- return code === 0
60
- }
61
-
62
11
  /**
63
12
  * The directories the agent harness ACTUALLY loads skills from, in resolution
64
13
  * order: a project `.claude/skills` found by walking UP from cwd (the harness
@@ -195,7 +195,7 @@ describe('program composition', () => {
195
195
  'whoami',
196
196
  ])
197
197
  expect(createHash('sha256').update(JSON.stringify(surface)).digest('hex')).toBe(
198
- '913d37eab2f603cc745bd9a1c10763a57bc9c2425d0181958d458b80e4a451d7',
198
+ '2e26ce7e4af960b3ec51401e2c439d1d54c13e06de7afda707aa65e8984bb37b',
199
199
  )
200
200
  })
201
201
 
@@ -1,59 +1,61 @@
1
1
  import type { SetupStep } from '../types'
2
2
 
3
3
  import { log } from '../../lib/log'
4
- import {
5
- ASTRALE_CLI_SKILL,
6
- ASTRALE_DOMAIN_SKILL,
7
- detectSkill,
8
- installSkills,
9
- SKILL_INSTALL_HINT,
10
- } from '../../lib/skills'
4
+ import { checkAstraleSkills, SKILL_INSTALL_HINT, syncAstraleSkills } from '../../lib/skills'
11
5
 
12
6
  const FIX = SKILL_INSTALL_HINT
13
7
 
14
8
  /**
15
- * Equip — the astrale agent skills. Both the astrale-cli (ops) and astrale-domain
16
- * (authoring) skills publish from the one public `astrale-os/cli` repo, so a
17
- * single `npx skills add astrale-os/cli -g` installs both, globally. We don't
18
- * reimplement skill installation; we detect presence and delegate to `npx skills
19
- * add` (the skill package manager). A global (user-level) install equips every
20
- * project on the machine, so a freshly scaffolded domain has the skills without
21
- * shipping its own copy.
9
+ * Equip — reconcile every top-level Astrale-owned skill from astrale-os/cli main.
10
+ * Setup and update deliberately share one owner, so an existence-only setup check
11
+ * can never bless an incomplete or stale global cohort.
22
12
  */
23
- const bothInstalled = (): boolean =>
24
- detectSkill(ASTRALE_CLI_SKILL).installed && detectSkill(ASTRALE_DOMAIN_SKILL).installed
25
-
26
13
  export const skillsStep: SetupStep = {
27
14
  id: 'skills',
28
- title: 'astrale agent skills (cli + domain)',
15
+ title: 'Astrale agent skills',
29
16
  group: 'equip',
30
17
 
31
18
  async detect() {
32
- if (bothInstalled()) {
33
- return { state: 'satisfied', summary: 'astrale-cli + astrale-domain skills installed' }
19
+ const result = await checkAstraleSkills()
20
+ if (result.status === 'current') {
21
+ return { state: 'satisfied', summary: 'Astrale skills are installed and up to date' }
22
+ }
23
+ if (result.status === 'unavailable') {
24
+ return {
25
+ state: 'broken',
26
+ summary: 'Astrale skills could not be verified',
27
+ detail: result.error,
28
+ fixHint: FIX,
29
+ }
30
+ }
31
+ return {
32
+ state: 'gap',
33
+ summary:
34
+ result.status === 'repair-needed'
35
+ ? 'Astrale skills need repair'
36
+ : 'Astrale skills need installation or update',
37
+ fixHint: FIX,
34
38
  }
35
- const have = detectSkill(ASTRALE_CLI_SKILL).installed
36
- ? 'astrale-domain'
37
- : 'astrale agent skills'
38
- return { state: 'gap', summary: `${have} not installed`, fixHint: FIX }
39
39
  },
40
40
 
41
41
  async ensure() {
42
- if (bothInstalled()) {
43
- log.success('astrale-cli + astrale-domain skills already installed')
44
- return 'unchanged'
45
- }
46
-
47
- log.step(`Installing the astrale agent skills — ${FIX}`)
48
- if (!(await installSkills())) {
49
- log.warn(`Skill install did not complete — run it later: ${FIX}`)
42
+ log.step('Ensuring Astrale agent skills are current and healthy')
43
+ try {
44
+ const result = await syncAstraleSkills()
45
+ if (result.status === 'unchanged') {
46
+ log.success('Astrale skills already up to date')
47
+ return 'unchanged'
48
+ }
49
+ if (result.status === 'installed') log.success('Astrale skills installed')
50
+ else if (result.status === 'updated') log.success('Astrale skills updated')
51
+ else if (result.status === 'repaired') log.success('Astrale skills repaired and updated')
52
+ return 'fixed'
53
+ } catch (error) {
54
+ log.warn(
55
+ `Astrale skills could not be installed safely${error instanceof Error ? `: ${error.message}` : ''}`,
56
+ )
57
+ log.dim(` Retry: ${FIX}`)
50
58
  return 'failed'
51
59
  }
52
- if (bothInstalled()) {
53
- log.success('astrale-cli + astrale-domain skills installed')
54
- } else {
55
- log.success('astrale-cli skill installed')
56
- }
57
- return 'fixed'
58
60
  },
59
61
  }