getculpa 0.0.1 → 1.0.2

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/LICENSE ADDED
@@ -0,0 +1,41 @@
1
+ Culpa — proprietary software licence
2
+ ====================================
3
+
4
+ Copyright (c) 2026 Myaigi AI Labs (Proprietary) Limited.
5
+ All rights reserved.
6
+
7
+ Culpa is proprietary software. It is licensed, not sold.
8
+
9
+ Your use of this package, the `getculpa` and `culpa` commands it installs, the
10
+ binaries it downloads, and the Culpa container images it runs is governed by
11
+ the Culpa End User Licence Agreement ("EULA") between you and Myaigi AI Labs
12
+ (Proprietary) Limited.
13
+
14
+ Licensor: Myaigi AI Labs (Proprietary) Limited
15
+ Registration number 2025/688531/07
16
+ 121 Senior Drive, Randburg, Gauteng, 2195, South Africa
17
+ Product: Culpa
18
+ Governing law: Republic of South Africa
19
+
20
+ To obtain the current EULA, contact the licensor at the address below.
21
+
22
+ Except as the EULA expressly permits, you may not copy, modify, merge,
23
+ publish, distribute, sublicense, sell, rent, lease, reverse engineer, or
24
+ create derivative works of the Software, and you may not remove or alter any
25
+ proprietary notice in it.
26
+
27
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28
+ IMPLIED, TO THE FULLEST EXTENT PERMITTED BY LAW. The EULA governs warranties,
29
+ liability, and your statutory rights, and prevails over this notice wherever
30
+ the two differ. Nothing here limits any right you have under applicable
31
+ consumer law that cannot lawfully be excluded.
32
+
33
+ This package downloads and installs additional Culpa components at install
34
+ time and at update time, from Culpa's official release channel, with each
35
+ artifact verified by checksum before it is used.
36
+
37
+ Third-party open-source components distributed with or downloaded by Culpa
38
+ remain under their own licences; those licences continue to apply to them and
39
+ nothing in this notice restricts rights granted by them.
40
+
41
+ Legal notices and licensing questions: info@myaigi.ai
package/README.md CHANGED
@@ -1,18 +1,37 @@
1
- # Culpa
2
-
3
- **LLM spend forensics and forecasting, local-first.**
4
-
5
- ## Coming soon
6
-
7
- This package reserves the name. It prints a notice and does nothing else —
8
- no network calls, no files written.
9
-
10
- ```bash
11
- npx getculpa
12
- ```
13
-
14
- Culpa answers where your LLM spend actually went: which feature, which
15
- customer, which conversationpriced from a versioned price book rather than
16
- from provider-reported totals, with the money kept in exact decimals.
17
-
18
- More at **[getculpa.com](https://getculpa.com)**.
1
+ # getculpa
2
+
3
+ `packaging/npm-getculpa` supersedes the `installers/npm@0.0.1` name-claim
4
+ placeholder (that package's contents are frozen; this one is the real
5
+ package published as `getculpa` on npm).
6
+
7
+ **`npm i -g getculpa` provisions the full Culpa install the same files,
8
+ shortcuts, and registration the Windows installer lays down — and starts
9
+ nothing.** `getculpa` is what wakes the stack up.
10
+
11
+ - `getculpa` / `getculpa start` — start (or resume) the Culpa stack
12
+ - `getculpa stop` — stop the running stack (`docker compose stop`; recorded data is kept)
13
+ - `getculpa restart` — stop, then start
14
+ - `getculpa status` install/docker/stack/license status (never prints key material)
15
+ - `getculpa doctor` — diagnose this install (version/platform, file integrity, Docker, container state, ports, health, license presence never prints key material); exits 1 if a check fails
16
+ - `getculpa repair` — re-stage any missing/corrupt install files (never touches an existing `docker-compose.yml` or recorded data); idempotent
17
+ - `getculpa uninstall` — remove Culpa (recorded data is kept unless you confirm)
18
+ - `getculpa scan` — zero-install local scan, no Docker, no network
19
+ - `getculpa connect` — print the topology-appropriate capture recipe (`--fly` also emits a ready-to-deploy Fly.io shape)
20
+ - `getculpa update` — manage CLI launcher updates
21
+
22
+ Why two layers? The npm package installs two things: the platform's `culpa`
23
+ launcher binary (trust level: HTTPS to one hardcoded GitHub Releases origin +
24
+ a published SHA-256 checksum — the same trust as `npm install` itself) and
25
+ the capture collector (same trust model; its absence only turns capture off,
26
+ nothing else degrades). Every subsequent launcher update then flows through
27
+ a stricter path: an Ed25519-signed release manifest verified against a key
28
+ embedded in the launcher, staged installs with a self-test, next-launch
29
+ activation, and `culpa update rollback`.
30
+
31
+ - `culpa update --check` — check for a new version (never installs)
32
+ - `culpa update` — download, verify, stage, self-test; activates next launch
33
+ - `culpa update --status` / `rollback`
34
+ - `culpa config set updates.mode <auto|notify|off>` (default: notify)
35
+
36
+ The package name is `getculpa` because the `culpa` npm name is not ours; the
37
+ installed commands are `getculpa` and `culpa`.
@@ -0,0 +1,137 @@
1
+ # CF18 (D-088 items 4/5) - the capture collector's lifecycle on a Windows
2
+ # host install. ONE implementation; install-culpa.ps1, launch-culpa.ps1 and
3
+ # uninstall-culpa.ps1 each make a one-line call, so the three can never drift.
4
+ #
5
+ # culpa-collector.ps1 -Start [-MailboxUrl http://127.0.0.1:4545]
6
+ # culpa-collector.ps1 -Stop
7
+ # culpa-collector.ps1 -Status
8
+ #
9
+ # WHY A HOST PROCESS AND NOT A COMPOSE SERVICE: collectord binds 127.0.0.1
10
+ # ONLY, by design (D-082 privacy boundary - raw observation material crosses
11
+ # loopback IPC and nothing else). Docker port-publishing cannot expose a
12
+ # loopback-bound process, so a containerized collector beside the server is
13
+ # unreachable by an app running on the host - the common case this installer
14
+ # serves. The native binary on the host IS the loopback the app shares.
15
+ # (Containerized apps use deploy/capture/capture-compose.yml's netns-sharing
16
+ # sidecar instead; `culpa connect` prints which applies.)
17
+ #
18
+ # The token authenticates the collector to the mailbox target. Against the
19
+ # local culpa-api it is not currently checked (capture is never gated,
20
+ # D-031 section 7), but collectord requires one at boot and a future hosted target
21
+ # does check it - so a real one is generated once and persisted beside the
22
+ # install, never a hardcoded placeholder.
23
+
24
+ param(
25
+ [switch]$Start,
26
+ [switch]$Stop,
27
+ [switch]$Status,
28
+ [string]$MailboxUrl = "http://127.0.0.1:4545",
29
+ # CodeRabbit PR#16: overridable when 4752 is taken on the host
30
+ [int]$Port = 4752
31
+ )
32
+
33
+ $ErrorActionPreference = "Stop"
34
+ $AppDir = $PSScriptRoot
35
+ $Exe = Join-Path $AppDir "culpa-collectord-win32-x64.exe"
36
+ $RegisterMjs = Join-Path $AppDir "register.mjs"
37
+ $PidFile = Join-Path $AppDir "collector.pid"
38
+ $TokenFile = Join-Path $AppDir "collector-token.txt"
39
+ $LogFile = Join-Path $AppDir "collector.log"
40
+
41
+ function Get-RunningCollectorId {
42
+ if (-not (Test-Path $PidFile)) { return $null }
43
+ $savedPid = (Get-Content $PidFile -ErrorAction SilentlyContinue | Select-Object -First 1)
44
+ # CodeRabbit PR#16: a corrupt PID file (partial write, manual edit) used to
45
+ # TERMINATE the script - Get-Process's [int] binding fails under
46
+ # ErrorActionPreference=Stop and SilentlyContinue does not cover binding
47
+ # errors. Parse defensively: garbage means "not running", never a crash.
48
+ $parsedPid = 0
49
+ if (-not [int]::TryParse("$savedPid", [ref]$parsedPid) -or $parsedPid -le 0) { return $null }
50
+ $proc = Get-Process -Id $parsedPid -ErrorAction SilentlyContinue
51
+ # PID reuse guard: only claim it is ours if the image name matches
52
+ if ($proc -and $proc.ProcessName -like "culpa-collectord*") { return $proc.Id }
53
+ return $null
54
+ }
55
+
56
+ if ($Status) {
57
+ $running = Get-RunningCollectorId
58
+ if ($running) { Write-Host "collector: running (pid $running, port $Port)" }
59
+ else { Write-Host "collector: not running" }
60
+ exit 0
61
+ }
62
+
63
+ if ($Stop) {
64
+ $running = Get-RunningCollectorId
65
+ if ($running) {
66
+ Stop-Process -Id $running -Force -ErrorAction SilentlyContinue
67
+ Write-Host "collector: stopped (pid $running)"
68
+ } else {
69
+ Write-Host "collector: was not running"
70
+ }
71
+ Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
72
+ exit 0
73
+ }
74
+
75
+ if (-not $Start) {
76
+ Write-Host "usage: culpa-collector.ps1 -Start [-MailboxUrl <url>] | -Stop | -Status"
77
+ exit 2
78
+ }
79
+
80
+ # -- Start --------------------------------------------------------------------
81
+ if (-not (Test-Path $Exe)) {
82
+ # an older zip layout without the capture plane - say so, never fail the
83
+ # caller's install; the stack itself is unaffected
84
+ Write-Host "collector: $((Split-Path $Exe -Leaf)) not present in this package - skipping capture setup" -ForegroundColor Yellow
85
+ exit 0
86
+ }
87
+
88
+ $existing = Get-RunningCollectorId
89
+ if ($existing) {
90
+ Write-Host "collector: already running (pid $existing)"
91
+ exit 0
92
+ }
93
+
94
+ if (-not (Test-Path $TokenFile)) {
95
+ # 64 hex chars from the cryptographic RNG; persisted so restarts keep the
96
+ # same credential (a hosted mailbox target checks it)
97
+ $bytes = New-Object byte[] 32
98
+ [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
99
+ ($bytes | ForEach-Object { $_.ToString("x2") }) -join "" | Set-Content $TokenFile -NoNewline
100
+ }
101
+ $token = Get-Content $TokenFile -Raw
102
+
103
+ $env:CULPA_MAILBOX_URL = $MailboxUrl
104
+ $env:CULPA_MAILBOX_TOKEN = $token
105
+ $env:CULPA_COLLECTOR_PORT = "$Port"
106
+ $proc = Start-Process -FilePath $Exe -WindowStyle Hidden -PassThru `
107
+ -RedirectStandardOutput $LogFile -RedirectStandardError "$LogFile.err"
108
+ Set-Content $PidFile $proc.Id
109
+
110
+ # bounded readiness wait: collectord prints "LISTENING <port>" on stdout
111
+ $ready = $false
112
+ for ($i = 0; $i -lt 20; $i++) {
113
+ Start-Sleep -Milliseconds 250
114
+ if ((Test-Path $LogFile) -and (Select-String -Path $LogFile -Pattern "LISTENING" -Quiet)) { $ready = $true; break }
115
+ if ($proc.HasExited) { break }
116
+ }
117
+ if (-not $ready) {
118
+ $detail = if (Test-Path "$LogFile.err") { Get-Content "$LogFile.err" -Raw } else { "(no log)" }
119
+ Write-Host "collector: failed to start - capture is OFF, everything else still works." -ForegroundColor Yellow
120
+ Write-Host " $($detail.Trim())" -ForegroundColor Yellow
121
+ Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
122
+ exit 0 # never fail the caller's install over capture
123
+ }
124
+
125
+ Write-Host "collector: running (pid $($proc.Id), 127.0.0.1:$Port -> $MailboxUrl)"
126
+ Write-Host ""
127
+ # The preload MUST be a file:// URL with forward slashes: on Windows, Node
128
+ # parses a bare `--import=C:\...` as a URL whose scheme is `c:` and the app
129
+ # CRASHES AT BOOT with ERR_UNSUPPORTED_ESM_URL_SCHEME. Proven live on this
130
+ # machine during the CF18 host-path gate - this line is why the guidance
131
+ # below is not simply $RegisterMjs.
132
+ $RegisterUrl = "file:///" + ($RegisterMjs -replace "\\", "/")
133
+ Write-Host "To capture a Node app on this machine, set on THAT app and restart it:" -ForegroundColor Cyan
134
+ Write-Host " NODE_OPTIONS=--import=$RegisterUrl"
135
+ Write-Host " CULPA_COLLECTOR_URL=http://127.0.0.1:$Port"
136
+ Write-Host "(containerized app, or 2+ machines? run: culpa connect --machines <N>)"
137
+ exit 0
@@ -0,0 +1,144 @@
1
+ # Culpa — release compose file shipped inside the installers (T-087/T-088).
2
+ # Net effect is identical to running the stack from the repository:
3
+ # same services, same container names, same ports, same privacy default.
4
+ # Images are pulled from ghcr.io; a locally built/tagged image with the same
5
+ # name is used when present, which is how these pins were verified before
6
+ # publication.
7
+ #
8
+ # ── RELEASE STATE: v1.0.2 PUBLISHED AND SIGNED (2026-08-17) ─────────
9
+ # The line above is REWRITTEN BY installers/publish.sh at real-publish time —
10
+ # this file has shipped a hand-edited, wrong publication claim twice, so the
11
+ # claim is now mechanical, never prose. While it reads NOT PUBLISHED, the pins
12
+ # below resolve only against locally built images and this file must not be
13
+ # distributed. v0.11.0 is the CF9-CF12 round (UI alignment fixes, forecast
14
+ # uncertainty-calibration repair + kappa bound + cohort/centring explain,
15
+ # cache coverage flags, relay-mesh W1/W2c groundwork — feature-gated out of
16
+ # shipped binaries) on top of v0.10.0, the CF7/CF8 round (forecast P50-anchor + cache
17
+ # counterfactual math repair, founder UI list U-1..U-8 + P-1, founder
18
+ # palette + card "i" tooltips, native CLI auto-update Option A, QW-1..4
19
+ # container-upgrade guards, CodeRabbit PR#13 triage) on top of v0.9.0.
20
+ # The relay and license-server carry NO changes since v0.9.0 and keep
21
+ # their v0.9.0 images — a version is only claimed where a real diff
22
+ # exists. A signed tag is never rebuilt (D-072), so new contents mean a
23
+ # new version.
24
+ #
25
+ # Once the state line reads PUBLISHED AND SIGNED, verify before you run:
26
+ # cosign verify \
27
+ # --certificate-identity 'info@myaigi.ai' \
28
+ # --certificate-oidc-issuer 'https://github.com/login/oauth' \
29
+ # ghcr.io/myaigidev/culpa-server:v1.0.2
30
+ # Repeat for culpa-dashboard. Both must report VERIFIED.
31
+ #
32
+ # If a pull fails with an authentication or "denied" error rather than a
33
+ # missing manifest, the package has not been made public yet — the images
34
+ # exist. Check with:
35
+ # gh api user/packages/container/culpa-server --jq .visibility
36
+ # Do NOT use `docker manifest inspect` with an emptied DOCKER_CONFIG to test
37
+ # this: on Docker Desktop for Windows the daemon authenticates through its own
38
+ # credential store, so that check reports a false PUBLIC.
39
+
40
+ services:
41
+ db:
42
+ image: postgres:16-alpine
43
+ container_name: culpa-db
44
+ environment:
45
+ POSTGRES_USER: culpa
46
+ POSTGRES_PASSWORD: culpa
47
+ POSTGRES_DB: culpa
48
+ volumes:
49
+ - culpa_pgdata:/var/lib/postgresql/data
50
+ healthcheck:
51
+ test: ["CMD-SHELL", "pg_isready -U culpa -d culpa"]
52
+ interval: 2s
53
+ timeout: 2s
54
+ retries: 30
55
+ restart: unless-stopped
56
+
57
+ server:
58
+ image: ghcr.io/myaigidev/culpa-server:v1.0.2
59
+ container_name: culpa-server
60
+ environment:
61
+ DATABASE_URL: postgres://culpa:culpa@db:5432/culpa
62
+ # full forensic capture is the default (A5); set metadata-only to opt out
63
+ # CF24-T1 (S2): the server binds 127.0.0.1 by DEFAULT
64
+ # (native/culpa-api/src/bind.rs — the binary the image actually runs).
65
+ # Inside a container that is unreachable through the published port, so
66
+ # containers must say 0.0.0.0 explicitly. The host-side `ports:` mapping
67
+ # below binds 127.0.0.1, so nothing OFF THIS MACHINE can reach it —
68
+ # reachable-by-design is always an operator's written choice, never a
69
+ # default.
70
+ #
71
+ # Say precisely what that does and does not cover (CF25, CodeRabbit
72
+ # PR #18): the `ports:` mapping governs HOST exposure only. Because the
73
+ # listener inside the container is 0.0.0.0, any other container attached
74
+ # to this Compose network reaches the server directly on 4545, without
75
+ # traversing the published port at all. That is ordinary Docker
76
+ # networking and it is fine for the services defined here — but adding a
77
+ # container to this network grants it API access, and the port mapping
78
+ # will not stop it.
79
+ CULPA_API_BIND: "0.0.0.0"
80
+ CULPA_PRIVACY_MODE: full
81
+ # T-120: the machine secret encrypting the customer key at rest lives
82
+ # here as a file — a VOLUME, so it survives container recreation
83
+ CULPA_SECRET_DIR: /var/lib/culpa
84
+ # T-M1/T-M2: scan the mounted host logs; live sweep every 5 minutes
85
+ CULPA_SCAN_HOME: /host
86
+ CULPA_SCAN_EVERY_MS: "300000"
87
+ # LAUNCH CONFIG (founder go, 2026-07-31 — D-041 enforcement flip;
88
+ # gating scope corrected 2026-08-03 by D-055): every customer-facing
89
+ # READ of the customer's own spend data requires a valid entitlement.
90
+ # Capture (ingest, scan, imports) and licensing lifecycle routes (claim,
91
+ # activation, license, checkout, billing-portal, entitlement, terms)
92
+ # are NEVER gated — a licensing failure must never break the
93
+ # customer's production traffic or block them from activating. Fail
94
+ # open ONLY on infrastructure failure (license server unreachable, DB
95
+ # blip, WASM missing), where the cached entitlement stands; an ABSENT
96
+ # or CANCELLED entitlement refuses. Activation: buy → claim in-app (or
97
+ # set CULPA_LICENSE_KEY, or paste the key in the dashboard under
98
+ # Account -> Plan & Billing -> Activate); the license server below is where the app
99
+ # syncs counts-only and refreshes the signed entitlement. Remove the
100
+ # license-server line and nothing ever leaves this machine — paid
101
+ # reads then stay degraded (that is the deal).
102
+ CULPA_TRUST_ENFORCE: "1"
103
+ CULPA_LICENSE_SERVER: https://culpa.fly.dev
104
+ # T-CF4-12 (Culpaflight4 #15): current model prices pull on first boot
105
+ # and refresh daily. This is a DOWNLOAD of the public price list —
106
+ # nothing about your usage goes anywhere (D-034). Remove the line and
107
+ # the committed snapshot + seeds keep pricing working offline.
108
+ CULPA_PRICING_SYNC: "1"
109
+ ports:
110
+ - "127.0.0.1:4545:4545"
111
+ volumes:
112
+ - culpa_data:/var/lib/culpa
113
+ # T-M3 ("+ See a cost"): the host's AI-tool session logs, READ-ONLY —
114
+ # this is what lets the in-dashboard scan see Claude Code/Codex history
115
+ # and keep it live. Only these two dirs, never the whole home.
116
+ # NARROW scope (review M2): only the session-log subdirs — ~/.claude
117
+ # also holds credentials/settings, which the container must never see
118
+ - ${CULPA_HOST_HOME:-${USERPROFILE:-${HOME:-}}}/.claude/projects:/host/.claude/projects:ro
119
+ - ${CULPA_HOST_HOME:-${USERPROFILE:-${HOME:-}}}/.codex/sessions:/host/.codex/sessions:ro
120
+ depends_on:
121
+ db:
122
+ condition: service_healthy
123
+ restart: unless-stopped
124
+
125
+ dashboard:
126
+ # CF14 (2026-08-12): dashboard-only bump. v0.11.1 is v0.11.0 plus the dark
127
+ # black-surface palette — no server, migration or API change, so the server
128
+ # above deliberately stays at v0.11.0 rather than being re-tagged for a
129
+ # release it has no diff in (D-072: never rebuild a tag that already exists).
130
+ image: ghcr.io/myaigidev/culpa-dashboard:v1.0.2
131
+ container_name: culpa-dashboard
132
+ environment:
133
+ CULPA_API_BASE: http://server:4545
134
+ ports:
135
+ - "127.0.0.1:3000:3000"
136
+ depends_on:
137
+ - server
138
+ restart: unless-stopped
139
+
140
+ volumes:
141
+ culpa_pgdata:
142
+ name: culpa_pgdata
143
+ culpa_data:
144
+ name: culpa_data