@laitszkin/apollo-toolkit 3.2.2 → 3.3.1

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.
@@ -1,6 +1,6 @@
1
1
  param(
2
2
  [Parameter(ValueFromRemainingArguments = $true)]
3
- [string[]]$Modes
3
+ [string[]]$RawArgs
4
4
  )
5
5
 
6
6
  Set-StrictMode -Version Latest
@@ -9,7 +9,8 @@ $ErrorActionPreference = "Stop"
9
9
  function Show-Usage {
10
10
  @"
11
11
  Usage:
12
- ./scripts/install_skills.ps1 [codex|openclaw|trae|agents|claude-code|all]...
12
+ ./scripts/install_skills.ps1 [install] [codex|openclaw|trae|agents|claude-code|all]...
13
+ ./scripts/install_skills.ps1 uninstall [codex|openclaw|trae|agents|claude-code|all]...
13
14
 
14
15
  Modes:
15
16
  codex Copy skills into ~/.codex/skills (includes ./codex/ agent-specific skills)
@@ -19,6 +20,10 @@ Modes:
19
20
  claude-code Copy skills into ~/.claude/skills
20
21
  all Install all supported targets
21
22
 
23
+ Options:
24
+ --symlink Install skills as symlinks (recommended; auto-update via git pull)
25
+ --copy Install skills as file copies (manual reinstall for updates)
26
+
22
27
  Optional environment overrides:
23
28
  CODEX_SKILLS_DIR Override codex skills destination path
24
29
  OPENCLAW_HOME Override openclaw home path
@@ -30,7 +35,8 @@ Optional environment overrides:
30
35
  "@
31
36
  }
32
37
 
33
- $ToolkitRepoUrl = if ($env:APOLLO_TOOLKIT_REPO_URL) { $env:APOLLO_TOOLKIT_REPO_URL } else { "https://github.com/LaiTszKin/apollo-toolkit.git" }
38
+ $Script:ToolkitRepoUrl = if ($env:APOLLO_TOOLKIT_REPO_URL) { $env:APOLLO_TOOLKIT_REPO_URL } else { "https://github.com/LaiTszKin/apollo-toolkit.git" }
39
+ $Script:ManifestFilename = ".apollo-toolkit-manifest.json"
34
40
 
