enigma-memory 0.1.17 → 0.1.22
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/README.md +85 -27
- package/apps/cli/bin/enigma-desktop.mjs +140 -0
- package/apps/cli/bin/enigma-terminal.mjs +78 -0
- package/apps/cli/bin/enigma.mjs +4979 -3263
- package/apps/desktop/electron-main.cjs +217 -0
- package/apps/desktop/package.json +12 -0
- package/apps/desktop/src/app.js +264 -7
- package/apps/desktop/src/index.html +3514 -1373
- package/apps/desktop/src/launch-electron.mjs +51 -0
- package/apps/desktop/src/server.mjs +2914 -0
- package/apps/desktop/src/styles.css +2972 -260
- package/apps/desktop/src/zk-browser-prove.mjs +53 -0
- package/apps/desktop/src/zk-state.mjs +1789 -0
- package/apps/gateway/bin/enigma-gateway.mjs +102 -5
- package/apps/gateway/src/server.mjs +271 -8
- package/apps/ios/EnigmaCore/Package.swift +12 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/EnigmaAPIClient.swift +227 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/Models.swift +278 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/PKCE.swift +96 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/PrivacyMinimizer.swift +187 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/ToolModels.swift +129 -0
- package/apps/ios/EnigmaCore/Tests/EnigmaCoreTests/EnigmaCoreTests.swift +42 -0
- package/apps/ios/EnigmaIOS/Enigma/AppModel.swift +346 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AccentColor.colorset/Contents.json +12 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/Contents.json +11 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/EnigmaAppIcon.png +0 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/Contents.json +3 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/LaunchBackground.colorset/Contents.json +12 -0
- package/apps/ios/EnigmaIOS/Enigma/ChatView.swift +181 -0
- package/apps/ios/EnigmaIOS/Enigma/CouncilView.swift +78 -0
- package/apps/ios/EnigmaIOS/Enigma/CreateView.swift +152 -0
- package/apps/ios/EnigmaIOS/Enigma/EnigmaApp.swift +52 -0
- package/apps/ios/EnigmaIOS/Enigma/Info.plist +52 -0
- package/apps/ios/EnigmaIOS/Enigma/NaturalLanguagePrivacyTagger.swift +26 -0
- package/apps/ios/EnigmaIOS/Enigma/OAuthClient.swift +321 -0
- package/apps/ios/EnigmaIOS/Enigma/OnboardingView.swift +105 -0
- package/apps/ios/EnigmaIOS/Enigma/PrivateVaultView.swift +275 -0
- package/apps/ios/EnigmaIOS/Enigma/SecureStore.swift +76 -0
- package/apps/ios/EnigmaIOS/Enigma/SettingsView.swift +60 -0
- package/apps/ios/EnigmaIOS/Enigma/Theme.swift +80 -0
- package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/project.pbxproj +211 -0
- package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/xcshareddata/xcschemes/Enigma.xcscheme +23 -0
- package/apps/native-host/README.md +19 -8
- package/apps/native-host/bin/enigma-native-host.mjs +229 -13
- package/apps/relay/bin/enigma-relay.mjs +103 -5
- package/apps/relay/src/federation-runtime.mjs +618 -0
- package/apps/relay/src/server.mjs +310 -9
- package/apps/verifier/bin/enigma-verify.mjs +327 -11
- package/cortex-v3/circuits/build/intent_vk_bytes.json +35 -0
- package/cortex-v3/circuits/build/sale_vk_bytes.json +35 -0
- package/cortex-v3/circuits/build/vk_bytes.json +32 -0
- package/cortex-v3/proving-assets.json +64 -0
- package/cortex-v3/zk/BUILD-CONTRACT.md +87 -0
- package/cortex-v3/zk/action-transition-vk.json +119 -0
- package/cortex-v3/zk/alias-adversarial.test.mjs +220 -0
- package/cortex-v3/zk/groth16-verify-child.mjs +17 -0
- package/cortex-v3/zk/intent-witness.mjs +365 -0
- package/cortex-v3/zk/intent-witness.test.mjs +485 -0
- package/cortex-v3/zk/proving-assets.mjs +203 -0
- package/cortex-v3/zk/sale-witness.mjs +783 -0
- package/cortex-v3/zk/sale-witness.test.mjs +784 -0
- package/cortex-v3/zk/sealed-sale-release-vk.json +119 -0
- package/cortex-v3/zk/settlement-evidence.mjs +722 -0
- package/cortex-v3/zk/setup-intent.mjs +688 -0
- package/cortex-v3/zk/setup-sale.mjs +666 -0
- package/cortex-v3/zk/setup.mjs +594 -0
- package/cortex-v3/zk/witness.mjs +184 -0
- package/cortex-v3/zk/zk-codec.mjs +232 -0
- package/cortex-v3/zk/zk-codec.test.mjs +293 -0
- package/cortex-v3/zk/zk-settle.mjs +370 -0
- package/cortex-v3/zk/zk-tree.mjs +256 -0
- package/cortex-v3/zk/zk-tree.test.mjs +419 -0
- package/deploy/SIMULATION.md +14 -9
- package/deploy/docker-compose.local-production-simulation.yml +54 -12
- package/docs/benchmark-attestation-network.md +487 -487
- package/docs/benchmark-reproducibility.md +289 -289
- package/docs/blockchain-only-mechanisms.md +400 -400
- package/docs/browser-extension-install.md +8 -6
- package/docs/client-connectors.md +16 -12
- package/docs/demo-proof-network.md +275 -275
- package/docs/developer-ecosystem.md +15 -13
- package/docs/developer-proof-quickstart.md +325 -325
- package/docs/enigma-memory-ready-conformance.md +378 -376
- package/docs/install-anywhere.md +64 -30
- package/docs/installers-and-desktop.md +8 -7
- package/docs/memory-benchmarks.md +1 -1
- package/docs/memory-drive-health-model.md +690 -690
- package/docs/novelty-invention-candidates.md +161 -161
- package/docs/proof-network-build-notes.md +240 -240
- package/docs/proof-network-claim-boundaries.md +320 -318
- package/docs/proof-network.md +339 -339
- package/docs/sdk-api.md +324 -324
- package/docs/solana-proof-rail.md +453 -453
- package/examples/01-quickstart-agent/index.mjs +49 -0
- package/examples/01_agent_memory_quickstart.mjs +57 -0
- package/examples/02-multi-agent-swarm/index.mjs +57 -0
- package/examples/02_cross_model_passport.mjs +64 -0
- package/examples/03-langchain-memory/index.mjs +41 -0
- package/examples/03_poseidon_commitment_verification.mjs +71 -0
- package/examples/04-python-trading-agent/trader.py +49 -0
- package/examples/README.md +27 -0
- package/examples/ci/github-actions.yml +7 -2
- package/package.json +410 -278
- package/packages/adapters/PACKAGE_CONTRACT.md +1 -1
- package/packages/connectors/src/index.js +196 -4
- package/packages/connectors/swarm-router.mjs +168 -0
- package/packages/core/src/index.js +249 -2
- package/packages/core/src/version.mjs +7 -0
- package/packages/dev-tools/package.json +19 -0
- package/packages/dev-tools/src/index.js +4 -0
- package/packages/dev-tools/src/memory-benchmark-suite.js +112 -0
- package/packages/dev-tools/src/swarm-simulator.js +101 -0
- package/packages/dev-tools/src/vault-inspector.js +114 -0
- package/packages/dev-tools/src/vector-benchmark.js +100 -0
- package/packages/developer-platform/src/access-credentials.js +341 -0
- package/packages/developer-platform/src/http.js +132 -0
- package/packages/developer-platform/src/index.js +4 -0
- package/packages/developer-platform/src/usage-http.js +60 -0
- package/packages/developer-platform/src/usage.js +295 -0
- package/packages/enclave-runtime/attestation.mjs +159 -0
- package/packages/enclave-runtime/index.mjs +47 -0
- package/packages/enclave-runtime/session-manager.mjs +253 -0
- package/packages/enclave-runtime/zeroization-proof.mjs +227 -0
- package/packages/enigma-reflex/package.json +14 -0
- package/packages/enigma-reflex/src/index.js +204 -0
- package/packages/enigma-reflex/training/generate-dataset.mjs +40 -0
- package/packages/enigma-reflex/training/requirements.txt +8 -0
- package/packages/enigma-reflex/training/train.py +314 -0
- package/packages/enigma-weave/LICENSE +22 -0
- package/packages/enigma-weave/UPSTREAM.json +21 -0
- package/packages/enigma-weave/package.json +14 -0
- package/packages/enigma-weave/src/index.js +286 -0
- package/packages/hosted-cloud/src/index.js +80 -5
- package/packages/importers/src/index.js +432 -0
- package/packages/inference-runtime/src/browser.js +401 -0
- package/packages/inference-runtime/src/chat.js +265 -0
- package/packages/inference-runtime/src/code.js +407 -0
- package/packages/inference-runtime/src/contracts.js +162 -0
- package/packages/inference-runtime/src/http.js +232 -0
- package/packages/inference-runtime/src/image.js +186 -0
- package/packages/inference-runtime/src/index.js +10 -0
- package/packages/inference-runtime/src/model-router.js +320 -0
- package/packages/inference-runtime/src/platform.js +125 -0
- package/packages/inference-runtime/src/privacy.js +400 -0
- package/packages/inference-runtime/src/video.js +253 -0
- package/packages/mcp-server/README.md +22 -6
- package/packages/mcp-server/bin/enigma-mcp.mjs +2 -1
- package/packages/mcp-server/src/index.js +2498 -1185
- package/packages/mcp-server/src/oauth.js +561 -0
- package/packages/mcp-server/src/private-handoff.js +84 -0
- package/packages/mcp-server/src/remote-http.js +273 -0
- package/packages/mcp-server/src/remote-policy.js +72 -0
- package/packages/mcp-server/swarm-bridge.mjs +361 -0
- package/packages/mesh/index.d.ts +283 -0
- package/packages/mesh/package.json +23 -0
- package/packages/mesh/src/crypto.js +189 -0
- package/packages/mesh/src/federation-packets.js +353 -0
- package/packages/mesh/src/gossip.js +311 -0
- package/packages/mesh/src/index.js +6 -0
- package/packages/mesh/src/protocol.js +255 -0
- package/packages/mesh/src/router.js +279 -0
- package/packages/mesh/src/transport.js +306 -0
- package/packages/passport/src/index.js +436 -7
- package/packages/private-economy/src/credits-http.js +100 -0
- package/packages/private-economy/src/credits.js +447 -0
- package/packages/private-economy/src/index.js +5 -0
- package/packages/private-economy/src/payments-http.js +120 -0
- package/packages/private-economy/src/payments.js +509 -0
- package/packages/private-economy/src/x402.js +346 -0
- package/packages/proof-network/PACKAGE_CONTRACT.md +21 -0
- package/packages/rag/index.d.ts +182 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/THIRD_PARTY_LICENSES.txt +207 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/config.json +25 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/onnx/model_quantized.onnx +0 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/sha256-manifest.json +28 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer.json +30686 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer_config.json +15 -0
- package/packages/rag/package.json +27 -0
- package/packages/rag/src/blinded-search.js +109 -0
- package/packages/rag/src/bm25.js +169 -0
- package/packages/rag/src/embeddings.js +459 -0
- package/packages/rag/src/hybrid.js +76 -0
- package/packages/rag/src/index.js +38 -0
- package/packages/rag/src/reranker.js +61 -0
- package/packages/rag/src/research.js +107 -0
- package/packages/rag/src/vector-store.js +430 -0
- package/packages/rag/src/verify-model-artifacts.mjs +4 -0
- package/packages/sdk/index.d.ts +760 -0
- package/packages/sdk/package.json +33 -0
- package/packages/sdk/python/README.md +24 -0
- package/packages/sdk/python/enigma_sdk.py +250 -0
- package/packages/sdk/python/pyproject.toml +34 -0
- package/packages/sdk/python/requirements.txt +1 -0
- package/packages/sdk/python/setup.py +20 -0
- package/packages/sdk/src/federation/capability-grant.js +389 -0
- package/packages/sdk/src/federation/federation-router.js +360 -0
- package/packages/sdk/src/federation/ghostmesh-bridge.js +497 -0
- package/packages/sdk/src/federation/index.js +3 -0
- package/packages/sdk/src/index.js +1796 -0
- package/packages/sdk/src/intelligence/contradiction.js +337 -0
- package/packages/sdk/src/intelligence/decision-engine.js +155 -0
- package/packages/sdk/src/intelligence/index.js +4 -0
- package/packages/sdk/src/intelligence/ontology.js +122 -0
- package/packages/sdk/src/intelligence/temporal.js +123 -0
- package/packages/sdk/src/market-client.js +142 -0
- package/packages/sdk/src/mesh-client.js +110 -0
- package/packages/sdk/src/middleware/index.js +3 -0
- package/packages/sdk/src/middleware/langchain.js +159 -0
- package/packages/sdk/src/middleware/llamaindex.js +101 -0
- package/packages/sdk/src/middleware/vercel-ai.js +112 -0
- package/packages/sdk/src/rag-client.js +85 -0
- package/packages/sdk/src/swarm-orchestrator.js +260 -0
- package/packages/settlement/PACKAGE_CONTRACT.md +1 -1
- package/packages/snapcompact/THIRD_PARTY_LICENSES.txt +40 -0
- package/packages/snapcompact/assets/8x13-latin1.bdf +3837 -0
- package/packages/snapcompact/index.d.ts +284 -0
- package/packages/snapcompact/package.json +25 -0
- package/packages/snapcompact/src/index.js +716 -0
- package/packages/storage/PACKAGE_CONTRACT.md +1 -1
- package/packages/terminal-console/animations.mjs +240 -0
- package/packages/terminal-console/auto-anchor.mjs +220 -0
- package/packages/terminal-console/banner.mjs +91 -0
- package/packages/terminal-console/commands.mjs +459 -0
- package/packages/terminal-console/delegation.mjs +152 -0
- package/packages/terminal-console/index.mjs +5 -0
- package/packages/terminal-console/outbox.mjs +143 -0
- package/packages/terminal-console/phantom-bridge.mjs +637 -0
- package/packages/terminal-console/repl.mjs +136 -0
- package/packages/terminal-console/signer-store.mjs +130 -0
- package/packages/terminal-console/solana-rpc.mjs +214 -0
- package/packages/terminal-console/solana-transport.mjs +189 -0
- package/packages/terminal-tui/dashboard.mjs +214 -0
- package/packages/terminal-tui/index.mjs +28 -0
- package/packages/terminal-tui/merkle-tree-renderer.mjs +268 -0
- package/packages/terminal-tui/telemetry-hud.mjs +137 -0
- package/packages/vault/index.d.ts +449 -0
- package/packages/vault/package.json +27 -0
- package/packages/vault/src/e2ee.mjs +393 -0
- package/packages/vault/src/enclave.js +481 -0
- package/packages/vault/src/erasure.js +207 -0
- package/packages/vault/src/index.js +1150 -125
- package/packages/vault/src/persistence.js +307 -0
- package/packages/vault/src/poseidon.js +354 -0
- package/packages/vault/src/receipt.js +459 -0
- package/scripts/benchmark-optical-context.mjs +166 -0
- package/scripts/bootstrap-enigma.mjs +502 -0
- package/scripts/build-edge-backend-workers.mjs +20 -5
- package/scripts/build-goal-completion-audit.mjs +72 -25
- package/scripts/build-hosted-api-key-lifecycle.mjs +292 -274
- package/scripts/build-hosted-customer-lifecycle.mjs +493 -476
- package/scripts/build-hosted-probe-worker.mjs +19 -4
- package/scripts/build-installer-assets.mjs +409 -389
- package/scripts/build-operator-evidence-starter.mjs +59 -1
- package/scripts/build-production-backend-env-kit.mjs +2 -0
- package/scripts/build-production-unblocker.mjs +4 -1
- package/scripts/build-proof-network-packet.mjs +213 -213
- package/scripts/check.mjs +25 -4
- package/scripts/collect-hosted-backend-live-evidence.mjs +49 -12
- package/scripts/install-enigma-local.mjs +18 -5
- package/scripts/release-audit.mjs +74 -115
- package/scripts/release-provenance.mjs +12 -2
- package/scripts/run-backend-readiness-smoke.mjs +112 -10
- package/scripts/run-standard-memory-benchmarks.mjs +1354 -1352
- package/scripts/scan-secrets.mjs +178 -0
- package/scripts/simulate-production-env.mjs +71 -10
- package/scripts/validate-hosted-backend-live.mjs +112 -1
- package/specs/antibody-pack-v1.schema.json +95 -0
- package/specs/antigen-envelope-v1.schema.json +81 -0
- package/specs/boundary-manifest-v1.schema.json +35 -35
- package/specs/capsule-v1.schema.json +55 -55
- package/specs/claim-boundary-manifest-v1.schema.json +22 -22
- package/specs/claim-ledger-v1.schema.json +291 -0
- package/specs/context-passport-v1.schema.json +59 -0
- package/specs/deletion-tombstone-v1.schema.json +26 -26
- package/specs/evidence-packet-v1.schema.json +177 -0
- package/specs/hosted-backend-live-evidence-v1.schema.json +72 -3
- package/specs/immune-scan-report-v1.schema.json +112 -0
- package/specs/lifecycle-receipt-log-v1.schema.json +67 -0
- package/specs/memory-atom-v1.schema.json +59 -0
- package/specs/memory-event-v1.schema.json +42 -42
- package/specs/passport-v1.schema.json +50 -50
- package/specs/proof-of-non-use-v1.schema.json +65 -0
- package/specs/quarantine-record-v1.schema.json +126 -0
- package/specs/receipt-v1.schema.json +61 -61
- package/specs/state-checkpoint-v1.schema.json +37 -37
- package/specs/trust-bundle-v1.schema.json +56 -56
- package/specs/trust-card-v1.schema.json +119 -0
- package/docs/proof-network-launch-plan.md +0 -421
- package/packages/metering/PACKAGE_CONTRACT.md +0 -20
- package/scripts/build-ai-orchestration-plan.mjs +0 -248
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
struct OnboardingView: View {
|
|
4
|
+
@EnvironmentObject private var model: AppModel
|
|
5
|
+
@State private var server = ""
|
|
6
|
+
@State private var issuer = ""
|
|
7
|
+
@State private var clientID = ""
|
|
8
|
+
@State private var resource = ""
|
|
9
|
+
@State private var directToken = ""
|
|
10
|
+
@State private var showAdvanced = false
|
|
11
|
+
|
|
12
|
+
var body: some View {
|
|
13
|
+
ScrollView {
|
|
14
|
+
VStack(alignment: .leading, spacing: 26) {
|
|
15
|
+
Spacer(minLength: 24)
|
|
16
|
+
HStack(spacing: 10) {
|
|
17
|
+
EnigmaMark(size: 26)
|
|
18
|
+
Text("Enigma.").font(.system(.title2, design: .serif, weight: .bold))
|
|
19
|
+
}
|
|
20
|
+
VStack(alignment: .leading, spacing: 11) {
|
|
21
|
+
SectionEyebrow(text: "PRIVATE AI WORKSPACE")
|
|
22
|
+
Text("Your intelligence.\nYour boundary.")
|
|
23
|
+
.font(.system(size: 42, weight: .medium, design: .serif))
|
|
24
|
+
.tracking(-1.4)
|
|
25
|
+
Text("Connect to an Enigma server with OAuth PKCE. Conversation memory remains in this app process unless you explicitly save it.")
|
|
26
|
+
.font(EnigmaTheme.body)
|
|
27
|
+
.foregroundStyle(EnigmaTheme.secondary)
|
|
28
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
29
|
+
}
|
|
30
|
+
VStack(alignment: .leading, spacing: 14) {
|
|
31
|
+
field("Enigma server", text: $server, prompt: "https://api.example.com")
|
|
32
|
+
field("OAuth issuer", text: $issuer, prompt: "https://auth.example.com")
|
|
33
|
+
field("OAuth client ID (optional)", text: $clientID, prompt: "auto-register when permitted")
|
|
34
|
+
field("Protected resource", text: $resource, prompt: "https://api.example.com")
|
|
35
|
+
Text("Leave client ID blank to request an approved public-client registration for this app.")
|
|
36
|
+
.font(.caption)
|
|
37
|
+
.foregroundStyle(EnigmaTheme.muted)
|
|
38
|
+
Button("Connect with OAuth PKCE") {
|
|
39
|
+
model.configure(server: server, issuer: issuer, clientID: clientID, resource: resource)
|
|
40
|
+
Task { await model.connectWithOAuth() }
|
|
41
|
+
}
|
|
42
|
+
.buttonStyle(PrimaryButtonStyle())
|
|
43
|
+
.disabled(model.isBusy || server.isEmpty || issuer.isEmpty || resource.isEmpty)
|
|
44
|
+
}
|
|
45
|
+
.enigmaSurface(padding: 18)
|
|
46
|
+
|
|
47
|
+
DisclosureGroup("Local development token", isExpanded: $showAdvanced) {
|
|
48
|
+
VStack(alignment: .leading, spacing: 12) {
|
|
49
|
+
Text("For a loopback development server only. The token is stored in Keychain and never written to UserDefaults.")
|
|
50
|
+
.font(.footnote)
|
|
51
|
+
.foregroundStyle(EnigmaTheme.secondary)
|
|
52
|
+
SecureField("Direct bearer token", text: $directToken)
|
|
53
|
+
.textInputAutocapitalization(.never)
|
|
54
|
+
.autocorrectionDisabled()
|
|
55
|
+
.padding(12)
|
|
56
|
+
.background(.white)
|
|
57
|
+
.overlay(RoundedRectangle(cornerRadius: 9).stroke(EnigmaTheme.line))
|
|
58
|
+
Button("Use direct token") {
|
|
59
|
+
model.configure(server: server, issuer: issuer, clientID: clientID, resource: resource)
|
|
60
|
+
Task { await model.connectWithToken(directToken) }
|
|
61
|
+
}
|
|
62
|
+
.buttonStyle(.bordered)
|
|
63
|
+
.disabled(server.isEmpty || directToken.isEmpty)
|
|
64
|
+
}
|
|
65
|
+
.padding(.top, 12)
|
|
66
|
+
}
|
|
67
|
+
.font(.subheadline.weight(.medium))
|
|
68
|
+
|
|
69
|
+
VStack(alignment: .leading, spacing: 6) {
|
|
70
|
+
privacyLine("No provider-native memory is treated as canonical.")
|
|
71
|
+
privacyLine("Smart Privacy runs on-device before external routes.")
|
|
72
|
+
privacyLine("OAuth tokens use Keychain with this-device-only access.")
|
|
73
|
+
}
|
|
74
|
+
Spacer(minLength: 24)
|
|
75
|
+
}
|
|
76
|
+
.padding(.horizontal, 24)
|
|
77
|
+
}
|
|
78
|
+
.onAppear {
|
|
79
|
+
server = model.serverAddress
|
|
80
|
+
issuer = model.oauthIssuer
|
|
81
|
+
clientID = model.oauthClientID
|
|
82
|
+
resource = model.oauthResource
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private func field(_ label: String, text: Binding<String>, prompt: String) -> some View {
|
|
87
|
+
VStack(alignment: .leading, spacing: 6) {
|
|
88
|
+
Text(label.uppercased()).font(EnigmaTheme.label).tracking(1).foregroundStyle(EnigmaTheme.muted)
|
|
89
|
+
TextField(prompt, text: text)
|
|
90
|
+
.textInputAutocapitalization(.never)
|
|
91
|
+
.autocorrectionDisabled()
|
|
92
|
+
.keyboardType(.URL)
|
|
93
|
+
.padding(12)
|
|
94
|
+
.background(.white)
|
|
95
|
+
.overlay(RoundedRectangle(cornerRadius: 9).stroke(EnigmaTheme.line))
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private func privacyLine(_ value: String) -> some View {
|
|
100
|
+
HStack(alignment: .top, spacing: 8) {
|
|
101
|
+
Text("·").foregroundStyle(EnigmaTheme.ember)
|
|
102
|
+
Text(value).font(.footnote).foregroundStyle(EnigmaTheme.muted)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import EnigmaCore
|
|
2
|
+
import SwiftUI
|
|
3
|
+
import WebKit
|
|
4
|
+
|
|
5
|
+
struct PrivateVaultView: View {
|
|
6
|
+
@EnvironmentObject private var model: AppModel
|
|
7
|
+
|
|
8
|
+
var body: some View {
|
|
9
|
+
List {
|
|
10
|
+
Section {
|
|
11
|
+
HStack(spacing: 12) {
|
|
12
|
+
EnigmaMark(size: 30)
|
|
13
|
+
VStack(alignment: .leading, spacing: 2) {
|
|
14
|
+
Text("Private").font(.system(.title3, design: .serif, weight: .semibold))
|
|
15
|
+
Text("Owner-controlled memory and accountless credits").font(.caption).foregroundStyle(EnigmaTheme.muted)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
.padding(.vertical, 8)
|
|
19
|
+
}
|
|
20
|
+
Section("Private economy") {
|
|
21
|
+
NavigationLink { CreditsView() } label: {
|
|
22
|
+
Label {
|
|
23
|
+
VStack(alignment: .leading) {
|
|
24
|
+
Text("Credits")
|
|
25
|
+
Text(model.creditsVault.map { "\($0.balance) private credits" } ?? "Create or recover a vault")
|
|
26
|
+
.font(.caption).foregroundStyle(EnigmaTheme.muted)
|
|
27
|
+
}
|
|
28
|
+
} icon: { Image(systemName: "wallet.pass") }
|
|
29
|
+
}
|
|
30
|
+
NavigationLink { UsageView() } label: {
|
|
31
|
+
Label("Usage without content", systemImage: "chart.bar.xaxis")
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
Section("Disposable tools") {
|
|
35
|
+
NavigationLink { BrowserToolView() } label: {
|
|
36
|
+
Label("Private browser", systemImage: "globe")
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
Section("Boundary") {
|
|
40
|
+
NavigationLink { PrivacyDetailView() } label: {
|
|
41
|
+
Label("Smart Privacy receipt", systemImage: "checkmark.shield")
|
|
42
|
+
}
|
|
43
|
+
NavigationLink { SettingsView() } label: {
|
|
44
|
+
Label("Connection settings", systemImage: "slider.horizontal.3")
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
.scrollContentBackground(.hidden)
|
|
49
|
+
.background(EnigmaTheme.canvas)
|
|
50
|
+
.navigationTitle("Private")
|
|
51
|
+
.task { await model.refreshCredits(); await model.refreshUsage() }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
struct CreditsView: View {
|
|
56
|
+
@EnvironmentObject private var model: AppModel
|
|
57
|
+
@State private var recovery = ""
|
|
58
|
+
|
|
59
|
+
var body: some View {
|
|
60
|
+
ScrollView {
|
|
61
|
+
VStack(alignment: .leading, spacing: 18) {
|
|
62
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
63
|
+
SectionEyebrow(text: "PRIVATE CREDITS")
|
|
64
|
+
Text("Accountless balance.\nRotating recovery.").font(EnigmaTheme.display)
|
|
65
|
+
Text("The credits ledger stores no email, wallet, or customer identity. Save each new recovery code offline.")
|
|
66
|
+
.font(.subheadline).foregroundStyle(EnigmaTheme.secondary)
|
|
67
|
+
}
|
|
68
|
+
HStack(spacing: 0) {
|
|
69
|
+
metric("BALANCE", value: model.creditsVault.map { "\($0.balance)" } ?? "0")
|
|
70
|
+
Divider().overlay(EnigmaTheme.line)
|
|
71
|
+
metric("AVAILABLE", value: model.creditsVault.map { "\($0.available)" } ?? "0")
|
|
72
|
+
}
|
|
73
|
+
.enigmaSurface(padding: 0)
|
|
74
|
+
if let code = model.recoveryCodeShownOnce {
|
|
75
|
+
VStack(alignment: .leading, spacing: 10) {
|
|
76
|
+
SectionEyebrow(text: "RECOVERY CODE — SHOWN ONCE")
|
|
77
|
+
Text("Store this offline. Successful recovery rotated the old code and revoked every prior credits access token.")
|
|
78
|
+
.font(.footnote).foregroundStyle(EnigmaTheme.secondary)
|
|
79
|
+
Text(code).font(.system(.caption, design: .monospaced)).textSelection(.enabled)
|
|
80
|
+
.padding(10).background(.white).overlay(RoundedRectangle(cornerRadius: 8).stroke(EnigmaTheme.line))
|
|
81
|
+
HStack {
|
|
82
|
+
Button("Copy") { UIPasteboard.general.string = code }
|
|
83
|
+
Spacer()
|
|
84
|
+
Button("Dismiss permanently", role: .destructive) { model.dismissRecoveryCode() }
|
|
85
|
+
}
|
|
86
|
+
.font(.footnote.weight(.semibold))
|
|
87
|
+
}
|
|
88
|
+
.enigmaSurface()
|
|
89
|
+
}
|
|
90
|
+
if model.creditsVault == nil {
|
|
91
|
+
Button("Create private vault") { Task { await model.createCreditsVault() } }
|
|
92
|
+
.buttonStyle(PrimaryButtonStyle())
|
|
93
|
+
}
|
|
94
|
+
VStack(alignment: .leading, spacing: 9) {
|
|
95
|
+
Text("RECOVER AND ROTATE").font(EnigmaTheme.label).foregroundStyle(EnigmaTheme.muted)
|
|
96
|
+
SecureField("enigma_cr_…", text: $recovery)
|
|
97
|
+
.textInputAutocapitalization(.never).autocorrectionDisabled()
|
|
98
|
+
.padding(12).background(.white).overlay(RoundedRectangle(cornerRadius: 9).stroke(EnigmaTheme.line))
|
|
99
|
+
Button("Recover vault") { Task { await model.recoverCreditsVault(recovery); recovery = "" } }
|
|
100
|
+
.buttonStyle(.bordered)
|
|
101
|
+
.disabled(recovery.isEmpty)
|
|
102
|
+
}
|
|
103
|
+
.enigmaSurface()
|
|
104
|
+
|
|
105
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
106
|
+
SectionEyebrow(text: "FUNDING LINKABILITY")
|
|
107
|
+
Text(model.creditsVault?.paymentFundingBoundary ?? "Funding can be linkable: on-chain rails can reveal a wallet and Stripe can identify a customer. A separate payment ledger maps the funding event to the pseudonymous vault.")
|
|
108
|
+
.font(.footnote).foregroundStyle(EnigmaTheme.secondary)
|
|
109
|
+
if !model.creditPackages.isEmpty {
|
|
110
|
+
Text(model.creditPackages.map { "\($0.label): \($0.rails.joined(separator: ", "))" }.joined(separator: "\n"))
|
|
111
|
+
.font(.caption).foregroundStyle(EnigmaTheme.muted)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
.enigmaSurface()
|
|
115
|
+
if model.creditsVault != nil {
|
|
116
|
+
Button("Forget this device session", role: .destructive) { model.forgetCreditsSession() }
|
|
117
|
+
.font(.footnote.weight(.semibold))
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
.padding(20)
|
|
121
|
+
}
|
|
122
|
+
.navigationTitle("Credits")
|
|
123
|
+
.navigationBarTitleDisplayMode(.inline)
|
|
124
|
+
.enigmaCanvas()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private func metric(_ label: String, value: String) -> some View {
|
|
128
|
+
VStack(alignment: .leading, spacing: 5) {
|
|
129
|
+
Text(label).font(EnigmaTheme.label).foregroundStyle(EnigmaTheme.muted)
|
|
130
|
+
Text(value).font(.system(.title, design: .serif, weight: .medium))
|
|
131
|
+
Text("private credits").font(.caption2).foregroundStyle(EnigmaTheme.muted)
|
|
132
|
+
}
|
|
133
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
134
|
+
.padding(16)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
struct UsageView: View {
|
|
139
|
+
@EnvironmentObject private var model: AppModel
|
|
140
|
+
|
|
141
|
+
var body: some View {
|
|
142
|
+
ScrollView {
|
|
143
|
+
VStack(alignment: .leading, spacing: 18) {
|
|
144
|
+
SectionEyebrow(text: "30 DAY USAGE")
|
|
145
|
+
Text("Counts, never content.").font(EnigmaTheme.display)
|
|
146
|
+
if let usage = model.usage {
|
|
147
|
+
LazyVGrid(columns: [.init(.flexible()), .init(.flexible())], spacing: 10) {
|
|
148
|
+
usageMetric("EVENTS", "\(usage.totals.eventCount)")
|
|
149
|
+
usageMetric("INPUT", "\(usage.totals.inputUnits)")
|
|
150
|
+
usageMetric("OUTPUT", "\(usage.totals.outputUnits)")
|
|
151
|
+
usageMetric("KNOWN COST", (Double(usage.totals.knownCostMicrousd) / 1_000_000).formatted(.currency(code: "USD")))
|
|
152
|
+
}
|
|
153
|
+
Text(usage.costBoundary).font(.footnote).foregroundStyle(EnigmaTheme.secondary)
|
|
154
|
+
Label(usage.contentStored ? "Content stored" : "No prompts, outputs, or credentials stored", systemImage: usage.contentStored ? "exclamationmark.triangle" : "checkmark.shield")
|
|
155
|
+
.font(.footnote.weight(.semibold)).foregroundStyle(usage.contentStored ? .red : EnigmaTheme.success)
|
|
156
|
+
} else { ContentUnavailableView("No usage", systemImage: "chart.bar", description: Text("No metered events in this period.")) }
|
|
157
|
+
}
|
|
158
|
+
.padding(20)
|
|
159
|
+
}
|
|
160
|
+
.refreshable { await model.refreshUsage() }
|
|
161
|
+
.navigationTitle("Usage")
|
|
162
|
+
.navigationBarTitleDisplayMode(.inline)
|
|
163
|
+
.enigmaCanvas()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private func usageMetric(_ label: String, _ value: String) -> some View {
|
|
167
|
+
VStack(alignment: .leading, spacing: 5) {
|
|
168
|
+
Text(label).font(EnigmaTheme.label).foregroundStyle(EnigmaTheme.muted)
|
|
169
|
+
Text(value).font(.system(.title2, design: .serif, weight: .medium))
|
|
170
|
+
}.frame(maxWidth: .infinity, minHeight: 82, alignment: .leading).enigmaSurface()
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
struct PrivacyDetailView: View {
|
|
175
|
+
@EnvironmentObject private var model: AppModel
|
|
176
|
+
|
|
177
|
+
var body: some View {
|
|
178
|
+
List {
|
|
179
|
+
if let receipt = model.lastPrivacyReceipt {
|
|
180
|
+
Section("Last turn") {
|
|
181
|
+
LabeledContent("Mode", value: receipt.mode.rawValue.capitalized)
|
|
182
|
+
LabeledContent("Reference", value: String(receipt.receiptReference.uuidString.prefix(12)) + "…")
|
|
183
|
+
LabeledContent("Input shape", value: "\(receipt.inputCharacters) characters")
|
|
184
|
+
}
|
|
185
|
+
Section("Categories") {
|
|
186
|
+
ForEach(receipt.summary, id: \.self) { finding in
|
|
187
|
+
LabeledContent(finding.category.rawValue, value: "\(finding.action) × \(finding.count)")
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} else { ContentUnavailableView("No receipt", systemImage: "checkmark.shield", description: Text("Send a protected chat turn first.")) }
|
|
191
|
+
Section { Text("Operational receipts contain category counts and a random reference—not prompt text, completion text, or a deterministic prompt hash.") }
|
|
192
|
+
}
|
|
193
|
+
.navigationTitle("Smart Privacy")
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
struct BrowserToolView: View {
|
|
198
|
+
@EnvironmentObject private var model: AppModel
|
|
199
|
+
@State private var address = "https://example.com"
|
|
200
|
+
|
|
201
|
+
var body: some View {
|
|
202
|
+
VStack(spacing: 12) {
|
|
203
|
+
HStack {
|
|
204
|
+
TextField("https://example.com", text: $address)
|
|
205
|
+
.textInputAutocapitalization(.never).autocorrectionDisabled().keyboardType(.URL)
|
|
206
|
+
.textFieldStyle(.roundedBorder)
|
|
207
|
+
Button("Go") { Task { await model.navigatePrivateBrowser(address) } }
|
|
208
|
+
.disabled(model.browserSession == nil)
|
|
209
|
+
}
|
|
210
|
+
.padding(.horizontal)
|
|
211
|
+
if let session = model.browserSession, let raw = session.streamUrl,
|
|
212
|
+
let stream = URL(string: raw), let expected = URL(string: model.browserViewerOrigin), sameOrigin(stream, expected) {
|
|
213
|
+
EphemeralBrowserView(url: stream, allowedOrigin: expected)
|
|
214
|
+
} else {
|
|
215
|
+
ContentUnavailableView(
|
|
216
|
+
"No private browser",
|
|
217
|
+
systemImage: "globe",
|
|
218
|
+
description: Text(model.browserViewerOrigin.isEmpty ? "Set the signed viewer origin in Connection settings first." : "Start a broker session after network-level egress proof is available.")
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
HStack {
|
|
222
|
+
Button("Start private session") { Task { await model.startPrivateBrowser() } }
|
|
223
|
+
.buttonStyle(.borderedProminent).disabled(model.browserViewerOrigin.isEmpty || model.browserSession != nil)
|
|
224
|
+
Button("Burn", role: .destructive) { Task { await model.burnPrivateBrowser() } }
|
|
225
|
+
.disabled(model.browserSession == nil)
|
|
226
|
+
}
|
|
227
|
+
.padding(.bottom)
|
|
228
|
+
}
|
|
229
|
+
.navigationTitle("Private browser")
|
|
230
|
+
.navigationBarTitleDisplayMode(.inline)
|
|
231
|
+
.enigmaCanvas()
|
|
232
|
+
.onDisappear { if model.browserSession != nil { Task { await model.burnPrivateBrowser() } } }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private func sameOrigin(_ left: URL, _ right: URL) -> Bool {
|
|
236
|
+
left.scheme == right.scheme && left.host == right.host && left.port == right.port && left.user == nil && left.password == nil
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private struct EphemeralBrowserView: UIViewRepresentable {
|
|
241
|
+
let url: URL
|
|
242
|
+
let allowedOrigin: URL
|
|
243
|
+
|
|
244
|
+
func makeCoordinator() -> Coordinator { Coordinator(origin: allowedOrigin) }
|
|
245
|
+
|
|
246
|
+
func makeUIView(context: Context) -> WKWebView {
|
|
247
|
+
let configuration = WKWebViewConfiguration()
|
|
248
|
+
configuration.websiteDataStore = .nonPersistent()
|
|
249
|
+
configuration.defaultWebpagePreferences.allowsContentJavaScript = true
|
|
250
|
+
let view = WKWebView(frame: .zero, configuration: configuration)
|
|
251
|
+
view.navigationDelegate = context.coordinator
|
|
252
|
+
view.allowsLinkPreview = false
|
|
253
|
+
view.load(URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData))
|
|
254
|
+
return view
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
func updateUIView(_ view: WKWebView, context: Context) {
|
|
258
|
+
if view.url != url { view.load(URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData)) }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
static func dismantleUIView(_ view: WKWebView, coordinator: Coordinator) {
|
|
262
|
+
view.stopLoading()
|
|
263
|
+
view.configuration.websiteDataStore.removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), modifiedSince: .distantPast) {}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
final class Coordinator: NSObject, WKNavigationDelegate {
|
|
267
|
+
let origin: URL
|
|
268
|
+
init(origin: URL) { self.origin = origin }
|
|
269
|
+
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
|
270
|
+
guard let url = navigationAction.request.url else { decisionHandler(.cancel); return }
|
|
271
|
+
let allowed = url.scheme == origin.scheme && url.host == origin.host && url.port == origin.port && url.user == nil && url.password == nil
|
|
272
|
+
decisionHandler(allowed ? .allow : .cancel)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
|
|
4
|
+
struct OAuthCredential: Codable, Sendable {
|
|
5
|
+
let accessToken: String
|
|
6
|
+
let refreshToken: String?
|
|
7
|
+
let expiresAt: Date
|
|
8
|
+
let clientID: String
|
|
9
|
+
let issuer: URL
|
|
10
|
+
let resource: URL
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
enum SecureStoreError: LocalizedError {
|
|
14
|
+
case osStatus(OSStatus)
|
|
15
|
+
|
|
16
|
+
var errorDescription: String? {
|
|
17
|
+
switch self {
|
|
18
|
+
case let .osStatus(status): return "Keychain operation failed (\(status))."
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
struct SecureStore: Sendable {
|
|
24
|
+
let service: String
|
|
25
|
+
|
|
26
|
+
init(service: String = "com.enigmamemory.ios") {
|
|
27
|
+
self.service = service
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func set(_ data: Data, account: String) throws {
|
|
31
|
+
let query: [String: Any] = [
|
|
32
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
33
|
+
kSecAttrService as String: service,
|
|
34
|
+
kSecAttrAccount as String: account,
|
|
35
|
+
]
|
|
36
|
+
SecItemDelete(query as CFDictionary)
|
|
37
|
+
var insertion = query
|
|
38
|
+
insertion[kSecValueData as String] = data
|
|
39
|
+
insertion[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
|
40
|
+
let status = SecItemAdd(insertion as CFDictionary, nil)
|
|
41
|
+
guard status == errSecSuccess else { throw SecureStoreError.osStatus(status) }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func data(account: String) throws -> Data? {
|
|
45
|
+
let query: [String: Any] = [
|
|
46
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
47
|
+
kSecAttrService as String: service,
|
|
48
|
+
kSecAttrAccount as String: account,
|
|
49
|
+
kSecReturnData as String: true,
|
|
50
|
+
kSecMatchLimit as String: kSecMatchLimitOne,
|
|
51
|
+
]
|
|
52
|
+
var result: CFTypeRef?
|
|
53
|
+
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
54
|
+
if status == errSecItemNotFound { return nil }
|
|
55
|
+
guard status == errSecSuccess, let data = result as? Data else { throw SecureStoreError.osStatus(status) }
|
|
56
|
+
return data
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func remove(account: String) throws {
|
|
60
|
+
let query: [String: Any] = [
|
|
61
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
62
|
+
kSecAttrService as String: service,
|
|
63
|
+
kSecAttrAccount as String: account,
|
|
64
|
+
]
|
|
65
|
+
let status = SecItemDelete(query as CFDictionary)
|
|
66
|
+
guard status == errSecSuccess || status == errSecItemNotFound else { throw SecureStoreError.osStatus(status) }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func setString(_ value: String, account: String) throws {
|
|
70
|
+
try set(Data(value.utf8), account: account)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func string(account: String) throws -> String? {
|
|
74
|
+
try data(account: account).flatMap { String(data: $0, encoding: .utf8) }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
struct SettingsView: View {
|
|
4
|
+
@EnvironmentObject private var model: AppModel
|
|
5
|
+
@State private var server = ""
|
|
6
|
+
@State private var issuer = ""
|
|
7
|
+
@State private var clientID = ""
|
|
8
|
+
@State private var resource = ""
|
|
9
|
+
@State private var viewerOrigin = ""
|
|
10
|
+
|
|
11
|
+
var body: some View {
|
|
12
|
+
Form {
|
|
13
|
+
Section("Connection") {
|
|
14
|
+
field("Server", text: $server)
|
|
15
|
+
field("OAuth issuer", text: $issuer)
|
|
16
|
+
field("OAuth client ID (optional with approved registration)", text: $clientID)
|
|
17
|
+
field("Protected resource", text: $resource)
|
|
18
|
+
Button("Save connection") {
|
|
19
|
+
model.configure(server: server, issuer: issuer, clientID: clientID, resource: resource)
|
|
20
|
+
model.setBrowserViewerOrigin(viewerOrigin)
|
|
21
|
+
}
|
|
22
|
+
Button("Reconnect with OAuth PKCE") {
|
|
23
|
+
model.configure(server: server, issuer: issuer, clientID: clientID, resource: resource)
|
|
24
|
+
model.setBrowserViewerOrigin(viewerOrigin)
|
|
25
|
+
Task { await model.connectWithOAuth() }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
Section("Private browser proof boundary") {
|
|
29
|
+
field("Signed viewer origin", text: $viewerOrigin)
|
|
30
|
+
Text("The app accepts only HTTPS stream URLs at this exact origin. The browser uses a non-persistent WKWebView, receives no Enigma OAuth token, rejects cross-origin top-level navigation, and burns on exit.")
|
|
31
|
+
.font(.footnote).foregroundStyle(EnigmaTheme.secondary)
|
|
32
|
+
}
|
|
33
|
+
Section("Custody") {
|
|
34
|
+
Label("OAuth and credits access tokens: Keychain, this device only", systemImage: "key.fill")
|
|
35
|
+
Label("Conversation: process memory only", systemImage: "memorychip")
|
|
36
|
+
Label("Recovery code: shown once, never saved automatically", systemImage: "doc.on.clipboard")
|
|
37
|
+
}
|
|
38
|
+
Section {
|
|
39
|
+
Button("Disconnect and clear OAuth credentials", role: .destructive) { Task { await model.disconnect() } }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
.navigationTitle("Connection")
|
|
43
|
+
.onAppear {
|
|
44
|
+
server = model.serverAddress
|
|
45
|
+
issuer = model.oauthIssuer
|
|
46
|
+
clientID = model.oauthClientID
|
|
47
|
+
resource = model.oauthResource
|
|
48
|
+
viewerOrigin = model.browserViewerOrigin
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private func field(_ label: String, text: Binding<String>) -> some View {
|
|
53
|
+
VStack(alignment: .leading, spacing: 5) {
|
|
54
|
+
Text(label).font(.caption).foregroundStyle(EnigmaTheme.muted)
|
|
55
|
+
TextField(label, text: text)
|
|
56
|
+
.textInputAutocapitalization(.never)
|
|
57
|
+
.autocorrectionDisabled()
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
enum EnigmaTheme {
|
|
4
|
+
static let canvas = Color(red: 0.976, green: 0.969, blue: 0.949)
|
|
5
|
+
static let surface = Color.white.opacity(0.82)
|
|
6
|
+
static let ink = Color(red: 0.13, green: 0.12, blue: 0.105)
|
|
7
|
+
static let secondary = Color(red: 0.42, green: 0.40, blue: 0.36)
|
|
8
|
+
static let muted = Color(red: 0.58, green: 0.55, blue: 0.50)
|
|
9
|
+
static let line = Color(red: 0.87, green: 0.85, blue: 0.81)
|
|
10
|
+
static let ember = Color(red: 0.94, green: 0.30, blue: 0.11)
|
|
11
|
+
static let success = Color(red: 0.22, green: 0.45, blue: 0.31)
|
|
12
|
+
|
|
13
|
+
static let display = Font.system(.largeTitle, design: .serif, weight: .medium)
|
|
14
|
+
static let title = Font.system(.title2, design: .serif, weight: .medium)
|
|
15
|
+
static let body = Font.system(.body, design: .rounded)
|
|
16
|
+
static let label = Font.system(.caption2, design: .monospaced, weight: .semibold)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
struct EnigmaCanvas: ViewModifier {
|
|
20
|
+
func body(content: Content) -> some View {
|
|
21
|
+
content
|
|
22
|
+
.foregroundStyle(EnigmaTheme.ink)
|
|
23
|
+
.background(EnigmaTheme.canvas.ignoresSafeArea())
|
|
24
|
+
.tint(EnigmaTheme.ember)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
extension View {
|
|
29
|
+
func enigmaCanvas() -> some View { modifier(EnigmaCanvas()) }
|
|
30
|
+
|
|
31
|
+
func enigmaSurface(padding: CGFloat = 16) -> some View {
|
|
32
|
+
self.padding(padding)
|
|
33
|
+
.background(EnigmaTheme.surface)
|
|
34
|
+
.overlay(RoundedRectangle(cornerRadius: 12).stroke(EnigmaTheme.line, lineWidth: 0.8))
|
|
35
|
+
.clipShape(RoundedRectangle(cornerRadius: 12))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
struct EnigmaMark: View {
|
|
40
|
+
var size: CGFloat = 22
|
|
41
|
+
|
|
42
|
+
var body: some View {
|
|
43
|
+
ZStack {
|
|
44
|
+
ForEach(0..<12, id: \.self) { index in
|
|
45
|
+
Capsule()
|
|
46
|
+
.fill(EnigmaTheme.ember)
|
|
47
|
+
.frame(width: size * 0.11, height: size * 0.44)
|
|
48
|
+
.offset(y: -size * 0.29)
|
|
49
|
+
.rotationEffect(.degrees(Double(index) * 30))
|
|
50
|
+
}
|
|
51
|
+
Circle().fill(EnigmaTheme.canvas).frame(width: size * 0.22, height: size * 0.22)
|
|
52
|
+
}
|
|
53
|
+
.frame(width: size, height: size)
|
|
54
|
+
.accessibilityHidden(true)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
struct SectionEyebrow: View {
|
|
59
|
+
let text: String
|
|
60
|
+
var body: some View {
|
|
61
|
+
Text(text.uppercased())
|
|
62
|
+
.font(EnigmaTheme.label)
|
|
63
|
+
.tracking(1.6)
|
|
64
|
+
.foregroundStyle(EnigmaTheme.ember)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
struct PrimaryButtonStyle: ButtonStyle {
|
|
69
|
+
func makeBody(configuration: Configuration) -> some View {
|
|
70
|
+
configuration.label
|
|
71
|
+
.font(.system(.subheadline, design: .rounded, weight: .semibold))
|
|
72
|
+
.frame(maxWidth: .infinity)
|
|
73
|
+
.padding(.vertical, 13)
|
|
74
|
+
.foregroundStyle(.white)
|
|
75
|
+
.background(EnigmaTheme.ember.opacity(configuration.isPressed ? 0.78 : 1))
|
|
76
|
+
.clipShape(RoundedRectangle(cornerRadius: 10))
|
|
77
|
+
.scaleEffect(configuration.isPressed ? 0.985 : 1)
|
|
78
|
+
.animation(.easeOut(duration: 0.12), value: configuration.isPressed)
|
|
79
|
+
}
|
|
80
|
+
}
|