@hasna/recordings 0.1.17 → 0.1.19
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/dist/cli/index.js +2 -2
- package/dist/mcp/index.js +2 -2
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +1 -2
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +50 -14
- package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +38 -0
package/dist/cli/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var __require = import.meta.require;
|
|
|
7
7
|
var require_package = __commonJS((exports, module) => {
|
|
8
8
|
module.exports = {
|
|
9
9
|
name: "@hasna/recordings",
|
|
10
|
-
version: "0.1.
|
|
10
|
+
version: "0.1.19",
|
|
11
11
|
type: "module",
|
|
12
12
|
description: "Speech-to-text recording tool with MCP and CLI \u2014 records, transcribes, and optionally enhances text using AI",
|
|
13
13
|
repository: {
|
|
@@ -10466,7 +10466,7 @@ async function processText(rawText, config, systemPrompt) {
|
|
|
10466
10466
|
}
|
|
10467
10467
|
|
|
10468
10468
|
// src/version.ts
|
|
10469
|
-
var VERSION = "0.1.
|
|
10469
|
+
var VERSION = "0.1.19";
|
|
10470
10470
|
|
|
10471
10471
|
// src/cli/index.ts
|
|
10472
10472
|
var program = new Command;
|
package/dist/mcp/index.js
CHANGED
|
@@ -21,7 +21,7 @@ var __require = import.meta.require;
|
|
|
21
21
|
var require_package = __commonJS((exports, module) => {
|
|
22
22
|
module.exports = {
|
|
23
23
|
name: "@hasna/recordings",
|
|
24
|
-
version: "0.1.
|
|
24
|
+
version: "0.1.19",
|
|
25
25
|
type: "module",
|
|
26
26
|
description: "Speech-to-text recording tool with MCP and CLI \u2014 records, transcribes, and optionally enhances text using AI",
|
|
27
27
|
repository: {
|
|
@@ -14983,7 +14983,7 @@ async function processText(rawText, config, systemPrompt) {
|
|
|
14983
14983
|
}
|
|
14984
14984
|
|
|
14985
14985
|
// src/version.ts
|
|
14986
|
-
var VERSION = "0.1.
|
|
14986
|
+
var VERSION = "0.1.19";
|
|
14987
14987
|
|
|
14988
14988
|
// src/mcp/index.ts
|
|
14989
14989
|
var config = loadConfig();
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.19";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -237,11 +237,10 @@ public struct MenuBarPopover: View {
|
|
|
237
237
|
showProject: filterProjectId == nil,
|
|
238
238
|
isCopied: copiedIndex == i
|
|
239
239
|
) {
|
|
240
|
-
NSPasteboard.general.clearContents()
|
|
241
|
-
NSPasteboard.general.setString(item.displayText, forType: .string)
|
|
242
240
|
withAnimation(.easeInOut(duration: 0.15)) {
|
|
243
241
|
copiedIndex = i
|
|
244
242
|
}
|
|
243
|
+
engine.pasteIntoFrontApp(item.displayText)
|
|
245
244
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) {
|
|
246
245
|
withAnimation { if copiedIndex == i { copiedIndex = nil } }
|
|
247
246
|
}
|
|
@@ -50,6 +50,12 @@ public enum RecordingTrigger {
|
|
|
50
50
|
case keyboardShortcut
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
struct PasteTargetCandidate: Equatable, Sendable {
|
|
54
|
+
let pid: pid_t
|
|
55
|
+
let bundleIdentifier: String?
|
|
56
|
+
let isRegularApp: Bool
|
|
57
|
+
}
|
|
58
|
+
|
|
53
59
|
private actor PCMStreamState {
|
|
54
60
|
private var recordedPCM = Data()
|
|
55
61
|
private var pendingChunk = Data()
|
|
@@ -429,6 +435,7 @@ public final class RecordingEngine: ObservableObject {
|
|
|
429
435
|
|
|
430
436
|
let curMode = mode
|
|
431
437
|
let targetAppBundleIdentifier = targetAppBundleIdentifier
|
|
438
|
+
let targetAppPid = targetAppPid
|
|
432
439
|
let activeProjectId = projectStore?.settings.activeProjectId
|
|
433
440
|
let activeProjectName = projectStore?.activeProject?.name
|
|
434
441
|
let audioPath = activeAudioPath
|
|
@@ -465,6 +472,7 @@ public final class RecordingEngine: ObservableObject {
|
|
|
465
472
|
audioPath: audioPath,
|
|
466
473
|
curMode: curMode,
|
|
467
474
|
targetAppBundleIdentifier: targetAppBundleIdentifier,
|
|
475
|
+
targetAppPid: targetAppPid,
|
|
468
476
|
activeProjectId: activeProjectId,
|
|
469
477
|
activeProjectName: activeProjectName
|
|
470
478
|
)
|
|
@@ -485,7 +493,7 @@ public final class RecordingEngine: ObservableObject {
|
|
|
485
493
|
return trimmed.count < 12 || words.count <= 2
|
|
486
494
|
}
|
|
487
495
|
|
|
488
|
-
private func finishWithText(_ text: String, curMode: RecordingMode, targetAppBundleIdentifier: String?, activeProjectId: String?, activeProjectName: String?) {
|
|
496
|
+
private func finishWithText(_ text: String, curMode: RecordingMode, targetAppBundleIdentifier: String?, targetAppPid: pid_t?, activeProjectId: String?, activeProjectName: String?) {
|
|
489
497
|
log("finishWithText mode=\(curMode.rawValue) chars=\(text.count)")
|
|
490
498
|
if curMode == .command {
|
|
491
499
|
runCommandMode(instruction: text)
|
|
@@ -494,7 +502,7 @@ public final class RecordingEngine: ObservableObject {
|
|
|
494
502
|
|
|
495
503
|
let shortcutText = voiceShortcuts?.match(text)
|
|
496
504
|
let output = shortcutText ?? text
|
|
497
|
-
pasteIntoFrontApp(output, targetAppBundleIdentifier: targetAppBundleIdentifier)
|
|
505
|
+
pasteIntoFrontApp(output, targetAppBundleIdentifier: targetAppBundleIdentifier, targetAppPid: targetAppPid)
|
|
498
506
|
recentTranscriptions.insert(
|
|
499
507
|
TranscriptionResult(
|
|
500
508
|
rawText: text,
|
|
@@ -576,7 +584,7 @@ public final class RecordingEngine: ObservableObject {
|
|
|
576
584
|
|
|
577
585
|
// MARK: - Fallback Transcription
|
|
578
586
|
|
|
579
|
-
private func fallbackTranscribe(audioPath: String, curMode: RecordingMode, targetAppBundleIdentifier: String?, activeProjectId: String?, activeProjectName: String?) {
|
|
587
|
+
private func fallbackTranscribe(audioPath: String, curMode: RecordingMode, targetAppBundleIdentifier: String?, targetAppPid: pid_t?, activeProjectId: String?, activeProjectName: String?) {
|
|
580
588
|
let homePath = home
|
|
581
589
|
|
|
582
590
|
isTranscribing = true
|
|
@@ -608,6 +616,7 @@ public final class RecordingEngine: ObservableObject {
|
|
|
608
616
|
text,
|
|
609
617
|
curMode: curMode,
|
|
610
618
|
targetAppBundleIdentifier: targetAppBundleIdentifier,
|
|
619
|
+
targetAppPid: targetAppPid,
|
|
611
620
|
activeProjectId: activeProjectId,
|
|
612
621
|
activeProjectName: activeProjectName
|
|
613
622
|
)
|
|
@@ -698,8 +707,8 @@ public final class RecordingEngine: ObservableObject {
|
|
|
698
707
|
|
|
699
708
|
// MARK: - Paste
|
|
700
709
|
|
|
701
|
-
func pasteIntoFrontApp(_ text: String, targetAppBundleIdentifier: String? = nil) {
|
|
702
|
-
log("paste requested chars=\(text.count) target=\(targetAppBundleIdentifier ?? "nil") accessibility=\(AXIsProcessTrusted())")
|
|
710
|
+
func pasteIntoFrontApp(_ text: String, targetAppBundleIdentifier: String? = nil, targetAppPid: pid_t? = nil) {
|
|
711
|
+
log("paste requested chars=\(text.count) target=\(targetAppBundleIdentifier ?? "nil") pid=\(targetAppPid.map(String.init) ?? "nil") accessibility=\(AXIsProcessTrusted())")
|
|
703
712
|
let pb = NSPasteboard.general
|
|
704
713
|
pb.clearContents()
|
|
705
714
|
pb.setString(text, forType: .string)
|
|
@@ -715,13 +724,22 @@ public final class RecordingEngine: ObservableObject {
|
|
|
715
724
|
|
|
716
725
|
let myPID = ProcessInfo.processInfo.processIdentifier
|
|
717
726
|
let runningApps = NSWorkspace.shared.runningApplications
|
|
718
|
-
let
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
}
|
|
727
|
+
let candidates = runningApps.map {
|
|
728
|
+
PasteTargetCandidate(
|
|
729
|
+
pid: $0.processIdentifier,
|
|
730
|
+
bundleIdentifier: $0.bundleIdentifier,
|
|
731
|
+
isRegularApp: $0.activationPolicy == .regular
|
|
732
|
+
)
|
|
733
|
+
}
|
|
734
|
+
let selectedTarget = Self.selectPasteTarget(
|
|
735
|
+
candidates: candidates,
|
|
736
|
+
currentPid: myPID,
|
|
737
|
+
targetBundleIdentifier: targetAppBundleIdentifier,
|
|
738
|
+
targetPid: targetAppPid
|
|
739
|
+
)
|
|
740
|
+
let targetApp = selectedTarget.flatMap { selected in
|
|
741
|
+
runningApps.first { $0.processIdentifier == selected.pid }
|
|
742
|
+
}
|
|
725
743
|
|
|
726
744
|
guard let app = targetApp else {
|
|
727
745
|
log("paste target app not found")
|
|
@@ -729,8 +747,8 @@ public final class RecordingEngine: ObservableObject {
|
|
|
729
747
|
return
|
|
730
748
|
}
|
|
731
749
|
|
|
732
|
-
// Activate
|
|
733
|
-
app.activate()
|
|
750
|
+
// Activate the exact app that owned focus when recording started, then paste after focus settles.
|
|
751
|
+
app.activate(options: [.activateIgnoringOtherApps])
|
|
734
752
|
|
|
735
753
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
736
754
|
let src = CGEventSource(stateID: .hidSystemState)
|
|
@@ -746,6 +764,24 @@ public final class RecordingEngine: ObservableObject {
|
|
|
746
764
|
}
|
|
747
765
|
}
|
|
748
766
|
|
|
767
|
+
nonisolated static func selectPasteTarget(
|
|
768
|
+
candidates: [PasteTargetCandidate],
|
|
769
|
+
currentPid: pid_t,
|
|
770
|
+
targetBundleIdentifier: String?,
|
|
771
|
+
targetPid: pid_t?
|
|
772
|
+
) -> PasteTargetCandidate? {
|
|
773
|
+
candidates.first {
|
|
774
|
+
guard let targetPid else { return false }
|
|
775
|
+
return $0.pid == targetPid && $0.pid != currentPid
|
|
776
|
+
} ?? candidates.first {
|
|
777
|
+
guard $0.pid != currentPid, $0.isRegularApp else { return false }
|
|
778
|
+
guard let targetBundleIdentifier else { return false }
|
|
779
|
+
return $0.bundleIdentifier == targetBundleIdentifier
|
|
780
|
+
} ?? candidates.first {
|
|
781
|
+
$0.isRegularApp && $0.pid != currentPid
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
749
785
|
private func ensureAccessibilityPermission(prompt: Bool) -> Bool {
|
|
750
786
|
if AXIsProcessTrusted() {
|
|
751
787
|
return true
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import Testing
|
|
2
|
+
@testable import RecordingsLib
|
|
3
|
+
|
|
4
|
+
struct PasteTargetTests {
|
|
5
|
+
@Test("paste target prefers captured process id over bundle fallback")
|
|
6
|
+
func prefersCapturedPid() {
|
|
7
|
+
let candidates = [
|
|
8
|
+
PasteTargetCandidate(pid: 10, bundleIdentifier: "com.editor", isRegularApp: true),
|
|
9
|
+
PasteTargetCandidate(pid: 20, bundleIdentifier: "com.editor", isRegularApp: true),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
let selected = RecordingEngine.selectPasteTarget(
|
|
13
|
+
candidates: candidates,
|
|
14
|
+
currentPid: 99,
|
|
15
|
+
targetBundleIdentifier: "com.editor",
|
|
16
|
+
targetPid: 20
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
#expect(selected?.pid == 20)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Test("paste target ignores current app when falling back")
|
|
23
|
+
func ignoresCurrentApp() {
|
|
24
|
+
let candidates = [
|
|
25
|
+
PasteTargetCandidate(pid: 99, bundleIdentifier: "com.hasna.recordings", isRegularApp: true),
|
|
26
|
+
PasteTargetCandidate(pid: 30, bundleIdentifier: "com.notes", isRegularApp: true),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
let selected = RecordingEngine.selectPasteTarget(
|
|
30
|
+
candidates: candidates,
|
|
31
|
+
currentPid: 99,
|
|
32
|
+
targetBundleIdentifier: nil,
|
|
33
|
+
targetPid: nil
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
#expect(selected?.pid == 30)
|
|
37
|
+
}
|
|
38
|
+
}
|