arkaos 2.10.0 → 2.11.0

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.
Files changed (46) hide show
  1. package/README.md +318 -107
  2. package/VERSION +1 -1
  3. package/config/hooks/cwd-changed.ps1 +144 -0
  4. package/config/hooks/post-tool-use.ps1 +347 -0
  5. package/config/hooks/post-tool-use.sh +6 -6
  6. package/config/hooks/pre-compact.ps1 +238 -0
  7. package/config/hooks/pre-compact.sh +10 -6
  8. package/config/hooks/session-start.ps1 +109 -0
  9. package/config/hooks/session-start.sh +1 -1
  10. package/config/hooks/user-prompt-submit.ps1 +287 -0
  11. package/config/hooks/user-prompt-submit.sh +5 -2
  12. package/config/statusline.ps1 +160 -0
  13. package/core/cognition/__pycache__/__init__.cpython-313.pyc +0 -0
  14. package/core/cognition/capture/__pycache__/__init__.cpython-313.pyc +0 -0
  15. package/core/cognition/capture/__pycache__/collector.cpython-313.pyc +0 -0
  16. package/core/cognition/capture/__pycache__/store.cpython-313.pyc +0 -0
  17. package/core/cognition/insights/__pycache__/__init__.cpython-313.pyc +0 -0
  18. package/core/cognition/insights/__pycache__/store.cpython-313.pyc +0 -0
  19. package/core/cognition/memory/__pycache__/__init__.cpython-313.pyc +0 -0
  20. package/core/cognition/memory/__pycache__/obsidian.cpython-313.pyc +0 -0
  21. package/core/cognition/memory/__pycache__/schemas.cpython-313.pyc +0 -0
  22. package/core/cognition/memory/__pycache__/vector.cpython-313.pyc +0 -0
  23. package/core/cognition/memory/__pycache__/writer.cpython-313.pyc +0 -0
  24. package/core/cognition/research/__pycache__/__init__.cpython-313.pyc +0 -0
  25. package/core/cognition/research/__pycache__/profiler.cpython-313.pyc +0 -0
  26. package/core/cognition/scheduler/__pycache__/__init__.cpython-313.pyc +0 -0
  27. package/core/cognition/scheduler/__pycache__/cli.cpython-313.pyc +0 -0
  28. package/core/cognition/scheduler/__pycache__/daemon.cpython-313.pyc +0 -0
  29. package/core/cognition/scheduler/__pycache__/platform.cpython-313.pyc +0 -0
  30. package/core/cognition/scheduler/daemon.py +77 -21
  31. package/core/cognition/scheduler/platform.py +43 -12
  32. package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
  33. package/core/knowledge/vector_store.py +50 -25
  34. package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
  35. package/core/synapse/layers.py +2 -2
  36. package/installer/adapters/claude-code.js +72 -45
  37. package/installer/cli.js +19 -6
  38. package/installer/doctor.js +130 -18
  39. package/installer/index.js +592 -149
  40. package/installer/platform.js +20 -0
  41. package/installer/prompts.js +109 -5
  42. package/installer/python-resolver.js +251 -0
  43. package/installer/update.js +497 -62
  44. package/package.json +1 -1
  45. package/pyproject.toml +2 -2
  46. package/scripts/start-dashboard.ps1 +271 -0