35
41
  function Expand-UserPath {
36
42
  param([string]$Path)
@@ -51,7 +57,7 @@ function Expand-UserPath {
51
57
  return $Path
52
58
  }
53
59
 
54
- $ToolkitHome = if ($env:APOLLO_TOOLKIT_HOME) { Expand-UserPath $env:APOLLO_TOOLKIT_HOME } else { Join-Path $HOME ".apollo-toolkit" }
60
+ $Script:ToolkitHome = if ($env:APOLLO_TOOLKIT_HOME) { Expand-UserPath $env:APOLLO_TOOLKIT_HOME } else { Join-Path $HOME ".apollo-toolkit" }
55
61
 
56
62
  function Show-Banner {
57
63
  @"
@@ -80,40 +86,43 @@ function Test-RepoRoot {
80
86
  }
81
87
 
82
88
  function Bootstrap-RepoIfNeeded {
83
- if (Test-Path -LiteralPath (Join-Path $ToolkitHome ".git") -PathType Container) {
84
- git -C $ToolkitHome pull --ff-only | Out-Null
89
+ if (Test-Path -LiteralPath (Join-Path $Script:ToolkitHome ".git") -PathType Container) {
90
+ git -C $Script:ToolkitHome pull --ff-only | Out-Null
85
91
  }
86
92
  else {
87
- if (Test-Path -LiteralPath $ToolkitHome) {
88
- Remove-Item -LiteralPath $ToolkitHome -Force -Recurse
93
+ if (Test-Path -LiteralPath $Script:ToolkitHome) {
94
+ Remove-Item -LiteralPath $Script:ToolkitHome -Force -Recurse
89
95
  }
90
- git clone --depth 1 $ToolkitRepoUrl $ToolkitHome | Out-Null
96
+ git clone --depth 1 $Script:ToolkitRepoUrl $Script:ToolkitHome | Out-Null
91
97
  }
92
98
  }
93
99
 
94
- $scriptPath = $MyInvocation.MyCommand.Path
100
+ $Script:LinkMode = ""
101
+ $Script:ScriptPath = $MyInvocation.MyCommand.Path
95
102
  $candidateRepoRoot = $null
96
103
 
97
- if (-not [string]::IsNullOrWhiteSpace($scriptPath)) {
98
- $scriptDir = Split-Path -Parent $scriptPath
104
+ if (-not [string]::IsNullOrWhiteSpace($Script:ScriptPath)) {
105
+ $scriptDir = Split-Path -Parent $Script:ScriptPath
99
106
  $candidateRepoRoot = Split-Path -Parent $scriptDir
100
107
  }
101
108
 
102
109
  if (Test-RepoRoot -Path $candidateRepoRoot) {
103
- $RepoRoot = $candidateRepoRoot
110
+ $Script:RepoRoot = $candidateRepoRoot
104
111
  }
105
112
  elseif (Test-RepoRoot -Path (Get-Location).Path) {
106
- $RepoRoot = (Get-Location).Path
113
+ $Script:RepoRoot = (Get-Location).Path
107
114
  }
108
115
  else {
109
116
  Bootstrap-RepoIfNeeded
110
- $RepoRoot = $ToolkitHome
117
+ $Script:RepoRoot = $Script:ToolkitHome
111
118
  }
112
119
 
120
+ # ---- Skill collection ----
121
+
113
122
  function Get-SkillPathGroups {
114
123
  param([string[]]$SelectedModes)
115
124
 
116
- $dirs = Get-ChildItem -Path $RepoRoot -Directory | Sort-Object Name
125
+ $dirs = Get-ChildItem -Path $Script:RepoRoot -Directory | Sort-Object Name
117
126
  $sharedSkills = @()
118
127
  $codexSkills = @()
119
128
 
@@ -123,9 +132,8 @@ function Get-SkillPathGroups {
123
132
  }
124
133
  }
125
134
 
126
- # For codex mode, also include codex-specific skills
127
135
  if ($SelectedModes -contains "codex") {
128
- $codexDir = Join-Path $RepoRoot "codex"
136
+ $codexDir = Join-Path $Script:RepoRoot "codex"
129
137
  if (Test-Path -LiteralPath $codexDir -PathType Container) {
130
138
  $codexDirs = Get-ChildItem -Path $codexDir -Directory | Sort-Object Name
131
139
  foreach ($dir in $codexDirs) {
@@ -137,102 +145,84 @@ function Get-SkillPathGroups {
137
145
  }
138
146
 
139
147
  if ($sharedSkills.Count -eq 0) {
140
- throw "No skill folders found in: $RepoRoot"
148
+ throw "No skill folders found in: $($Script:RepoRoot)"
141
149
  }
142
150
 
143
151
  [PSCustomObject]@{
144
152
  Shared = $sharedSkills
145
- Codex = $codexSkills
153
+ Codex = $codexSkills
146
154
  }
147
155
  }
148
156
 
149
- function Add-ModeOnce {
150
- param(
151
- [System.Collections.Generic.List[string]]$Selected,
152
- [string]$Mode
153
- )
157
+ function Get-SkillNames {
158
+ param([string[]]$Paths)
154
159
 
155
- if (-not $Selected.Contains($Mode)) {
156
- $Selected.Add($Mode)
160
+ $names = @()
161
+ foreach ($p in $Paths) {
162
+ $names += Split-Path -Path $p -Leaf
157
163
  }
164
+ return ($names | Sort-Object -Unique)
158
165
  }
159
166
 
160
- function Resolve-Modes {
161
- param([string[]]$Requested)
167
+ # ---- Manifest management ----
162
168
 
163
- $selected = [System.Collections.Generic.List[string]]::new()
169
+ function Read-ManifestSkills {
170
+ param([string]$TargetRoot)
164
171
 
165
- if ($Requested.Count -eq 0) {
166
- Show-Banner
167
- Write-Host ""
168
- Write-Host "Select install options (comma-separated):"
169
- Write-Host "1) codex (~/.codex/skills, includes ./codex/ agent-specific skills)"
170
- Write-Host "2) openclaw (~/.openclaw/workspace*/skills)"
171
- Write-Host "3) trae (~/.trae/skills)"
172
- Write-Host "4) agents (~/.agents/skills)"
173
- Write-Host "5) claude-code (~/.claude/skills)"
174
- Write-Host "6) all"
175
- $inputValue = Read-Host "Enter choice(s) [1-6]"
172
+ $manifestPath = Join-Path $TargetRoot $Script:ManifestFilename
173
+ if (-not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) {
174
+ return @()
175
+ }
176
176
 
177
- foreach ($rawChoice in ($inputValue -split ",")) {
178
- $choice = $rawChoice.Trim()
179
- switch ($choice) {
180
- "1" { Add-ModeOnce -Selected $selected -Mode "codex" }
181
- "2" { Add-ModeOnce -Selected $selected -Mode "openclaw" }
182
- "3" { Add-ModeOnce -Selected $selected -Mode "trae" }
183
- "4" { Add-ModeOnce -Selected $selected -Mode "agents" }
184
- "5" { Add-ModeOnce -Selected $selected -Mode "claude-code" }
185
- "6" {
186
- Add-ModeOnce -Selected $selected -Mode "codex"
187
- Add-ModeOnce -Selected $selected -Mode "openclaw"
188
- Add-ModeOnce -Selected $selected -Mode "trae"
189
- Add-ModeOnce -Selected $selected -Mode "agents"
190
- Add-ModeOnce -Selected $selected -Mode "claude-code"
191
- }
192
- default {
193
- throw "Invalid choice: $choice"
194
- }
195
- }
177
+ try {
178
+ $manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
179
+ $skills = @()
180
+ if ($manifest.historicalSkills) {
181
+ $skills += $manifest.historicalSkills
196
182
  }
197
- }
198
- else {
199
- foreach ($mode in $Requested) {
200
- switch ($mode.ToLowerInvariant()) {
201
- "codex" { Add-ModeOnce -Selected $selected -Mode "codex" }
202
- "openclaw" { Add-ModeOnce -Selected $selected -Mode "openclaw" }
203
- "trae" { Add-ModeOnce -Selected $selected -Mode "trae" }
204
- "agents" { Add-ModeOnce -Selected $selected -Mode "agents" }
205
- "claude-code" { Add-ModeOnce -Selected $selected -Mode "claude-code" }
206
- "all" {
207
- Add-ModeOnce -Selected $selected -Mode "codex"
208
- Add-ModeOnce -Selected $selected -Mode "openclaw"
209
- Add-ModeOnce -Selected $selected -Mode "trae"
210
- Add-ModeOnce -Selected $selected -Mode "agents"
211
- Add-ModeOnce -Selected $selected -Mode "claude-code"
212
- }
213
- default {
214
- Show-Usage
215
- throw "Invalid mode: $mode"
216
- }
217
- }
183
+ if ($manifest.skills) {
184
+ $skills += $manifest.skills
218
185
  }
186
+ return ($skills | Sort-Object -Unique)
219
187
  }
220
-
221
- if ($selected.Count -eq 0) {
222
- throw "No install option selected."
188
+ catch {
189
+ return @()
223
190
  }
224
-
225
- return $selected
226
191
  }
227
192
 
228
- function Remove-PathForce {
229
- param([string]$Target)
193
+ function Write-Manifest {
194
+ param(
195
+ [string]$TargetRoot,
196
+ [string]$Version,
197
+ [string]$LinkMode,
198
+ [string[]]$SkillNames
199
+ )
230
200
 
231
- if (Test-Path -LiteralPath $Target) {
232
- Remove-Item -LiteralPath $Target -Force -Recurse
201
+ $manifestPath = Join-Path $TargetRoot $Script:ManifestFilename
202
+ $historicalSkills = @()
203
+ if (Test-Path -LiteralPath $manifestPath -PathType Leaf) {
204
+ $historicalSkills = Read-ManifestSkills -TargetRoot $TargetRoot
233
205
  }
206
+
207
+ $merged = @()
208
+ $merged += $historicalSkills
209
+ $merged += $SkillNames
210
+ $allSkills = ($merged | Sort-Object -Unique)
211
+
212
+ $manifest = [PSCustomObject]@{
213
+ version = $Version
214
+ installedAt = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ")
215
+ linkMode = $LinkMode
216
+ skills = ($SkillNames | Sort-Object -Unique)
217
+ historicalSkills = $allSkills
218
+ }
219
+
220
+ New-Item -ItemType Directory -Path $TargetRoot -Force | Out-Null
221
+ $manifest | ConvertTo-Json -Depth 3 | Set-Content -LiteralPath $manifestPath -Encoding UTF8
234
222
  }
235
223
 
224
+ # ---- Install operations ----
225
+
236
226
  function Copy-Skill {
237
227
  param(
238
228
  [string]$Source,
@@ -249,6 +239,171 @@ function Copy-Skill {
249
239
  Write-Host "[copied] $Source -> $target"
250
240
  }
251
241
 
242
+ function Symlink-Skill {
243
+ param(
244
+ [string]$Source,
245
+ [string]$TargetRoot
246
+ )
247
+
248
+ $name = Split-Path -Path $Source -Leaf
249
+ $target = Join-Path $TargetRoot $name
250
+
251
+ New-Item -ItemType Directory -Path $TargetRoot -Force | Out-Null
252
+ Remove-PathForce -Target $target
253
+
254
+ New-Item -ItemType SymbolicLink -Path $target -Target $Source -Force | Out-Null
255
+ Write-Host "[symlink] $Source -> $target"
256
+ }
257
+
258
+ function Do-Replace {
259
+ param(
260
+ [string]$Source,
261
+ [string]$TargetRoot
262
+ )
263
+
264
+ if ($Script:LinkMode -eq "symlink") {
265
+ Symlink-Skill -Source $Source -TargetRoot $TargetRoot
266
+ }
267
+ else {
268
+ Copy-Skill -Source $Source -TargetRoot $TargetRoot
269
+ }
270
+ }
271
+
272
+ function Remove-PathForce {
273
+ param([string]$Target)
274
+
275
+ if (Test-Path -LiteralPath $Target) {
276
+ Remove-Item -LiteralPath $Target -Force -Recurse
277
+ }
278
+ }
279
+
280
+ # ---- Uninstall operations ----
281
+
282
+ function Uninstall-Target {
283
+ param(
284
+ [string]$TargetRoot,
285
+ [string]$TargetLabel
286
+ )
287
+
288
+ $manifestPath = Join-Path $TargetRoot $Script:ManifestFilename
289
+ if (-not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) {
290
+ Write-Host "[skip] No manifest found in: $TargetRoot"
291
+ return
292
+ }
293
+
294
+ $skills = Read-ManifestSkills -TargetRoot $TargetRoot
295
+ if ($skills.Count -eq 0) {
296
+ Write-Host "[skip] No skills in manifest: $TargetRoot"
297
+ Remove-Item -LiteralPath $manifestPath -Force -ErrorAction SilentlyContinue
298
+ return
299
+ }
300
+
301
+ Write-Host "Uninstalling from $($TargetLabel): $TargetRoot"
302
+ foreach ($name in $skills) {
303
+ $skillPath = Join-Path $TargetRoot $name
304
+ if (Test-Path -LiteralPath $skillPath) {
305
+ Remove-Item -LiteralPath $skillPath -Force -Recurse
306
+ Write-Host " [removed] $skillPath"
307
+ }
308
+ }
309
+
310
+ Remove-Item -LiteralPath $manifestPath -Force -ErrorAction SilentlyContinue
311
+ Write-Host " [removed manifest] $manifestPath"
312
+ }
313
+
314
+ function Invoke-Uninstall {
315
+ param([string[]]$SelectedModes)
316
+
317
+ if ($SelectedModes.Count -eq 0) {
318
+ $SelectedModes = @("codex", "openclaw", "trae", "agents", "claude-code")
319
+ }
320
+
321
+ Write-Host "Uninstalling Apollo Toolkit skills..."
322
+ Write-Host "Target modes: $($SelectedModes -join ', ')"
323
+ Write-Host ""
324
+
325
+ # Show all known skills
326
+ $skillGroups = Get-SkillPathGroups -SelectedModes $SelectedModes
327
+ $currentNames = Get-SkillNames -Paths ($skillGroups.Shared + $skillGroups.Codex)
328
+ $allNames = @()
329
+ $allNames += $currentNames
330
+
331
+ # Collect historical from manifests
332
+ $targetDirs = @()
333
+ foreach ($m in $SelectedModes) {
334
+ switch ($m) {
335
+ "codex" {
336
+ $d = if ($env:CODEX_SKILLS_DIR) { Expand-UserPath $env:CODEX_SKILLS_DIR } else { Join-Path $HOME ".codex/skills" }
337
+ $targetDirs += $d
338
+ }
339
+ "openclaw" {
340
+ $oh = if ($env:OPENCLAW_HOME) { Expand-UserPath $env:OPENCLAW_HOME } else { Join-Path $HOME ".openclaw" }
341
+ if (Test-Path -LiteralPath $oh) {
342
+ foreach ($ws in (Get-ChildItem -Path $oh -Directory -Filter "workspace*")) {
343
+ $targetDirs += (Join-Path $ws.FullName "skills")
344
+ }
345
+ }
346
+ }
347
+ "trae" {
348
+ $d = if ($env:TRAE_SKILLS_DIR) { Expand-UserPath $env:TRAE_SKILLS_DIR } else { Join-Path $HOME ".trae/skills" }
349
+ $targetDirs += $d
350
+ }
351
+ "agents" {
352
+ $d = if ($env:AGENTS_SKILLS_DIR) { Expand-UserPath $env:AGENTS_SKILLS_DIR } else { Join-Path $HOME ".agents/skills" }
353
+ $targetDirs += $d
354
+ }
355
+ "claude-code" {
356
+ $d = if ($env:CLAUDE_CODE_SKILLS_DIR) { Expand-UserPath $env:CLAUDE_CODE_SKILLS_DIR } else { Join-Path $HOME ".claude/skills" }
357
+ $targetDirs += $d
358
+ }
359
+ }
360
+ }
361
+
362
+ foreach ($td in $targetDirs) {
363
+ $allNames += (Read-ManifestSkills -TargetRoot $td)
364
+ }
365
+
366
+ $allKnown = ($allNames | Sort-Object -Unique)
367
+ Write-Host "All known skills (current + historical):"
368
+ foreach ($n in $allKnown) {
369
+ Write-Host " - $n"
370
+ }
371
+ Write-Host ""
372
+
373
+ foreach ($m in $SelectedModes) {
374
+ switch ($m) {
375
+ "codex" {
376
+ $d = if ($env:CODEX_SKILLS_DIR) { Expand-UserPath $env:CODEX_SKILLS_DIR } else { Join-Path $HOME ".codex/skills" }
377
+ Uninstall-Target -TargetRoot $d -TargetLabel "codex"
378
+ }
379
+ "trae" {
380
+ $d = if ($env:TRAE_SKILLS_DIR) { Expand-UserPath $env:TRAE_SKILLS_DIR } else { Join-Path $HOME ".trae/skills" }
381
+ Uninstall-Target -TargetRoot $d -TargetLabel "trae"
382
+ }
383
+ "agents" {
384
+ $d = if ($env:AGENTS_SKILLS_DIR) { Expand-UserPath $env:AGENTS_SKILLS_DIR } else { Join-Path $HOME ".agents/skills" }
385
+ Uninstall-Target -TargetRoot $d -TargetLabel "agents"
386
+ }
387
+ "claude-code" {
388
+ $d = if ($env:CLAUDE_CODE_SKILLS_DIR) { Expand-UserPath $env:CLAUDE_CODE_SKILLS_DIR } else { Join-Path $HOME ".claude/skills" }
389
+ Uninstall-Target -TargetRoot $d -TargetLabel "claude-code"
390
+ }
391
+ "openclaw" {
392
+ $oh = if ($env:OPENCLAW_HOME) { Expand-UserPath $env:OPENCLAW_HOME } else { Join-Path $HOME ".openclaw" }
393
+ if (Test-Path -LiteralPath $oh) {
394
+ foreach ($ws in (Get-ChildItem -Path $oh -Directory -Filter "workspace*")) {
395
+ Uninstall-Target -TargetRoot (Join-Path $ws.FullName "skills") -TargetLabel "openclaw"
396
+ }
397
+ }
398
+ }
399
+ }
400
+ }
401
+
402
+ Write-Host "Done."
403
+ }
404
+
405
+ # ---- Install functions ----
406
+
252
407
  function Install-Codex {
253
408
  param([string[]]$SkillPaths)
254
409
 
@@ -259,10 +414,13 @@ function Install-Codex {
259
414
  Join-Path $HOME ".codex/skills"
260
415
  }
261
416
 
262
- Write-Host "Installing to codex: $target"
417
+ Write-Host "Installing to codex: $target (mode: $Script:LinkMode)"
418
+ $skillNames = @()
263
419
  foreach ($src in $SkillPaths) {
264
- Copy-Skill -Source $src -TargetRoot $target
420
+ Do-Replace -Source $src -TargetRoot $target
421
+ $skillNames += (Split-Path -Path $src -Leaf)
265
422
  }
423
+ Write-Manifest -TargetRoot $target -Version $Script:Version -LinkMode $Script:LinkMode -SkillNames $skillNames
266
424
  }
267
425
 
268
426
  function Install-OpenClaw {
@@ -286,10 +444,13 @@ function Install-OpenClaw {
286
444
 
287
445
  foreach ($workspace in $workspaces) {
288
446
  $skillsDir = Join-Path $workspace.FullName "skills"
289
- Write-Host "Installing to openclaw workspace: $skillsDir"
447
+ Write-Host "Installing to openclaw workspace: $skillsDir (mode: $Script:LinkMode)"
448
+ $skillNames = @()
290
449
  foreach ($src in $SkillPaths) {
291
- Copy-Skill -Source $src -TargetRoot $skillsDir
450
+ Do-Replace -Source $src -TargetRoot $skillsDir
451
+ $skillNames += (Split-Path -Path $src -Leaf)
292
452
  }
453
+ Write-Manifest -TargetRoot $skillsDir -Version $Script:Version -LinkMode $Script:LinkMode -SkillNames $skillNames
293
454
  }
294
455
  }
295
456
 
@@ -303,10 +464,13 @@ function Install-Trae {
303
464
  Join-Path $HOME ".trae/skills"
304
465
  }
305
466
 
306
- Write-Host "Installing to trae: $target"
467
+ Write-Host "Installing to trae: $target (mode: $Script:LinkMode)"
468
+ $skillNames = @()
307
469
  foreach ($src in $SkillPaths) {
308
- Copy-Skill -Source $src -TargetRoot $target
470
+ Do-Replace -Source $src -TargetRoot $target
471
+ $skillNames += (Split-Path -Path $src -Leaf)
309
472
  }
473
+ Write-Manifest -TargetRoot $target -Version $Script:Version -LinkMode $Script:LinkMode -SkillNames $skillNames
310
474
  }
311
475
 
312
476
  function Install-Agents {
@@ -319,10 +483,13 @@ function Install-Agents {
319
483
  Join-Path $HOME ".agents/skills"
320
484
  }
321
485
 
322
- Write-Host "Installing to agents: $target"
486
+ Write-Host "Installing to agents: $target (mode: $Script:LinkMode)"
487
+ $skillNames = @()
323
488
  foreach ($src in $SkillPaths) {
324
- Copy-Skill -Source $src -TargetRoot $target
489
+ Do-Replace -Source $src -TargetRoot $target
490
+ $skillNames += (Split-Path -Path $src -Leaf)
325
491
  }
492
+ Write-Manifest -TargetRoot $target -Version $Script:Version -LinkMode $Script:LinkMode -SkillNames $skillNames
326
493
  }
327
494
 
328
495
  function Install-ClaudeCode {
@@ -335,27 +502,240 @@ function Install-ClaudeCode {
335
502
  Join-Path $HOME ".claude/skills"
336
503
  }
337
504
 
338
- Write-Host "Installing to claude-code: $target"
505
+ Write-Host "Installing to claude-code: $target (mode: $Script:LinkMode)"
506
+ $skillNames = @()
339
507
  foreach ($src in $SkillPaths) {
340
- Copy-Skill -Source $src -TargetRoot $target
508
+ Do-Replace -Source $src -TargetRoot $target
509
+ $skillNames += (Split-Path -Path $src -Leaf)
510
+ }
511
+ Write-Manifest -TargetRoot $target -Version $Script:Version -LinkMode $Script:LinkMode -SkillNames $skillNames
512
+ }
513
+
514
+ # ---- Mode management ----
515
+
516
+ function Add-ModeOnce {
517
+ param(
518
+ [System.Collections.Generic.List[string]]$Selected,
519
+ [string]$Mode
520
+ )
521
+
522
+ if (-not $Selected.Contains($Mode)) {
523
+ $Selected.Add($Mode)
341
524
  }
342
525
  }
343
526
 
344
- if ($Modes.Count -gt 0 -and ($Modes[0] -eq "-h" -or $Modes[0] -eq "--help")) {
345
- Show-Usage
527
+ function Resolve-Modes {
528
+ param([string[]]$Requested)
529
+
530
+ $selected = [System.Collections.Generic.List[string]]::new()
531
+
532
+ if ($Requested.Count -eq 0) {
533
+ Show-Banner
534
+ Write-Host ""
535
+ Write-Host "Select install options (comma-separated):"
536
+ Write-Host "1) codex (~/.codex/skills, includes ./codex/ agent-specific skills)"
537
+ Write-Host "2) openclaw (~/.openclaw/workspace*/skills)"
538
+ Write-Host "3) trae (~/.trae/skills)"
539
+ Write-Host "4) agents (~/.agents/skills)"
540
+ Write-Host "5) claude-code (~/.claude/skills)"
541
+ Write-Host "6) all"
542
+ $inputValue = Read-Host "Enter choice(s) [1-6]"
543
+
544
+ foreach ($rawChoice in ($inputValue -split ",")) {
545
+ $choice = $rawChoice.Trim()
546
+ switch ($choice) {
547
+ "1" { Add-ModeOnce -Selected $selected -Mode "codex" }
548
+ "2" { Add-ModeOnce -Selected $selected -Mode "openclaw" }
549
+ "3" { Add-ModeOnce -Selected $selected -Mode "trae" }
550
+ "4" { Add-ModeOnce -Selected $selected -Mode "agents" }
551
+ "5" { Add-ModeOnce -Selected $selected -Mode "claude-code" }
552
+ "6" {
553
+ Add-ModeOnce -Selected $selected -Mode "codex"
554
+ Add-ModeOnce -Selected $selected -Mode "openclaw"
555
+ Add-ModeOnce -Selected $selected -Mode "trae"
556
+ Add-ModeOnce -Selected $selected -Mode "agents"
557
+ Add-ModeOnce -Selected $selected -Mode "claude-code"
558
+ }
559
+ default {
560
+ throw "Invalid choice: $choice"
561
+ }
562
+ }
563
+ }
564
+ }
565
+ else {
566
+ foreach ($mode in $Requested) {
567
+ switch ($mode.ToLowerInvariant()) {
568
+ "codex" { Add-ModeOnce -Selected $selected -Mode "codex" }
569
+ "openclaw" { Add-ModeOnce -Selected $selected -Mode "openclaw" }
570
+ "trae" { Add-ModeOnce -Selected $selected -Mode "trae" }
571
+ "agents" { Add-ModeOnce -Selected $selected -Mode "agents" }
572
+ "claude-code" { Add-ModeOnce -Selected $selected -Mode "claude-code" }
573
+ "all" {
574
+ Add-ModeOnce -Selected $selected -Mode "codex"
575
+ Add-ModeOnce -Selected $selected -Mode "openclaw"
576
+ Add-ModeOnce -Selected $selected -Mode "trae"
577
+ Add-ModeOnce -Selected $selected -Mode "agents"
578
+ Add-ModeOnce -Selected $selected -Mode "claude-code"
579
+ }
580
+ default {
581
+ Show-Usage
582
+ throw "Invalid mode: $mode"
583
+ }
584
+ }
585
+ }
586
+ }
587
+
588
+ if ($selected.Count -eq 0) {
589
+ throw "No install option selected."
590
+ }
591
+
592
+ return $selected
593
+ }
594
+
595
+ function Read-YesNo {
596
+ param(
597
+ [string]$Prompt,
598
+ [bool]$DefaultYes = $true
599
+ )
600
+
601
+ $hint = if ($DefaultYes) { "[Y/n]" } else { "[y/N]" }
602
+ $result = Read-Host "$Prompt $hint"
603
+ if ([string]::IsNullOrWhiteSpace($result)) {
604
+ return $DefaultYes
605
+ }
606
+ $trimmed = $result.Trim().ToLowerInvariant()
607
+ return ($trimmed -eq "y" -or $trimmed -eq "yes")
608
+ }
609
+
610
+ function Prompt-LinkMode {
611
+ Write-Host ""
612
+ Write-Host "Symlink mode:"
613
+ Write-Host " Pro: Skills auto-update when you 'git pull' in ~/.apollo-toolkit"
614
+ Write-Host " Pro: No need to re-run installer after patch updates"
615
+ Write-Host " Con: Changes pushed to the repo automatically reflect in your skills -"
616
+ Write-Host " you may receive updates you did not intend to accept"
617
+ Write-Host ""
618
+
619
+ if (Read-YesNo -Prompt "Install skills as symlinks (recommended)?" -DefaultYes $true) {
620
+ $Script:LinkMode = "symlink"
621
+ }
622
+ else {
623
+ $Script:LinkMode = "copy"
624
+ }
625
+ Write-Host "Using: $Script:LinkMode"
626
+ }
627
+
628
+ function Prompt-IncludeExclusive {
629
+ param(
630
+ [string[]]$SelectedModes,
631
+ [string[]]$CodexSkillPaths
632
+ )
633
+
634
+ if ($CodexSkillPaths.Count -eq 0) {
635
+ return
636
+ }
637
+
638
+ $hasNonCodex = $false
639
+ foreach ($m in $SelectedModes) {
640
+ if ($m -ne "codex") {
641
+ $hasNonCodex = $true
642
+ break
643
+ }
644
+ }
645
+
646
+ if (-not $hasNonCodex) {
647
+ return
648
+ }
649
+
650
+ $codexOnlyNames = @()
651
+ foreach ($cp in $CodexSkillPaths) {
652
+ $codexOnlyNames += (Split-Path -Path $cp -Leaf)
653
+ }
654
+
655
+ Write-Host ""
656
+ Write-Host "Exclusive skills detected:"
657
+ Write-Host " The following skills are exclusive to codex: $($codexOnlyNames -join ', ')"
658
+ Write-Host " Your selected non-codex targets: $(($SelectedModes | Where-Object { $_ -ne 'codex' }) -join ', ')"
659
+
660
+ if (Read-YesNo -Prompt "Install codex-exclusive skills to non-codex targets as well?" -DefaultYes $false) {
661
+ return $true
662
+ }
663
+ return $false
664
+ }
665
+
666
+ # ---- Version detection ----
667
+ $Script:Version = "unknown"
668
+ $pkgJsonPath = Join-Path $Script:RepoRoot "package.json"
669
+ if (Test-Path -LiteralPath $pkgJsonPath -PathType Leaf) {
670
+ try {
671
+ $pkg = Get-Content -LiteralPath $pkgJsonPath -Raw | ConvertFrom-Json
672
+ if ($pkg.version) {
673
+ $Script:Version = $pkg.version
674
+ }
675
+ }
676
+ catch { }
677
+ }
678
+
679
+ # ---- Main ----
680
+
681
+ # Parse arguments
682
+ $Args = @()
683
+ $Command = "install"
684
+ foreach ($arg in $RawArgs) {
685
+ switch ($arg) {
686
+ "-h" { Show-Usage; exit 0 }
687
+ "--help" { Show-Usage; exit 0 }
688
+ "--symlink" { $Script:LinkMode = "symlink" }
689
+ "--copy" { $Script:LinkMode = "copy" }
690
+ default { $Args += $arg }
691
+ }
692
+ }
693
+
694
+ if ($Args.Count -gt 0 -and $Args[0] -eq "uninstall") {
695
+ $Command = "uninstall"
696
+ $Args = $Args[1..($Args.Count - 1)]
697
+ }
698
+ elseif ($Args.Count -gt 0 -and $Args[0] -eq "install") {
699
+ $Args = $Args[1..($Args.Count - 1)]
700
+ }
701
+
702
+ # Resolve modes
703
+ $selectedModes = Resolve-Modes -Requested $Args
704
+
705
+ if ($Command -eq "uninstall") {
706
+ Invoke-Uninstall -SelectedModes $selectedModes
346
707
  exit 0
347
708
  }
348
709
 
349
- $selectedModes = Resolve-Modes -Requested $Modes
710
+ # Install flow
350
711
  $skillPathGroups = Get-SkillPathGroups -SelectedModes $selectedModes
351
712
 
713
+ # Prompt for link mode if not set
714
+ if ([string]::IsNullOrEmpty($Script:LinkMode)) {
715
+ Prompt-LinkMode
716
+ }
717
+
718
+ # Prompt for exclusive skills inclusion
719
+ $includeExclusive = Prompt-IncludeExclusive -SelectedModes $selectedModes -CodexSkillPaths $skillPathGroups.Codex
720
+ $effectiveShared = $skillPathGroups.Shared
721
+ if ($includeExclusive) {
722
+ $effectiveShared += $skillPathGroups.Codex
723
+ }
724
+
725
+ # Summarize and install
726
+ Write-Host ""
727
+ Write-Host "Apollo Toolkit repo: $($Script:RepoRoot)"
728
+ Write-Host "Install mode: $Script:LinkMode"
729
+ Write-Host "Targets: $($selectedModes -join ', ')"
730
+ Write-Host ""
731
+
352
732
  foreach ($mode in $selectedModes) {
353
733
  switch ($mode) {
354
- "codex" { Install-Codex -SkillPaths ($skillPathGroups.Shared + $skillPathGroups.Codex) }
355
- "openclaw" { Install-OpenClaw -SkillPaths $skillPathGroups.Shared }
356
- "trae" { Install-Trae -SkillPaths $skillPathGroups.Shared }
357
- "agents" { Install-Agents -SkillPaths $skillPathGroups.Shared }
358
- "claude-code" { Install-ClaudeCode -SkillPaths $skillPathGroups.Shared }
734
+ "codex" { Install-Codex -SkillPaths ($effectiveShared + $skillPathGroups.Codex) }
735
+ "openclaw" { Install-OpenClaw -SkillPaths $effectiveShared }
736
+ "trae" { Install-Trae -SkillPaths $effectiveShared }
737
+ "agents" { Install-Agents -SkillPaths $effectiveShared }
738
+ "claude-code" { Install-ClaudeCode -SkillPaths $effectiveShared }
359
739
  default { throw "Unknown mode: $mode" }
360
740
  }
361
741
  }