@hasna/recordings 0.3.13 → 0.5.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.
- package/README.md +279 -43
- package/bun.lock +37 -38
- package/contracts/v1/fixtures.json +1206 -0
- package/dist/cli/index.js +1909 -619
- package/dist/cli/macos-shortcut.d.ts +2 -2
- package/dist/contracts/hosted-v1.d.ts +105 -0
- package/dist/contracts/hosted-v1.d.ts.map +1 -0
- package/dist/contracts/hosted-v1.js +41 -0
- package/dist/contracts/stream-v1.d.ts +117 -0
- package/dist/contracts/stream-v1.d.ts.map +1 -0
- package/dist/contracts/stream-v1.js +35 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/recordings.d.ts +6 -0
- package/dist/db/recordings.d.ts.map +1 -1
- package/dist/hosted/index.d.ts +58 -0
- package/dist/hosted/index.d.ts.map +1 -0
- package/dist/hosted/index.js +266 -0
- package/dist/hosted/transport.d.ts +36 -0
- package/dist/hosted/transport.d.ts.map +1 -0
- package/dist/hosted-v1-aavn7ktb.js +4114 -0
- package/dist/hosted-v1-gdr9extc.js +84 -0
- package/dist/http/client.d.ts +183 -22
- package/dist/http/client.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2879 -2288
- package/dist/lib/audio-artifact-storage.d.ts +98 -0
- package/dist/lib/audio-artifact-storage.d.ts.map +1 -0
- package/dist/lib/capture-probe.d.ts +3 -3
- package/dist/lib/capture-probe.d.ts.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/local-opt-in.d.ts +132 -0
- package/dist/lib/local-opt-in.d.ts.map +1 -0
- package/dist/lib/macos-bundle.d.ts +1 -1
- package/dist/lib/persistence-probe.d.ts +56 -17
- package/dist/lib/persistence-probe.d.ts.map +1 -1
- package/dist/lib/transcriber.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1713 -459
- package/dist/sdk/index.d.ts +13 -5
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +904 -1
- package/dist/sdk/resolve.d.ts +53 -0
- package/dist/sdk/resolve.d.ts.map +1 -0
- package/dist/sdk/v1.generated.d.ts +6 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/client-ip.d.ts +39 -0
- package/dist/server/client-ip.d.ts.map +1 -0
- package/dist/server/cloud-config.d.ts +12 -0
- package/dist/server/cloud-config.d.ts.map +1 -1
- package/dist/server/cloud-readiness.d.ts.map +1 -1
- package/dist/server/cloud.d.ts +20 -2
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +590 -287
- package/dist/server/migrate-command.d.ts +1 -1
- package/dist/server/migrate-command.d.ts.map +1 -1
- package/dist/server/openapi.d.ts +21 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/repo.d.ts.map +1 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +1358 -874
- package/dist/store.d.ts +12 -3
- package/dist/store.d.ts.map +1 -1
- package/dist/types/index.d.ts +15 -0
- package/dist/types/index.d.ts.map +1 -1
- package/docs/hosted-sdk.md +71 -0
- package/docs/wire-contracts.md +24 -0
- package/package.json +33 -11
- package/packaging/macos/build_release_pkg.sh +3 -3
- package/packaging/macos/managed_bootstrap.sh +4 -4
- package/packaging/macos/scripts/postinstall +2 -2
- package/packaging/macos/scripts/preinstall +2 -2
- package/scripts/build_companion_cli.sh +7 -1
- package/scripts/ci-linux-suite.ts +23 -13
- package/scripts/install_macos_app.sh +27 -24
- package/scripts/macos_artifact.ts +60 -49
- package/scripts/migrate.ts +4 -20
- package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
- package/scripts/native/recordings_fs_guard.c +36 -4
- package/scripts/native-core-receipt.py +171 -0
- package/scripts/native_fs_guard.ts +2 -0
- package/scripts/release-suite-gate.ts +227 -150
- package/scripts/resolve_tailscale_cli.sh +24 -3
- package/scripts/smoke_macos_app.sh +21 -21
- package/src/native/Recordings/App/ContentView.swift +53 -68
- package/src/native/Recordings/App/MenuBarStatusView.swift +171 -61
- package/src/native/Recordings/App/RecordWorkspaceView.swift +47 -449
- package/src/native/Recordings/App/RecorderSettingsView.swift +75 -0
- package/src/native/Recordings/App/RecordingDetailView.swift +3 -4
- package/src/native/Recordings/App/RecordingsApp.swift +185 -9
- package/src/native/Recordings/App/RecordingsListView.swift +60 -112
- package/src/native/Recordings/App/RecordingsStore.swift +124 -109
- package/src/native/Recordings/App/Theme.swift +73 -61
- package/src/native/Recordings/Package.swift +1 -0
- package/src/native/Recordings/RecordingsLib/BlockingOperation.swift +29 -0
- package/src/native/Recordings/RecordingsLib/CaptureMonitor.swift +65 -0
- package/src/native/Recordings/RecordingsLib/Info.plist +3 -3
- package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +75 -21
- package/src/native/Recordings/RecordingsLib/ProjectStore.swift +4 -4
- package/src/native/Recordings/RecordingsLib/Recording.swift +14 -2
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +539 -89
- package/src/native/Recordings/RecordingsLib/RecordingGlassBackground.swift +34 -0
- package/src/native/Recordings/RecordingsLib/RecordingPasteTarget.swift +114 -0
- package/src/native/Recordings/RecordingsLib/RecordingProvider.swift +112 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +17 -10
- package/src/native/Recordings/RecordingsLib/ServiceAPIConfiguration.swift +107 -0
- package/src/native/Recordings/RecordingsLib/ServiceConnectionView.swift +76 -0
- package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -149
- package/src/native/Recordings/RecordingsLib/SettingsWindowController.swift +48 -0
- package/src/native/Recordings/RecordingsTests/BlockingOperationTests.swift +85 -0
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +454 -76
- package/src/native/Recordings/RecordingsTests/OpenAIAPIKeyStoreTests.swift +43 -38
- package/src/native/Recordings/RecordingsTests/PipeClosureFixture.swift +215 -0
- package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +10 -10
- package/src/native/Recordings/RecordingsTests/RecorderSurfaceTests.swift +60 -0
- package/src/native/Recordings/RecordingsTests/RecordingDateTests.swift +26 -0
- package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +1 -1
- package/src/native/Recordings/RecordingsTests/RecordingFrozenPasteTargetTests.swift +50 -0
- package/src/native/Recordings/RecordingsTests/RecordingPasteTargetTrackerTests.swift +48 -0
- package/src/native/Recordings/RecordingsTests/RecordingProviderTests.swift +308 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +1 -1
- package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +123 -21
- package/src/native/Recordings/RecordingsTests/ServiceAPIConfigurationTests.swift +75 -0
- package/src/native/Recordings/RecordingsTests/SettingsWindowControllerTests.swift +28 -0
- package/src/native/Recordings/RecordingsTests/TestHomeDirectory.swift +5 -1
- package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +1 -1
- package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +2 -2
- package/src/native/Recordings/Updater/Broker/BrokerMain.swift +1 -1
- package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +1 -1
- package/src/native/Recordings/Updater/Broker/InstallJournal.swift +1 -1
- package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +2 -2
- package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +1 -1
- package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +2 -2
- package/src/native/Recordings/build.sh +8 -7
- package/dist/__tests__/helpers/installer-guard-execution.d.ts +0 -22
- package/dist/__tests__/helpers/installer-guard-execution.d.ts.map +0 -1
- package/dist/__tests__/helpers/installer-preflight.d.ts +0 -22
- package/dist/__tests__/helpers/installer-preflight.d.ts.map +0 -1
- package/dist/__tests__/helpers/native-fs-guard.d.ts +0 -2
- package/dist/__tests__/helpers/native-fs-guard.d.ts.map +0 -1
- package/dist/__tests__/helpers/source-assertions.d.ts +0 -171
- package/dist/__tests__/helpers/source-assertions.d.ts.map +0 -1
- package/dist/__tests__/preload.d.ts +0 -2
- package/dist/__tests__/preload.d.ts.map +0 -1
- package/src/native/Recordings/App/SidebarView.swift +0 -212
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
+
import Security
|
|
2
3
|
|
|
3
4
|
struct ProcessingModelSelection: Equatable, Sendable {
|
|
4
5
|
let transcriptionPrompt: String
|
|
@@ -16,11 +17,17 @@ enum OpenAIAPIKeyStore {
|
|
|
16
17
|
static func load(
|
|
17
18
|
homePath: String,
|
|
18
19
|
environment: [String: String] = ProcessInfo.processInfo.environment,
|
|
19
|
-
userDefaultKey: String? = UserDefaults.standard.string(forKey: "openAIAPIKey")
|
|
20
|
+
userDefaultKey: String? = UserDefaults.standard.string(forKey: "openAIAPIKey"),
|
|
21
|
+
keychainLoader: (() throws -> String?)? = nil
|
|
20
22
|
) -> String {
|
|
21
|
-
if let key = firstNonEmpty(environment["OPENAI_API_KEY"], environment["RECORDINGS_API_KEY"]) {
|
|
23
|
+
if let key = firstNonEmpty(environment["RECORDINGS_OPENAI_API_KEY"], environment["OPENAI_API_KEY"], environment["RECORDINGS_API_KEY"]) {
|
|
22
24
|
return key
|
|
23
25
|
}
|
|
26
|
+
// Test and resolver homes must never read the owner's credentials.
|
|
27
|
+
if let loader = keychainLoader,
|
|
28
|
+
let key = try? loader(), let key = firstNonEmpty(key) { return key }
|
|
29
|
+
if keychainLoader == nil, homePath == FileManager.default.homeDirectoryForCurrentUser.path,
|
|
30
|
+
let key = try? loadKeychain(), let key = firstNonEmpty(key) { return key }
|
|
24
31
|
if let key = firstNonEmpty(userDefaultKey) {
|
|
25
32
|
return key
|
|
26
33
|
}
|
|
@@ -96,30 +103,77 @@ enum OpenAIAPIKeyStore {
|
|
|
96
103
|
)
|
|
97
104
|
}
|
|
98
105
|
|
|
99
|
-
///
|
|
100
|
-
///
|
|
101
|
-
static func save(
|
|
106
|
+
/// The app and its embedded helper share the provider credential through Keychain
|
|
107
|
+
/// and an in-memory child environment. Never write a new key into preferences.
|
|
108
|
+
static func save(
|
|
109
|
+
key: String, homePath: String,
|
|
110
|
+
defaults: UserDefaults = .standard,
|
|
111
|
+
keychainWriter: (String) throws -> Void = saveKeychain
|
|
112
|
+
) throws {
|
|
102
113
|
let trimmed = key.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
var json: [String: Any] = [:]
|
|
109
|
-
if let data = try? Data(contentsOf: url),
|
|
110
|
-
let existing = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
111
|
-
json = existing
|
|
114
|
+
try keychainWriter(trimmed)
|
|
115
|
+
var json = loadMutableConfig(homePath: homePath)
|
|
116
|
+
if json.removeValue(forKey: "openai_api_key") != nil || json["api_key"] != nil {
|
|
117
|
+
json.removeValue(forKey: "api_key")
|
|
118
|
+
try writeConfig(json, homePath: homePath)
|
|
112
119
|
}
|
|
120
|
+
defaults.removeObject(forKey: "openAIAPIKey")
|
|
121
|
+
}
|
|
113
122
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
private static var keychainQuery: [String: Any] {
|
|
124
|
+
[kSecClass as String: kSecClassGenericPassword,
|
|
125
|
+
kSecAttrService as String: "hasna.credentials.openai.api-key",
|
|
126
|
+
kSecAttrAccount as String: "openai/api_key"]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static func loadKeychain(allowInteraction: Bool = false) throws -> String? {
|
|
130
|
+
var query = keychainQuery
|
|
131
|
+
query[kSecReturnData as String] = true
|
|
132
|
+
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
133
|
+
if !allowInteraction { query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail }
|
|
134
|
+
var result: CFTypeRef?
|
|
135
|
+
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
136
|
+
if status == errSecItemNotFound { return nil }
|
|
137
|
+
guard status == errSecSuccess else { throw KeychainFailure(status: status) }
|
|
138
|
+
return (result as? Data).flatMap { String(data: $0, encoding: .utf8) }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private static func saveKeychain(_ key: String) throws {
|
|
142
|
+
let query = keychainQuery
|
|
143
|
+
if key.isEmpty {
|
|
144
|
+
let status = SecItemDelete(query as CFDictionary)
|
|
145
|
+
guard status == errSecSuccess || status == errSecItemNotFound else { throw KeychainFailure(status: status) }
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
let value = [kSecValueData as String: Data(key.utf8)]
|
|
149
|
+
var status = SecItemUpdate(query as CFDictionary, value as CFDictionary)
|
|
150
|
+
if status == errSecItemNotFound {
|
|
151
|
+
var item = query.merging(value) { _, new in new }
|
|
152
|
+
item[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
|
153
|
+
status = SecItemAdd(item as CFDictionary, nil)
|
|
118
154
|
}
|
|
155
|
+
guard status == errSecSuccess else { throw KeychainFailure(status: status) }
|
|
156
|
+
}
|
|
119
157
|
|
|
120
|
-
|
|
121
|
-
let
|
|
122
|
-
|
|
158
|
+
struct KeychainFailure: Error, LocalizedError {
|
|
159
|
+
let status: OSStatus
|
|
160
|
+
var errorDescription: String? { "Could not access the OpenAI key in Keychain (\(status))." }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static func childEnvironment(
|
|
164
|
+
base: [String: String], homePath: String,
|
|
165
|
+
keyLoader: (() -> String)? = nil
|
|
166
|
+
) -> [String: String] {
|
|
167
|
+
var result = base
|
|
168
|
+
let key = keyLoader?() ?? load(homePath: homePath, environment: base)
|
|
169
|
+
// RECORDINGS_API_KEY is also an alias in the Hasna service resolver. Keeping
|
|
170
|
+
// it would send a provider key to the service API as its authentication.
|
|
171
|
+
result.removeValue(forKey: "RECORDINGS_API_KEY")
|
|
172
|
+
if !key.isEmpty {
|
|
173
|
+
result["OPENAI_API_KEY"] = key
|
|
174
|
+
result["RECORDINGS_OPENAI_API_KEY"] = key
|
|
175
|
+
}
|
|
176
|
+
return result
|
|
123
177
|
}
|
|
124
178
|
|
|
125
179
|
static func saveLanguage(language: String, homePath: String) throws {
|
|
@@ -288,7 +288,7 @@ public final class ProjectStore: ObservableObject {
|
|
|
288
288
|
isReadyForRecording = false
|
|
289
289
|
let registrations: [(String, RecordingsCLI.CanonicalProject)]
|
|
290
290
|
do {
|
|
291
|
-
registrations = try await
|
|
291
|
+
registrations = try await BlockingOperation.runThrowing(qos: .utility) {
|
|
292
292
|
try original.projects.map { project in
|
|
293
293
|
let canonical = try RecordingsCLI.registerProject(
|
|
294
294
|
name: project.name,
|
|
@@ -297,7 +297,7 @@ public final class ProjectStore: ObservableObject {
|
|
|
297
297
|
)
|
|
298
298
|
return (project.id, canonical)
|
|
299
299
|
}
|
|
300
|
-
}
|
|
300
|
+
}
|
|
301
301
|
} catch {
|
|
302
302
|
let message = "Failed to register projects: \((error as? RecordingsCLI.Failure)?.message ?? error.localizedDescription)"
|
|
303
303
|
synchronizationError = message
|
|
@@ -347,9 +347,9 @@ public final class ProjectStore: ObservableObject {
|
|
|
347
347
|
let local = RecProject(name: name, path: path, systemPrompt: systemPrompt, color: color)
|
|
348
348
|
let canonical: RecordingsCLI.CanonicalProject
|
|
349
349
|
do {
|
|
350
|
-
canonical = try await
|
|
350
|
+
canonical = try await BlockingOperation.runThrowing {
|
|
351
351
|
try RecordingsCLI.registerProject(name: local.name, path: local.registrationPath, home: home)
|
|
352
|
-
}
|
|
352
|
+
}
|
|
353
353
|
} catch {
|
|
354
354
|
persistenceError = "Failed to register project: \((error as? RecordingsCLI.Failure)?.message ?? error.localizedDescription)"
|
|
355
355
|
throw error
|
|
@@ -100,12 +100,24 @@ public struct Recording: Identifiable, Codable, Sendable, Equatable {
|
|
|
100
100
|
|
|
101
101
|
public static func parseDate(_ value: String) -> Date? {
|
|
102
102
|
guard !value.isEmpty else { return nil }
|
|
103
|
+
// PostgreSQL NOW()::text uses a space and may abbreviate the UTC offset
|
|
104
|
+
// to hours, for example "2026-09-06 10:52:42.523193+00".
|
|
105
|
+
var internetDate = value
|
|
106
|
+
if value.range(of: #"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{1,6})?[+-]\d{2}(?::?\d{2})?$"#,
|
|
107
|
+
options: .regularExpression) != nil {
|
|
108
|
+
guard value.range(of: #"[+-](?:[01]\d|2[0-3])(?::?[0-5]\d)?$"#,
|
|
109
|
+
options: .regularExpression) != nil else { return nil }
|
|
110
|
+
internetDate = value.replacingOccurrences(of: " ", with: "T")
|
|
111
|
+
if internetDate.range(of: #"[+-]\d{2}$"#, options: .regularExpression) != nil {
|
|
112
|
+
internetDate += ":00"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
103
115
|
let withFrac = ISO8601DateFormatter()
|
|
104
116
|
withFrac.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
105
|
-
if let d = withFrac.date(from:
|
|
117
|
+
if let d = withFrac.date(from: internetDate) { return d }
|
|
106
118
|
let plain = ISO8601DateFormatter()
|
|
107
119
|
plain.formatOptions = [.withInternetDateTime]
|
|
108
|
-
if let d = plain.date(from:
|
|
120
|
+
if let d = plain.date(from: internetDate) { return d }
|
|
109
121
|
// SQLite "YYYY-MM-DD HH:MM:SS" fallback.
|
|
110
122
|
let df = DateFormatter()
|
|
111
123
|
df.locale = Locale(identifier: "en_US_POSIX")
|