@@ -0,0 +1,109 @@
1
+ # ============================================================================
2
+ # ArkaOS — SessionStart Hook (Windows / PowerShell 5.1+)
3
+ #
4
+ # Port of config/hooks/session-start.sh. Must remain behaviourally identical:
5
+ # - Same profile lookup, time-based greeting, version-drift detection.
6
+ # - Same JSON output contract: {"systemMessage": "<...>"} on stdout.
7
+ #
8
+ # Minimum PowerShell: 5.1 (shipped with every Windows 10+). No pwsh 7 prereq.
9
+ # Box-drawing characters are built from [char] codes so this file stays pure
10
+ # ASCII and avoids source-encoding pitfalls with PS 5.1 default ANSI reads.
11
+ # ============================================================================
12
+
13
+ $ErrorActionPreference = 'Stop'
14
+ # Force UTF-8 on stdout so ConvertTo-Json does not emit mojibake box chars.
15
+ [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
16
+
17
+ $arkaosHome = Join-Path $env:USERPROFILE '.arkaos'
18
+
19
+ # ─── Profile ───────────────────────────────────────────────────────────
20
+ $name = 'founder'
21
+ $company = 'WizardingCode'
22
+ $version = '2.x'
23
+
24
+ $profilePath = Join-Path $arkaosHome 'profile.json'
25
+ if (Test-Path -LiteralPath $profilePath) {
26
+ try {
27
+ # $profile is an automatic variable in PowerShell — use $arkaosProfile.
28
+ $arkaosProfile = Get-Content -Raw -LiteralPath $profilePath -Encoding UTF8 | ConvertFrom-Json
29
+ if ($arkaosProfile.name) { $name = [string]$arkaosProfile.name }
30
+ elseif ($arkaosProfile.role) { $name = [string]$arkaosProfile.role }
31
+ if ($arkaosProfile.company) { $company = [string]$arkaosProfile.company }
32
+ } catch {
33
+ # Corrupt profile — keep defaults and continue.
34
+ }
35
+ }
36
+
37
+ $repoPathFile = Join-Path $arkaosHome '.repo-path'
38
+ if (Test-Path -LiteralPath $repoPathFile) {
39
+ try {
40
+ $repo = (Get-Content -Raw -LiteralPath $repoPathFile -Encoding UTF8).Trim()
41
+ if ($repo) {
42
+ $versionFile = Join-Path $repo 'VERSION'
43
+ if (Test-Path -LiteralPath $versionFile) {
44
+ $version = (Get-Content -Raw -LiteralPath $versionFile -Encoding UTF8).Trim()
45
+ }
46
+ }
47
+ } catch { }
48
+ }
49
+
50
+ # ─── Time greeting ─────────────────────────────────────────────────────
51
+ $hour = [int](Get-Date -Format 'HH')
52
+ if ($hour -ge 5 -and $hour -lt 12) { $greeting = 'Bom dia' }
53
+ elseif ($hour -ge 12 -and $hour -lt 19) { $greeting = 'Boa tarde' }
54
+ else { $greeting = 'Boa noite' }
55
+
56
+ # ─── Version drift ─────────────────────────────────────────────────────
57
+ $drift = ''
58
+ $syncStatePath = Join-Path $arkaosHome 'sync-state.json'
59
+ if (Test-Path -LiteralPath $syncStatePath) {
60
+ try {
61
+ $syncState = Get-Content -Raw -LiteralPath $syncStatePath -Encoding UTF8 | ConvertFrom-Json
62
+ $synced = if ($null -ne $syncState.version) { [string]$syncState.version } else { 'none' }
63
+ if ($synced -ne $version) {
64
+ $drift = "`n[arka:update-available] Core v$version != synced v$synced. Run /arka update."
65
+ }
66
+ } catch {
67
+ $drift = "`n[arka:update-available] Sync state unreadable. Run /arka update."
68
+ }
69
+ } else {
70
+ $drift = "`n[arka:update-available] Never synced. Run /arka update."
71
+ }
72
+
73
+ # ─── Build ASCII header from char codes ───────────────────────────────
74
+ $tl = [char]0x2554 # ╔
75
+ $tr = [char]0x2557 # ╗
76
+ $bl = [char]0x255A # ╚
77
+ $br = [char]0x255D # ╝
78
+ $h = [char]0x2550 # ═
79
+ $v = [char]0x2551 # ║
80
+ $bar = [string]($h.ToString() * 46)
81
+
82
+ $topLine = "$tl$bar$tr"
83
+ $bottomLine = "$bl$bar$br"
84
+ $empty = "$v" + (' ' * 46) + "$v"
85
+ function Pad-Line([string]$text) {
86
+ $innerWidth = 46
87
+ $padLeft = [int](($innerWidth - $text.Length) / 2)
88
+ $padRight = $innerWidth - $text.Length - $padLeft
89
+ return "$v" + (' ' * $padLeft) + $text + (' ' * $padRight) + "$v"
90
+ }
91
+
92
+ $lines = @(
93
+ $topLine
94
+ $empty
95
+ (Pad-Line 'A R K A O S')
96
+ $empty
97
+ (Pad-Line 'The Operating System for AI Teams')
98
+ (Pad-Line 'by WizardingCode')
99
+ $empty
100
+ $bottomLine
101
+ ''
102
+ "$greeting, $name ($company)"
103
+ "ArkaOS v$version | 65 agents | 17 departments | 244+ skills$drift"
104
+ )
105
+
106
+ $msg = "`n" + ($lines -join "`n")
107
+
108
+ # ─── Output as systemMessage JSON (single line, same contract as .sh) ──
109
+ [pscustomobject]@{ systemMessage = $msg } | ConvertTo-Json -Compress
@@ -39,7 +39,7 @@ else
39
39
  fi
40
40
 
41
41
  # ─── Build message ─────────────────────────────────────────────────────
42
- MSG="╔══════════════════════════════════════════════╗\\n"
42
+ MSG="\\n╔══════════════════════════════════════════════╗\\n"
43
43
  MSG+="║ ║\\n"
44
44
  MSG+="║ A R K A O S ║\\n"
45
45
  MSG+="║ ║\\n"
@@ -0,0 +1,287 @@
1
+ # ============================================================================
2
+ # ArkaOS v2 - UserPromptSubmit Hook (Synapse Bridge) (Windows / PowerShell 5.1+)
3
+ #
4
+ # Port of config/hooks/user-prompt-submit.sh. Calls the Python Synapse bridge
5
+ # for 8-layer context injection, with a pure-PowerShell fallback for
6
+ # installations where Python is unavailable or the bridge is missing.
7
+ #
8
+ # Contract:
9
+ # - Reads a JSON payload from stdin (userInput / message / raw).
10
+ # - Emits a single-line JSON object on stdout:
11
+ # {"additionalContext": "<sync-notice><synapse-or-fallback>"}
12
+ # - Side effect: writes one line to the cache metrics JSONL.
13
+ #
14
+ # Target latency: <100ms (hook timeout is 10s in settings).
15
+ #
16
+ # File is pure ASCII. Any typographic characters that need to appear in the
17
+ # output are built from [char] codes at runtime so PS 5.1's default ANSI
18
+ # source read cannot mojibake them.
19
+ # ============================================================================
20
+
21
+ $ErrorActionPreference = 'Stop'
22
+ [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
23
+
24
+ # --- Read stdin ------------------------------------------------------------
25
+ $stdinText = [Console]::In.ReadToEnd()
26
+ $payload = $null
27
+ if (-not [string]::IsNullOrWhiteSpace($stdinText)) {
28
+ try { $payload = $stdinText | ConvertFrom-Json } catch { $payload = $null }
29
+ }
30
+
31
+ # --- V1 migration detection ------------------------------------------------
32
+ # The old logic fired whenever any v1 skill directory existed on disk and
33
+ # the migrated-from-v1 marker was absent. That is too noisy on a Windows
34
+ # install that has v2 running correctly alongside leftover v1 skill dirs
35
+ # (common when a user installed via `npx arkaos install` after having
36
+ # previously used v1): the hook early-returned with the MIGRATION message
37
+ # on every single user prompt, drowning out the Synapse context.
38
+ #
39
+ # The presence of a valid v2 install manifest at `~/.arkaos/install-
40
+ # manifest.json` is the canonical signal that v2 is functional. If we
41
+ # see that file, skip the migration nag — the user has clearly already
42
+ # moved to v2, even if the explicit `migrated-from-v1` flag file was
43
+ # never created.
44
+ $v2Manifest = Join-Path $env:USERPROFILE '.arkaos\install-manifest.json'
45
+ $v2Installed = Test-Path -LiteralPath $v2Manifest
46
+
47
+ if (-not $v2Installed) {
48
+ $v1Paths = @(
49
+ (Join-Path $env:USERPROFILE '.claude\skills\arka-os'),
50
+ (Join-Path $env:USERPROFILE '.claude\skills\arkaos')
51
+ )
52
+ $migrationMarker = Join-Path $env:USERPROFILE '.arkaos\migrated-from-v1'
53
+
54
+ foreach ($v1 in $v1Paths) {
55
+ if ((Test-Path -LiteralPath $v1 -PathType Container) -and -not (Test-Path -LiteralPath $migrationMarker)) {
56
+ $msg = "[MIGRATION] ArkaOS v1 detected at $v1. Run: npx arkaos migrate - This will backup v1, preserve your data, and install v2. See: https://github.com/andreagroferreira/arka-os#install"
57
+ [pscustomobject]@{ additionalContext = $msg } | ConvertTo-Json -Compress
58
+ exit 0
59
+ }
60
+ }
61
+ }
62
+
63
+ # --- Performance timing ----------------------------------------------------
64
+ $sw = [System.Diagnostics.Stopwatch]::StartNew()
65
+
66
+ # --- Sync version detection ------------------------------------------------
67
+ $syncNotice = ''
68
+ $arkaosHome = Join-Path $env:USERPROFILE '.arkaos'
69
+ $syncStatePath = Join-Path $arkaosHome 'sync-state.json'
70
+ $repoPathFile = Join-Path $arkaosHome '.repo-path'
71
+
72
+ $currentVersion = ''
73
+ if (Test-Path -LiteralPath $repoPathFile) {
74
+ try {
75
+ $repoPath = (Get-Content -Raw -LiteralPath $repoPathFile -Encoding UTF8).Trim()
76
+ if ($repoPath) {
77
+ $vFile = Join-Path $repoPath 'VERSION'
78
+ $pkgFile = Join-Path $repoPath 'package.json'
79
+ if (Test-Path -LiteralPath $vFile) {
80
+ $currentVersion = (Get-Content -Raw -LiteralPath $vFile -Encoding UTF8).Trim()
81
+ } elseif (Test-Path -LiteralPath $pkgFile) {
82
+ try {
83
+ $currentVersion = [string]((Get-Content -Raw -LiteralPath $pkgFile -Encoding UTF8 | ConvertFrom-Json).version)
84
+ } catch { }
85
+ }
86
+ }
87
+ } catch { }
88
+ }
89
+
90
+ if ($currentVersion) {
91
+ $syncedVersion = 'none'
92
+ if (Test-Path -LiteralPath $syncStatePath) {
93
+ try {
94
+ $ss = Get-Content -Raw -LiteralPath $syncStatePath -Encoding UTF8 | ConvertFrom-Json
95
+ if ($ss.version) { $syncedVersion = [string]$ss.version }
96
+ } catch { }
97
+ }
98
+ if ($currentVersion -ne $syncedVersion) {
99
+ $syncNotice = "[arka:update-available] ArkaOS v$currentVersion installed (synced: $syncedVersion). Run /arka update to sync all projects. "
100
+ }
101
+ }
102
+
103
+ # --- Resolve ARKAOS_ROOT (env var -> .repo-path -> ~/.arkaos -> fallback) --
104
+ if ($env:ARKAOS_ROOT) {
105
+ $arkaosRoot = $env:ARKAOS_ROOT
106
+ } elseif (Test-Path -LiteralPath $repoPathFile) {
107
+ try {
108
+ $arkaosRoot = (Get-Content -Raw -LiteralPath $repoPathFile -Encoding UTF8).Trim()
109
+ } catch {
110
+ $arkaosRoot = $null
111
+ }
112
+ }
113
+ if (-not $arkaosRoot) {
114
+ if (Test-Path -LiteralPath $arkaosHome -PathType Container) {
115
+ $arkaosRoot = $arkaosHome
116
+ } else {
117
+ # Portable fallback - matches the shell hook's final branch.
118
+ $arkaosRoot = if ($env:ARKA_OS) { $env:ARKA_OS } else { Join-Path $env:USERPROFILE '.claude\skills\arkaos' }
119
+ }
120
+ }
121
+
122
+ # --- Cache dir -------------------------------------------------------------
123
+ # Windows equivalent of /tmp: use the per-user temp dir so constitution
124
+ # caches are isolated per user.
125
+ $cacheRoot = [System.IO.Path]::GetTempPath()
126
+ $cacheDir = Join-Path $cacheRoot 'arkaos-context-cache'
127
+ $null = New-Item -ItemType Directory -Force -Path $cacheDir -ErrorAction SilentlyContinue
128
+ $cacheTtlSeconds = 300
129
+
130
+ # --- Extract user input ----------------------------------------------------
131
+ $userInput = ''
132
+ if ($payload) {
133
+ if ($payload.userInput) {
134
+ $userInput = [string]$payload.userInput
135
+ } elseif ($payload.message) {
136
+ $userInput = [string]$payload.message
137
+ }
138
+ }
139
+ if (-not $userInput) {
140
+ # Fallback: truncate raw stdin (mirrors `head -c 2000` from bash).
141
+ if ($stdinText.Length -gt 2000) {
142
+ $userInput = $stdinText.Substring(0, 2000)
143
+ } else {
144
+ $userInput = $stdinText
145
+ }
146
+ }
147
+
148
+ # --- Helper: locate a usable Python interpreter ----------------------------
149
+ function Find-Python {
150
+ # Prefer the ArkaOS venv python if the install manifest tells us where it is.
151
+ $manifestPath = Join-Path $env:USERPROFILE '.arkaos\install-manifest.json'
152
+ if (Test-Path -LiteralPath $manifestPath) {
153
+ try {
154
+ $m = Get-Content -Raw -LiteralPath $manifestPath -Encoding UTF8 | ConvertFrom-Json
155
+ if ($m.pythonCmd -and (Test-Path -LiteralPath $m.pythonCmd)) {
156
+ return $m.pythonCmd
157
+ }
158
+ } catch { }
159
+ }
160
+ # Fallback to the typical Windows venv layout.
161
+ $venvPy = Join-Path $env:USERPROFILE '.arkaos\venv\Scripts\python.exe'
162
+ if (Test-Path -LiteralPath $venvPy) { return $venvPy }
163
+ # System python. python3 is rare on Windows; `python` and `py -3` are typical.
164
+ foreach ($cmd in 'python3','python','py') {
165
+ $resolved = Get-Command $cmd -ErrorAction SilentlyContinue
166
+ if ($resolved) { return $resolved.Source }
167
+ }
168
+ return $null
169
+ }
170
+
171
+ # --- Try Python Synapse bridge ---------------------------------------------
172
+ $pythonResult = ''
173
+ $bridgeScript = Join-Path $arkaosRoot 'scripts\synapse-bridge.py'
174
+ $python = Find-Python
175
+
176
+ if ($python -and (Test-Path -LiteralPath $bridgeScript)) {
177
+ try {
178
+ $bridgeInput = [pscustomobject]@{ user_input = $userInput } | ConvertTo-Json -Compress
179
+
180
+ $psi = New-Object System.Diagnostics.ProcessStartInfo
181
+ $psi.FileName = $python
182
+ # PS 5.1 / .NET Framework: ProcessStartInfo only exposes `Arguments`
183
+ # as a single command-line string. Quote both paths so spaces work.
184
+ $psi.Arguments = "`"$bridgeScript`" --root `"$arkaosRoot`""
185
+ $psi.RedirectStandardInput = $true
186
+ $psi.RedirectStandardOutput = $true
187
+ $psi.RedirectStandardError = $true
188
+ $psi.UseShellExecute = $false
189
+ $psi.CreateNoWindow = $true
190
+ $psi.StandardInputEncoding = [System.Text.UTF8Encoding]::new($false)
191
+ $psi.StandardOutputEncoding = [System.Text.UTF8Encoding]::new($false)
192
+ # Set ARKAOS_ROOT in the child environment.
193
+ if ($psi.EnvironmentVariables.ContainsKey('ARKAOS_ROOT')) {
194
+ $psi.EnvironmentVariables['ARKAOS_ROOT'] = $arkaosRoot
195
+ } else {
196
+ [void]$psi.EnvironmentVariables.Add('ARKAOS_ROOT', $arkaosRoot)
197
+ }
198
+
199
+ $proc = [System.Diagnostics.Process]::Start($psi)
200
+ $proc.StandardInput.Write($bridgeInput)
201
+ $proc.StandardInput.Close()
202
+ # Cap the bridge call at 3 seconds so a stuck bridge cannot burn the
203
+ # full 10-second hook budget.
204
+ if (-not $proc.WaitForExit(3000)) {
205
+ try { $proc.Kill() } catch { }
206
+ } else {
207
+ $bridgeOutput = $proc.StandardOutput.ReadToEnd()
208
+ if ($bridgeOutput) {
209
+ try {
210
+ $parsed = $bridgeOutput | ConvertFrom-Json
211
+ if ($parsed.context_string) {
212
+ $pythonResult = [string]$parsed.context_string
213
+ }
214
+ } catch { }
215
+ }
216
+ }
217
+ } catch {
218
+ $pythonResult = ''
219
+ }
220
+ }
221
+
222
+ # --- Fallback: pure-PowerShell context -------------------------------------
223
+ if (-not $pythonResult) {
224
+ # L0 Constitution (5-min cache)
225
+ $l0CacheFile = Join-Path $cacheDir 'l0-constitution'
226
+ $l0 = ''
227
+ $cacheFresh = $false
228
+ if (Test-Path -LiteralPath $l0CacheFile) {
229
+ try {
230
+ $ageSeconds = ((Get-Date) - (Get-Item -LiteralPath $l0CacheFile).LastWriteTime).TotalSeconds
231
+ if ($ageSeconds -lt $cacheTtlSeconds) {
232
+ $l0 = (Get-Content -Raw -LiteralPath $l0CacheFile -Encoding UTF8).Trim()
233
+ if ($l0) { $cacheFresh = $true }
234
+ }
235
+ } catch { }
236
+ }
237
+ if (-not $cacheFresh) {
238
+ $l0 = '[Constitution] NON-NEGOTIABLE: branch-isolation, obsidian-output, authority-boundaries, security-gate, context-first, solid-clean-code, spec-driven, human-writing, squad-routing, full-visibility, sequential-validation, mandatory-qa, arka-supremacy | QUALITY-GATE: marta-cqo, eduardo-copy, francisca-tech-ux | MUST: conventional-commits, test-coverage, pattern-matching, actionable-output, memory-persistence'
239
+ try {
240
+ $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
241
+ [System.IO.File]::WriteAllText($l0CacheFile, $l0, $utf8NoBom)
242
+ } catch { }
243
+ }
244
+
245
+ # L4 Git branch
246
+ $l4 = ''
247
+ try {
248
+ $branchOutput = & git rev-parse --abbrev-ref HEAD 2>$null
249
+ if ($LASTEXITCODE -eq 0 -and $branchOutput) {
250
+ $branch = $branchOutput.Trim()
251
+ if ($branch -and $branch -ne 'main' -and $branch -ne 'master' -and $branch -ne 'dev') {
252
+ $l4 = "[branch:$branch]"
253
+ }
254
+ }
255
+ } catch { }
256
+
257
+ # L7 Time
258
+ $hour = [int](Get-Date -Format 'HH')
259
+ if ($hour -ge 5 -and $hour -lt 12) { $l7 = '[time:morning]' }
260
+ elseif ($hour -ge 12 -and $hour -lt 18) { $l7 = '[time:afternoon]' }
261
+ else { $l7 = '[time:evening]' }
262
+
263
+ $pythonResult = (@($l0, $l4, $l7) | Where-Object { $_ }) -join ' '
264
+ }
265
+
266
+ # --- Output ----------------------------------------------------------------
267
+ $additionalContext = "$syncNotice$pythonResult"
268
+ [pscustomobject]@{ additionalContext = $additionalContext } | ConvertTo-Json -Compress
269
+
270
+ # --- Metrics (JSONL append) ------------------------------------------------
271
+ $elapsed = [int]$sw.ElapsedMilliseconds
272
+ if ($elapsed -gt 0) {
273
+ try {
274
+ $metricsLine = [pscustomobject]@{ hook = 'user-prompt-submit-v2'; ms = $elapsed } | ConvertTo-Json -Compress
275
+ $metricsFile = Join-Path $cacheDir 'hook-metrics.jsonl'
276
+ $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
277
+ # Append mode: read existing bytes, append new line, write back.
278
+ # Small enough that this is cheaper than opening a FileStream.
279
+ $existing = ''
280
+ if (Test-Path -LiteralPath $metricsFile) {
281
+ $existing = [System.IO.File]::ReadAllText($metricsFile, $utf8NoBom)
282
+ }
283
+ [System.IO.File]::WriteAllText($metricsFile, "$existing$metricsLine`n", $utf8NoBom)
284
+ } catch {
285
+ # Metrics are best-effort.
286
+ }
287
+ }
@@ -58,7 +58,7 @@ _hook_ms() {
58
58
  }
59
59
 
60
60
  # ─── Paths ───────────────────────────────────────────────────────────────
61
- # Resolve ARKAOS_ROOT: env var → .repo-path → npm package → fallback
61
+ # Resolve ARKAOS_ROOT: env var → .repo-path → $HOME/.arkaosportable fallback
62
62
  if [ -n "${ARKAOS_ROOT:-}" ]; then
63
63
  : # already set
64
64
  elif [ -f "$HOME/.arkaos/.repo-path" ]; then
@@ -66,7 +66,10 @@ elif [ -f "$HOME/.arkaos/.repo-path" ]; then
66
66
  elif [ -d "$HOME/.arkaos" ]; then
67
67
  ARKAOS_ROOT="$HOME/.arkaos"
68
68
  else
69
- ARKAOS_ROOT="/Users/andreagroferreira/.npm/_npx/67d92defd11b9985/node_modules/arkaos"
69
+ # Portable fallback — matches user-prompt-submit-v2.sh. Power users can
70
+ # override with the ARKA_OS env var. Reached only on corrupt/uninitialised
71
+ # installs; synapse-bridge.py will fail gracefully if the path is wrong.
72
+ ARKAOS_ROOT="${ARKA_OS:-$HOME/.claude/skills/arkaos}"
70
73
  fi
71
74
  export ARKAOS_ROOT
72
75
 
@@ -0,0 +1,160 @@
1
+ # ============================================================================
2
+ # ARKA OS — Two-Line Color-Coded Status Line for Claude Code (Windows port)
3
+ # Receives JSON via stdin, outputs formatted two-line status bar.
4
+ #
5
+ # Paridade 1:1 com config/statusline.sh. Usa ConvertFrom-Json nativo — no jq
6
+ # or bc dependency, works on stock Windows PowerShell 5.1 and PowerShell 7.
7
+ #
8
+ # Why a separate file instead of invoking the bash script: Windows has no
9
+ # POSIX shell on the default PATH, and wrapping via `wsl bash -c ...` adds
10
+ # ~150 ms per statusline render. This port matches the canonical schema
11
+ # documented by Claude Code's statusline payload (model.display_name,
12
+ # context_window.used_percentage, cost.total_cost_usd, etc.) — NOT the
13
+ # experimental statusline-v2.sh schema, which is broken.
14
+ # ============================================================================
15
+
16
+ $ErrorActionPreference = 'SilentlyContinue'
17
+
18
+ # Force UTF-8 on stdout so the Unicode box-drawing characters (█ ░ ▲)
19
+ # render correctly on Windows PowerShell 5.1, which otherwise emits
20
+ # cp1252 to the host and shows "?" in place of the blocks.
21
+ try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
22
+
23
+ # ─── Read stdin ────────────────────────────────────────────────────────────
24
+ $raw = [Console]::In.ReadToEnd()
25
+ if ([string]::IsNullOrWhiteSpace($raw)) {
26
+ # Claude Code may invoke the statusline with no payload at startup.
27
+ # Emit a minimal banner so the bar is never blank.
28
+ Write-Output ([char]0x25B2 + 'ARKA')
29
+ return
30
+ }
31
+
32
+ # ─── Parse JSON ────────────────────────────────────────────────────────────
33
+ try {
34
+ $j = $raw | ConvertFrom-Json
35
+ } catch {
36
+ Write-Output ([char]0x25B2 + 'ARKA | json parse failed')
37
+ return
38
+ }
39
+
40
+ # Safe nested property access. PSCustomObject returns $null silently for
41
+ # missing members, which is the behavior we want — mimics jq's `// default`.
42
+ function Get-Field($obj, $path, $default) {
43
+ $cur = $obj
44
+ foreach ($seg in $path -split '\.') {
45
+ if ($null -eq $cur) { return $default }
46
+ $cur = $cur.$seg
47
+ }
48
+ if ($null -eq $cur) { return $default }
49
+ return $cur
50
+ }
51
+
52
+ $model = [string](Get-Field $j 'model.display_name' 'unknown')
53
+ $cwd = [string](Get-Field $j 'cwd' '')
54
+ $projDir = [string](Get-Field $j 'workspace.project_dir' '')
55
+ $pctRaw = Get-Field $j 'context_window.used_percentage' 0
56
+ $inTok = [int64](Get-Field $j 'context_window.total_input_tokens' 0)
57
+ $outTok = [int64](Get-Field $j 'context_window.total_output_tokens' 0)
58
+ $cost = [double](Get-Field $j 'cost.total_cost_usd' 0)
59
+ $durMs = [int64](Get-Field $j 'cost.total_duration_ms' 0)
60
+ $added = [int64](Get-Field $j 'cost.total_lines_added' 0)
61
+ $removed = [int64](Get-Field $j 'cost.total_lines_removed' 0)
62
+
63
+ $pct = [int][math]::Floor([double]$pctRaw)
64
+ if ($pct -lt 0) { $pct = 0 }
65
+ if ($pct -gt 100) { $pct = 100 }
66
+
67
+ # ─── Project name ─────────────────────────────────────────────────────────
68
+ $workDir = if ($cwd) { $cwd } else { $projDir }
69
+ if ([string]::IsNullOrEmpty($workDir)) {
70
+ $dirName = 'arka'
71
+ } else {
72
+ $dirName = Split-Path -Leaf $workDir
73
+ }
74
+
75
+ # ─── Git branch ───────────────────────────────────────────────────────────
76
+ # No file cache: PowerShell startup already dominates the render cost, the
77
+ # git call is ~20 ms on NTFS, and caching across renders is unsafe because
78
+ # Claude Code can render the statusline from multiple cwd's concurrently.
79
+ $branch = ''
80
+ if ($workDir -and (Test-Path -LiteralPath $workDir)) {
81
+ $branch = & git -C $workDir rev-parse --abbrev-ref HEAD 2>$null
82
+ if ($null -eq $branch) { $branch = '' }
83
+ $branch = ([string]$branch).Trim()
84
+ }
85
+
86
+ # ─── ANSI colors ──────────────────────────────────────────────────────────
87
+ $ESC = [char]27
88
+ $C_RESET = "$ESC[0m"
89
+ $C_CYAN = "$ESC[0;36m"
90
+ $C_DIM = "$ESC[2m"
91
+ $C_WHITE = "$ESC[1;37m"
92
+ $C_GREEN = "$ESC[0;32m"
93
+ $C_YELLOW = "$ESC[1;33m"
94
+ $C_RED = "$ESC[0;31m"
95
+ $C_BLINK_RED = "$ESC[5;31m"
96
+
97
+ if ($pct -ge 90) { $C_BAR = $C_BLINK_RED }
98
+ elseif ($pct -ge 80) { $C_BAR = $C_RED }
99
+ elseif ($pct -ge 60) { $C_BAR = $C_YELLOW }
100
+ else { $C_BAR = $C_GREEN }
101
+
102
+ # ─── Format tokens (K/M, invariant culture so pt-PT doesn't emit "12,3K") ──
103
+ function Format-Tokens($n) {
104
+ $inv = [cultureinfo]::InvariantCulture
105
+ if ($n -ge 1000000) {
106
+ return ([double]($n / 1000000.0)).ToString('0.0', $inv) + 'M'
107
+ } elseif ($n -ge 1000) {
108
+ return ([double]($n / 1000.0)).ToString('0.0', $inv) + 'K'
109
+ } else {
110
+ return "$n"
111
+ }
112
+ }
113
+
114
+ $inFmt = Format-Tokens $inTok
115
+ $outFmt = Format-Tokens $outTok
116
+
117
+ # ─── Progress bar (10 chars) ──────────────────────────────────────────────
118
+ # Floor the division: 95 % → 9 blocks, matching the bash integer division.
119
+ # PowerShell's `/` returns double, so [int] alone would round-to-even and
120
+ # drift on odd multiples of 5.
121
+ $filled = [int][math]::Floor($pct / 10.0)
122
+ $empty = 10 - $filled
123
+ $block = [char]0x2588 # █
124
+ $shade = [char]0x2591 # ░
125
+ $bar = ([string]$block * $filled) + ([string]$shade * $empty)
126
+
127
+ # ─── Format duration ──────────────────────────────────────────────────────
128
+ $secs = [int64][math]::Floor($durMs / 1000.0)
129
+ if ($secs -ge 3600) {
130
+ $h = [int][math]::Floor($secs / 3600.0)
131
+ $m = [int][math]::Floor(($secs % 3600) / 60.0)
132
+ $timeFmt = "${h}h${m}m"
133
+ } elseif ($secs -ge 60) {
134
+ $m = [int][math]::Floor($secs / 60.0)
135
+ $r = [int]($secs % 60)
136
+ $timeFmt = "${m}m${r}s"
137
+ } else {
138
+ $timeFmt = "${secs}s"
139
+ }
140
+
141
+ # ─── Format cost (invariant culture: always "$0.00", never "$0,00") ───────
142
+ $costFmt = '$' + ([double]$cost).ToString('0.00', [cultureinfo]::InvariantCulture)
143
+
144
+ # ─── Build Line 1: Context bar ────────────────────────────────────────────
145
+ $triangle = [char]0x25B2 # ▲
146
+ $line1 = "${C_CYAN}${triangle}ARKA${C_RESET} ${C_WHITE}${dirName}${C_RESET}"
147
+ if ($branch -and $branch -ne 'main' -and $branch -ne 'master') {
148
+ $line1 += " ${C_DIM}on${C_RESET} ${C_GREEN}${branch}${C_RESET}"
149
+ }
150
+ $line1 += " ${C_DIM}|${C_RESET} ${model}"
151
+
152
+ # ─── Build Line 2: Metrics bar ────────────────────────────────────────────
153
+ $line2 = "${C_BAR}${bar} ${pct}%${C_RESET}"
154
+ $line2 += " ${C_DIM}|${C_RESET} ${inFmt} in ${outFmt} out"
155
+ $line2 += " ${C_DIM}|${C_RESET} ${C_GREEN}+${added}${C_RESET} ${C_RED}-${removed}${C_RESET}"
156
+ $line2 += " ${C_DIM}|${C_RESET} ${timeFmt}"
157
+ $line2 += " ${C_DIM}|${C_RESET} ${costFmt}"
158
+
159
+ Write-Output $line1
160
+ Write-Output $line2