forked from earlyaidopters/second-brain
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup.ps1
More file actions
572 lines (532 loc) · 28 KB
/
Copy pathsetup.ps1
File metadata and controls
572 lines (532 loc) · 28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
# ─────────────────────────────────────────────────────────────────────────────
# SECOND BRAIN — Windows Setup (PowerShell)
# Compatible with PowerShell 5.1+ (Windows default) and PowerShell 7+
# Run with: powershell -ExecutionPolicy Bypass -File setup.ps1
# ─────────────────────────────────────────────────────────────────────────────
# --- Enable ANSI/VT100 color support for PS 5.1 (best-effort) ---------------
# PS 7+ enables this automatically. PS 5.1 on Windows 10 1903+ supports it
# but needs it turned on via Win32 API. If this fails, colors show as raw
# escape codes — ugly but the script still works.
try {
$null = Add-Type -MemberDefinition @'
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetConsoleMode(IntPtr h, uint m);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GetConsoleMode(IntPtr h, out uint m);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetStdHandle(int n);
'@ -Name 'VTConsole' -Namespace 'SecondBrainVT' -ErrorAction Stop
$h = [SecondBrainVT.VTConsole]::GetStdHandle(-11)
$mode = 0
[SecondBrainVT.VTConsole]::GetConsoleMode($h, [ref]$mode) | Out-Null
[SecondBrainVT.VTConsole]::SetConsoleMode($h, $mode -bor 0x0004) | Out-Null
} catch {
# VT enablement failed — colors will show as raw escape codes
# The script still works, just not pretty
}
# Fix winget UTF-8 encoding for PS 5.1 (prevents mojibake in winget output)
try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
# --- PowerShell 5.1 compatible escape codes (no backtick-e) -----------------
$ESC = [char]27
$Purple = "$ESC[35m"
$Green = "$ESC[32m"
$Orange = "$ESC[33m"
$Red = "$ESC[31m"
$White = "$ESC[1;37m"
$Cyan = "$ESC[36m"
$Dim = "$ESC[2m"
$Reset = "$ESC[0m"
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
# --- Self-bootstrap: if repo files are missing, clone them -------------------
# This happens when the script is run via the one-liner (Invoke-WebRequest).
# We clone the full repo and re-exec from there.
if (-not (Test-Path "$scriptDir\CLAUDE.md")) {
Write-Host "Repo files not found alongside script. Downloading full repo..."
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
Write-Host ""
Write-Host " ${Red}ERROR${Reset} git is required but not found."
Write-Host ""
Write-Host " ${White}Option 1:${Reset} Install git, then re-run:"
Write-Host " winget install --id Git.Git --silent --accept-package-agreements --accept-source-agreements"
Write-Host " (Close and reopen PowerShell after installing git)"
Write-Host ""
Write-Host " ${White}Option 2:${Reset} Clone the repo manually:"
Write-Host " 1. Install git from https://git-scm.com/download/win"
Write-Host " 2. git clone https://github.com/earlyaidopters/second-brain.git"
Write-Host " 3. cd second-brain"
Write-Host " 4. powershell -ExecutionPolicy Bypass -File setup.ps1"
exit 1
}
$bootstrapDir = Join-Path ([System.IO.Path]::GetTempPath()) "second-brain-setup"
Remove-Item $bootstrapDir -Recurse -Force -ErrorAction SilentlyContinue
git clone --depth=1 https://github.com/earlyaidopters/second-brain.git $bootstrapDir *>$null
if ($LASTEXITCODE -ne 0) {
Write-Host " ${Red}ERROR${Reset} Could not clone repo. Check your internet connection."
Write-Host " Alternative: download from https://github.com/earlyaidopters/second-brain"
exit 1
}
Write-Host " ${Green}OK${Reset} Repo downloaded"
# Re-exec from the cloned repo
& powershell -ExecutionPolicy Bypass -File "$bootstrapDir\setup.ps1"
Remove-Item $bootstrapDir -Recurse -Force -ErrorAction SilentlyContinue
exit $LASTEXITCODE
}
Clear-Host
Write-Host ""
Write-Host "${Purple}" -NoNewline
Write-Host @"
___ ___ ___ ___ _ _ ___ ___ ___ _ ___ _ _
/ __|| __|| __/ _ \| \| || \ | _ )_ _|/_\ |_ _| \| |
\__ \| _| | _| (_) | .`` || |) | | _ \| | / _ \ | || .`` |
|___/|___||___\___/|_|\_||___/ |___/___/_/ \_\___|_|\_|
"@
Write-Host "${Reset}"
Write-Host " Obsidian + Claude Code - Your AI-powered second brain"
Write-Host ""
Write-Host " ====================================================="
Write-Host ""
Write-Host " ${White}What this script installs:${Reset}"
Write-Host ""
Write-Host " ${Purple}Obsidian${Reset} Free note-taking app. Notes live as plain text files"
Write-Host " on your computer - private, local, forever yours."
Write-Host ""
Write-Host " ${Purple}Claude Code${Reset} Anthropic's AI that runs in your terminal. Reads and"
Write-Host " writes your vault directly - no copy-pasting."
Write-Host ""
Write-Host " ${Purple}Python packages${Reset} Background libraries used by Gemini 3 Flash to read"
Write-Host " and synthesize your existing files (PDFs, docs, slides)."
Write-Host ""
Write-Host " ${Purple}Vault skills${Reset} Slash commands that teach Claude how to use your vault:"
Write-Host " /vault-setup /daily /tldr /file-intel"
Write-Host ""
Write-Host " ${Purple}Obsidian Skills${Reset} Official skills by Kepano (Obsidian CEO) - lets Claude"
Write-Host " ${Dim}(optional)${Reset} navigate your vault using the Obsidian CLI."
Write-Host ""
Write-Host " ${Dim} Nothing is uploaded. Your vault stays on your machine.${Reset}"
Write-Host ""
Write-Host " ====================================================="
Write-Host ""
# === STEP 1: Check winget ===================================================
Write-Host "${White}Step 1/7 -- Checking winget${Reset}"
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
Write-Host " ${Red}FAIL${Reset} winget not found."
Write-Host " Install 'App Installer' from the Microsoft Store,"
Write-Host " or update to Windows 11 / latest Windows 10."
exit 1
}
Write-Host " ${Green}OK${Reset} winget available"
# === STEP 2: Obsidian ========================================================
Write-Host ""
Write-Host "${White}Step 2/7 -- Installing Obsidian${Reset}"
$obsCheck = (winget list --id Obsidian.Obsidian 2>$null | Select-String "Obsidian.Obsidian")
if (-not $obsCheck) {
Write-Host " Installing Obsidian..."
winget install --id Obsidian.Obsidian --silent --accept-package-agreements --accept-source-agreements
if ($LASTEXITCODE -eq 0) {
Write-Host " ${Green}OK${Reset} Obsidian installed"
} else {
Write-Host " ${Orange}WARNING${Reset} Obsidian install may have failed."
Write-Host " To retry: winget install Obsidian.Obsidian"
Write-Host " Or download from: https://obsidian.md/download"
}
} else {
Write-Host " ${Green}OK${Reset} Obsidian already installed"
}
# === STEP 3: Claude Code =====================================================
Write-Host ""
Write-Host "${White}Step 3/7 -- Installing Claude Code${Reset}"
if (-not (Get-Command claude -ErrorAction SilentlyContinue)) {
Write-Host " Installing Claude Code via winget..."
winget install --id Anthropic.ClaudeCode --silent --accept-package-agreements --accept-source-agreements
if ($LASTEXITCODE -eq 0) {
Write-Host " ${Green}OK${Reset} Claude Code installed"
} else {
Write-Host " ${Orange}WARNING${Reset} Claude Code install may have failed."
Write-Host " You can also install via npm: npm install -g @anthropic-ai/claude-code"
}
Write-Host ""
Write-Host " ${Orange}NOTE${Reset} You MUST close and reopen this terminal (or open a new one)"
Write-Host " for the 'claude' command to be available."
} else {
Write-Host " ${Green}OK${Reset} Claude Code already installed"
}
# === STEP 4: Python deps =====================================================
Write-Host ""
Write-Host "${White}Step 4/7 -- Installing Python dependencies${Reset}"
$pipInstalled = $false
# Check requirements.txt exists first
if (-not (Test-Path "$scriptDir\requirements.txt")) {
Write-Host " ${Orange}WARNING${Reset} requirements.txt not found in script directory."
Write-Host " After cloning: cd second-brain, then rerun:"
Write-Host " powershell -ExecutionPolicy Bypass -File setup.ps1"
} else {
# Try 'pip' first, then 'python -m pip', then 'py -m pip'
if (Get-Command pip -ErrorAction SilentlyContinue) {
pip install -q -r "$scriptDir\requirements.txt" 2>$null
if ($LASTEXITCODE -eq 0) {
$pipInstalled = $true
Write-Host " ${Green}OK${Reset} Python packages installed"
} else {
Write-Host " ${Orange}WARNING${Reset} pip install had errors -- some packages may be missing"
}
} elseif (Get-Command python -ErrorAction SilentlyContinue) {
python -m pip install -q -r "$scriptDir\requirements.txt" 2>$null
if ($LASTEXITCODE -eq 0) {
$pipInstalled = $true
Write-Host " ${Green}OK${Reset} Python packages installed (via python -m pip)"
} else {
Write-Host " ${Orange}WARNING${Reset} pip install had errors -- some packages may be missing"
}
} elseif (Get-Command py -ErrorAction SilentlyContinue) {
py -m pip install -q -r "$scriptDir\requirements.txt" 2>$null
if ($LASTEXITCODE -eq 0) {
$pipInstalled = $true
Write-Host " ${Green}OK${Reset} Python packages installed (via py -m pip)"
} else {
Write-Host " ${Orange}WARNING${Reset} pip install had errors -- some packages may be missing"
}
}
}
if (-not $pipInstalled) {
Write-Host " ${Orange}WARNING${Reset} Python/pip not found or install failed."
Write-Host ""
Write-Host " ${White}How to fix:${Reset}"
Write-Host " 1. Download Python from https://python.org/downloads"
Write-Host " 2. On the FIRST screen of the installer, tick 'Add Python to PATH'"
Write-Host " 3. Complete the install, then close and reopen PowerShell"
Write-Host " 4. Re-run this script (it is safe to re-run)"
Write-Host ""
Write-Host " ${Dim}NOTE: If Python is only installed inside WSL, this script cannot use it."
Write-Host " You need native Windows Python.${Reset}"
}
# === STEP 5: Vault setup =====================================================
Write-Host ""
Write-Host "${White}Step 5/7 -- Setting up your vault${Reset}"
Write-Host ""
Write-Host " Where should your second brain live?"
Write-Host " ${Dim}Press Enter for default: $env:USERPROFILE\second-brain${Reset}"
Write-Host " ${Dim}(e.g. C:\Users\YourName\Documents\MyVault -- quotes OK)${Reset}"
$vaultInput = Read-Host " Vault path"
if (-not $vaultInput) { $vaultInput = "$env:USERPROFILE\second-brain" }
# Sanitize: strip surrounding quotes, trim whitespace, expand tilde
$vaultPath = $vaultInput.Trim().Trim('"').Trim("'")
if ($vaultPath.StartsWith('~')) {
$vaultPath = $vaultPath.Replace('~', $env:USERPROFILE)
}
# Strip trailing backslash (unless it's a root like C:\)
if ($vaultPath.Length -gt 3 -and $vaultPath.EndsWith('\')) {
$vaultPath = $vaultPath.TrimEnd('\')
}
# Guard: check if path is an existing file (not a directory)
if ((Test-Path $vaultPath) -and -not (Test-Path $vaultPath -PathType Container)) {
Write-Host " ${Red}ERROR${Reset} That path points to a file, not a folder: $vaultPath"
Write-Host " Please re-run and enter a folder path."
exit 1
}
# --- Detect existing vault ---------------------------------------------------
$isExistingVault = $false
$hasObsidianFolder = Test-Path "$vaultPath\.obsidian"
$hasExistingClaude = Test-Path "$vaultPath\CLAUDE.md"
# Check if the directory exists and is non-empty (any file or folder inside)
$isNonEmptyDir = (Test-Path $vaultPath) -and ((Get-ChildItem $vaultPath -Force -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne '.DS_Store' } | Measure-Object).Count -gt 0)
if ($hasObsidianFolder -or $isNonEmptyDir) {
$isExistingVault = $true
Write-Host ""
Write-Host " ====================================================="
Write-Host " ${Cyan}Existing vault detected at: $vaultPath${Reset}"
Write-Host " ====================================================="
Write-Host ""
Write-Host " We found existing files here. The script will:"
Write-Host ""
Write-Host " ${Green}+${Reset} Add missing folders (inbox/, daily/, projects/, etc.)"
Write-Host " ${Green}+${Reset} Install 4 slash commands: /vault-setup /daily /tldr /file-intel"
Write-Host " ${Green}+${Reset} Copy helper scripts to scripts/"
Write-Host " ${Green}+${Reset} Install skills globally to $env:USERPROFILE\.claude\skills\"
if ($hasExistingClaude) {
$previewBackup = "CLAUDE.md.backup-$(Get-Date -Format 'yyyy-MM-dd-HHmmss')"
Write-Host " ${Orange}!${Reset} Back up your existing CLAUDE.md -> $previewBackup"
Write-Host " ${Orange}!${Reset} Install new CLAUDE.md template (run /vault-setup to personalize)"
} else {
Write-Host " ${Green}+${Reset} Create CLAUDE.md template (run /vault-setup to personalize)"
}
Write-Host ""
Write-Host " ${White}Will NOT touch:${Reset}"
Write-Host " - Your existing notes, including files already in inbox/, daily/, etc."
Write-Host " - Your Obsidian plugins, themes, and settings (.obsidian/)"
Write-Host ""
$continueAnswer = Read-Host " Continue? [Y/n]"
if (-not $continueAnswer) { $continueAnswer = "Y" }
if (-not ($continueAnswer -match "^[Yy]")) {
Write-Host " Setup cancelled. Your vault is unchanged."
exit 0
}
# Backup existing CLAUDE.md
if ($hasExistingClaude) {
$backupName = "CLAUDE.md.backup-$(Get-Date -Format 'yyyy-MM-dd-HHmmss')"
Copy-Item "$vaultPath\CLAUDE.md" "$vaultPath\$backupName" -Force
Write-Host " ${Green}OK${Reset} Backed up existing CLAUDE.md -> $backupName"
}
}
# Create folder structure (New-Item -Force won't remove existing content)
New-Item -ItemType Directory -Force -Path $vaultPath | Out-Null
New-Item -ItemType Directory -Force -Path "$vaultPath\inbox" | Out-Null
New-Item -ItemType Directory -Force -Path "$vaultPath\daily" | Out-Null
New-Item -ItemType Directory -Force -Path "$vaultPath\projects" | Out-Null
New-Item -ItemType Directory -Force -Path "$vaultPath\research" | Out-Null
New-Item -ItemType Directory -Force -Path "$vaultPath\archive" | Out-Null
New-Item -ItemType Directory -Force -Path "$vaultPath\scripts" | Out-Null
New-Item -ItemType Directory -Force -Path "$vaultPath\.claude\skills\vault-setup" | Out-Null
New-Item -ItemType Directory -Force -Path "$vaultPath\.claude\skills\daily" | Out-Null
New-Item -ItemType Directory -Force -Path "$vaultPath\.claude\skills\tldr" | Out-Null
New-Item -ItemType Directory -Force -Path "$vaultPath\.claude\skills\file-intel" | Out-Null
# Copy core files (with existence checks to handle partial repo downloads)
$copyErrors = 0
foreach ($src in @(
@("$scriptDir\CLAUDE.md", "$vaultPath\CLAUDE.md"),
@("$scriptDir\skills\vault-setup\SKILL.md", "$vaultPath\.claude\skills\vault-setup\SKILL.md"),
@("$scriptDir\skills\daily\SKILL.md", "$vaultPath\.claude\skills\daily\SKILL.md"),
@("$scriptDir\skills\tldr\SKILL.md", "$vaultPath\.claude\skills\tldr\SKILL.md"),
@("$scriptDir\skills\file-intel\SKILL.md", "$vaultPath\.claude\skills\file-intel\SKILL.md")
)) {
if (Test-Path $src[0]) {
Copy-Item $src[0] $src[1] -Force
} else {
Write-Host " ${Orange}WARNING${Reset} Missing: $($src[0])"
$copyErrors++
}
}
# Copy memory.md: on fresh installs always, on existing vaults only if missing
if (Test-Path "$scriptDir\memory.md") {
if ((-not $isExistingVault) -or (-not (Test-Path "$vaultPath\memory.md"))) {
Copy-Item "$scriptDir\memory.md" "$vaultPath\memory.md" -Force
}
}
# Copy scripts (optional -- file processing won't work without them but vault still works)
foreach ($script in @("process_docs_to_obsidian.py", "process_files_with_gemini.py")) {
if (Test-Path "$scriptDir\scripts\$script") {
Copy-Item "$scriptDir\scripts\$script" "$vaultPath\scripts\" -Force
}
}
if ($copyErrors -gt 0) {
Write-Host " ${Orange}WARNING${Reset} $copyErrors file(s) missing -- vault may not work correctly."
Write-Host " Try: git clone https://github.com/earlyaidopters/second-brain.git"
Write-Host " Then: cd second-brain && powershell -ExecutionPolicy Bypass -File setup.ps1"
}
# Install skills globally (so they work in ANY folder, not just the vault)
$globalSkillsPath = "$env:USERPROFILE\.claude\skills"
foreach ($skill in @("vault-setup", "daily", "tldr", "file-intel")) {
New-Item -ItemType Directory -Force -Path "$globalSkillsPath\$skill" | Out-Null
if (Test-Path "$scriptDir\skills\$skill\SKILL.md") {
Copy-Item "$scriptDir\skills\$skill\SKILL.md" "$globalSkillsPath\$skill\SKILL.md" -Force
}
}
if ($isExistingVault) {
Write-Host " ${Green}OK${Reset} Skills + scripts added to existing vault at $vaultPath"
} else {
Write-Host " ${Green}OK${Reset} Vault created at $vaultPath"
}
Write-Host " ${Green}OK${Reset} Skills installed globally -- work in any folder"
# API key (masked input)
Write-Host ""
Write-Host " ${Dim}Get your free Google API key: https://aistudio.google.com/apikey${Reset}"
Write-Host " ${Dim}Your key will NOT be visible as you paste -- this is normal. Press Enter when done.${Reset}"
$secureKey = Read-Host " Paste your Google API key (or press Enter to skip)" -AsSecureString
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureKey)
$apiKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
# Trim whitespace from API key (clipboard paste often adds spaces)
if ($apiKey) { $apiKey = $apiKey.Trim() }
if ($apiKey) {
# Write without BOM (PS5.1's Out-File -Encoding UTF8 adds a BOM that breaks Python dotenv)
[System.IO.File]::WriteAllText("$vaultPath\.env", "GOOGLE_API_KEY=$apiKey`n", (New-Object System.Text.UTF8Encoding $false))
Write-Host " ${Green}OK${Reset} API key saved (hidden from display)"
} else {
if (-not (Test-Path "$vaultPath\.env")) {
if (Test-Path "$scriptDir\.env.example") {
Copy-Item "$scriptDir\.env.example" "$vaultPath\.env" -Force
}
}
Write-Host " ${Orange}WARNING${Reset} Add your key to $vaultPath\.env before processing files"
}
# === STEP 6: Import existing files ==========================================
Write-Host ""
Write-Host "${White}Step 6/7 -- Import existing files (optional)${Reset}"
Write-Host ""
Write-Host " Do you have existing files to import? (PDFs, Word docs, slides)"
Write-Host " ${Dim}Gemini 3 Flash will synthesize them into clean Markdown notes${Reset}"
Write-Host ""
$importFolder = Read-Host " Folder path to import (or press Enter to skip)"
if ($importFolder) {
$importFolder = $importFolder.Trim().Trim('"').Trim("'")
}
if ($importFolder -and (Test-Path $importFolder)) {
if (-not $pipInstalled) {
Write-Host " ${Orange}WARNING${Reset} Python packages were not installed in Step 4."
Write-Host " File processing may fail. Install Python first, then run manually:"
Write-Host " python `"$vaultPath\scripts\process_docs_to_obsidian.py`" `"$importFolder`" `"$vaultPath\inbox`""
} else {
Write-Host " Processing files with Gemini 3 Flash..."
$pythonCmd = $null
if (Get-Command python -ErrorAction SilentlyContinue) { $pythonCmd = "python" }
elseif (Get-Command py -ErrorAction SilentlyContinue) { $pythonCmd = "py" }
if ($pythonCmd) {
& $pythonCmd "$vaultPath\scripts\process_docs_to_obsidian.py" $importFolder "$vaultPath\inbox"
if ($LASTEXITCODE -eq 0) {
Write-Host " ${Green}OK${Reset} Files processed -> saved to $vaultPath\inbox"
Write-Host " ${Dim}Open Claude Code and say: 'Sort everything in inbox/ into the right folders'${Reset}"
} else {
Write-Host " ${Orange}WARNING${Reset} File processing had errors -- check your API key in .env"
}
} else {
Write-Host " ${Orange}WARNING${Reset} Python not found -- install Python first, then run:"
Write-Host " python `"$vaultPath\scripts\process_docs_to_obsidian.py`" `"$importFolder`" `"$vaultPath\inbox`""
}
}
} elseif ($importFolder) {
Write-Host " ${Orange}WARNING${Reset} Folder not found: $importFolder"
}
# === STEP 7: Kepano Obsidian Skills (optional) ==============================
Write-Host ""
Write-Host "${White}Step 7/7 -- Obsidian Skills by Kepano (optional)${Reset}"
Write-Host ""
Write-Host " Kepano (Steph Ango) is the CEO of Obsidian. He published a set of"
Write-Host " official agent skills that teach Claude Code to natively read, write,"
Write-Host " and navigate your vault using the Obsidian CLI."
Write-Host ""
Write-Host " Adds these slash commands to Claude Code:"
Write-Host " ${Dim} obsidian-cli obsidian-markdown obsidian-bases json-canvas${Reset}"
Write-Host " ${Dim} (Read-only navigation commands -- they do not send your notes anywhere.)${Reset}"
Write-Host ""
$kepanoAnswer = Read-Host " Install Kepano's Obsidian skills? [Y/n]"
if (-not $kepanoAnswer) { $kepanoAnswer = "Y" }
if ($kepanoAnswer -match "^[Yy]") {
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
Write-Host " ${Orange}WARNING${Reset} git not found."
Write-Host " To install: winget install --id Git.Git --silent --accept-package-agreements"
Write-Host " Then close and reopen PowerShell, and re-run this script."
Write-Host " Or install manually: https://github.com/kepano/obsidian-skills"
} else {
Write-Host " Cloning obsidian-skills..."
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
try {
git clone --depth=1 https://github.com/kepano/obsidian-skills.git "$tempDir\obsidian-skills" *>$null
$skillsPath = "$tempDir\obsidian-skills\skills"
if (Test-Path $skillsPath) {
Get-ChildItem $skillsPath -Directory | ForEach-Object {
$skillName = $_.Name
# Install to vault AND globally
New-Item -ItemType Directory -Force -Path "$vaultPath\.claude\skills\$skillName" | Out-Null
New-Item -ItemType Directory -Force -Path "$globalSkillsPath\$skillName" | Out-Null
Copy-Item "$($_.FullName)\SKILL.md" "$vaultPath\.claude\skills\$skillName\SKILL.md" -Force -ErrorAction SilentlyContinue
Copy-Item "$($_.FullName)\SKILL.md" "$globalSkillsPath\$skillName\SKILL.md" -Force -ErrorAction SilentlyContinue
}
Write-Host " ${Green}OK${Reset} Kepano's Obsidian skills installed (vault + global)"
} else {
Write-Host " ${Orange}WARNING${Reset} Cloned repo but skills/ folder not found"
}
} catch {
Write-Host " ${Orange}WARNING${Reset} Could not reach GitHub."
Write-Host " Optional -- your vault works without this."
Write-Host " To install later: https://github.com/kepano/obsidian-skills"
} finally {
Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
} else {
Write-Host " ${Dim} Skipped -- install anytime: https://github.com/kepano/obsidian-skills${Reset}"
}
# === VERIFICATION ============================================================
Write-Host ""
Write-Host " ${White}Checking installation...${Reset}"
Write-Host " ${Dim}(Any failures above are safe to retry -- just re-run this script.)${Reset}"
Write-Host ""
$obsVerify = (winget list --id Obsidian.Obsidian 2>$null | Select-String "Obsidian.Obsidian")
if ($obsVerify) {
Write-Host " ${Green}OK${Reset} Obsidian"
} else {
Write-Host " ${Red}FAIL${Reset} Obsidian -- run: winget install Obsidian.Obsidian"
}
if (Get-Command claude -ErrorAction SilentlyContinue) {
Write-Host " ${Green}OK${Reset} Claude Code"
} else {
Write-Host " ${Orange}NOTE${Reset} Claude Code not in PATH -- close and reopen this terminal"
}
$pyFound = $false
if (Get-Command python -ErrorAction SilentlyContinue) {
$pyVer = python --version 2>&1
Write-Host " ${Green}OK${Reset} $pyVer"
$pyFound = $true
} elseif (Get-Command py -ErrorAction SilentlyContinue) {
$pyVer = py --version 2>&1
Write-Host " ${Green}OK${Reset} $pyVer (via py launcher)"
$pyFound = $true
}
if (-not $pyFound) {
Write-Host " ${Orange}WARNING${Reset} Python not found -- install from python.org (tick 'Add to PATH')"
}
if (Test-Path "$vaultPath\CLAUDE.md") {
Write-Host " ${Green}OK${Reset} Vault $vaultPath"
} else {
Write-Host " ${Red}FAIL${Reset} Vault files missing at $vaultPath"
}
$skillCount = (Get-ChildItem "$vaultPath\.claude\skills" -Directory -ErrorAction SilentlyContinue | Measure-Object).Count
Write-Host " ${Green}OK${Reset} $skillCount skills installed"
# === IMPORTANT WARNINGS =====================================================
Write-Host ""
Write-Host " ====================================================="
Write-Host ""
Write-Host " ${Orange}IMPORTANT -- Read before using:${Reset}"
Write-Host ""
Write-Host " ${White}Obsidian Sync / Cloud Sync users:${Reset}"
Write-Host " If you use Obsidian Sync, iCloud, OneDrive, or Dropbox,"
Write-Host " EXCLUDE these folders from sync: ${Cyan}.claude/${Reset} ${Cyan}scripts/${Reset} ${Cyan}.env${Reset}"
Write-Host ""
Write-Host " In Obsidian: Settings -> Sync -> Excluded folders"
Write-Host " Add: .claude, scripts"
Write-Host ""
Write-Host " ${Red}Why?${Reset} If .claude/ gets synced, it can create a recursive loop"
Write-Host " that bloats your vault and corrupts Claude's context."
Write-Host " (In severe cases, this has made vaults completely unusable.)"
Write-Host ""
Write-Host " ====================================================="
Write-Host ""
# Done
if ($isExistingVault) {
Write-Host " ${Green}Your vault is upgraded.${Reset}"
Write-Host ""
Write-Host " ${White}What you just got:${Reset}"
Write-Host " - 4 slash commands: /vault-setup /daily /tldr /file-intel"
if ($hasExistingClaude) {
Write-Host " - New CLAUDE.md template (your original backed up as $backupName)"
} else {
Write-Host " - CLAUDE.md template (personalize with /vault-setup)"
}
Write-Host " - Missing vault folders added (your existing notes untouched)"
Write-Host " - File processing scripts in scripts/"
} else {
Write-Host " ${Green}Your second brain is ready.${Reset}"
Write-Host ""
Write-Host " ${White}What you just got:${Reset}"
Write-Host " - 4 slash commands: /vault-setup /daily /tldr /file-intel"
Write-Host " - CLAUDE.md template (personalize it with /vault-setup)"
Write-Host " - Vault folder structure for organizing your notes"
Write-Host " - File processing scripts (optional, needs Gemini API key)"
}
Write-Host ""
Write-Host " Claude Code now knows your vault structure and will read it before every session."
Write-Host ""
Write-Host " ${White}Next steps:${Reset}"
Write-Host " 1. Open Obsidian, select vault: $vaultPath"
Write-Host " 2. In Obsidian: gear icon (bottom-left) -> General -> Enable CLI"
Write-Host " 3. Open a new PowerShell window (Win -> type 'powershell' -> Enter):"
Write-Host " cd `"$vaultPath`""
Write-Host " claude"
Write-Host " 4. Type: /vault-setup"
Write-Host " (Claude will interview you and personalize your vault)"
Write-Host ""
Write-Host " ${Dim}This script is safe to re-run -- it detects existing vaults, creates"
Write-Host " timestamped backups of CLAUDE.md, and only adds what is missing.${Reset}"
Write-Host ""
# Open Obsidian
Start-Process "obsidian://" -ErrorAction SilentlyContinue