create-claude-cabinet 0.34.0 → 0.34.1
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/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import Cocoa
|
|
2
|
+
|
|
3
|
+
class InputDialog: NSObject {
|
|
4
|
+
func run(prompt: String) -> String? {
|
|
5
|
+
let alert = NSAlert()
|
|
6
|
+
alert.messageText = prompt
|
|
7
|
+
alert.addButton(withTitle: "OK")
|
|
8
|
+
alert.addButton(withTitle: "Cancel")
|
|
9
|
+
|
|
10
|
+
let stack = NSStackView(frame: NSRect(x: 0, y: 0, width: 300, height: 50))
|
|
11
|
+
stack.orientation = .vertical
|
|
12
|
+
stack.spacing = 8
|
|
13
|
+
|
|
14
|
+
let secureField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24))
|
|
15
|
+
secureField.placeholderString = "Enter passphrase"
|
|
16
|
+
let plainField = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24))
|
|
17
|
+
plainField.placeholderString = "Enter passphrase"
|
|
18
|
+
plainField.isHidden = true
|
|
19
|
+
|
|
20
|
+
let toggle = NSButton(checkboxWithTitle: "Show passphrase", target: nil, action: nil)
|
|
21
|
+
|
|
22
|
+
class ToggleHandler: NSObject {
|
|
23
|
+
let secureField: NSSecureTextField
|
|
24
|
+
let plainField: NSTextField
|
|
25
|
+
let toggle: NSButton
|
|
26
|
+
init(s: NSSecureTextField, p: NSTextField, t: NSButton) {
|
|
27
|
+
secureField = s; plainField = p; toggle = t
|
|
28
|
+
}
|
|
29
|
+
@objc func toggled(_ sender: Any?) {
|
|
30
|
+
if toggle.state == .on {
|
|
31
|
+
plainField.stringValue = secureField.stringValue
|
|
32
|
+
secureField.isHidden = true
|
|
33
|
+
plainField.isHidden = false
|
|
34
|
+
plainField.becomeFirstResponder()
|
|
35
|
+
} else {
|
|
36
|
+
secureField.stringValue = plainField.stringValue
|
|
37
|
+
plainField.isHidden = true
|
|
38
|
+
secureField.isHidden = false
|
|
39
|
+
secureField.becomeFirstResponder()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
let handler = ToggleHandler(s: secureField, p: plainField, t: toggle)
|
|
44
|
+
toggle.target = handler
|
|
45
|
+
toggle.action = #selector(ToggleHandler.toggled(_:))
|
|
46
|
+
|
|
47
|
+
stack.addArrangedSubview(secureField)
|
|
48
|
+
stack.addArrangedSubview(plainField)
|
|
49
|
+
stack.addArrangedSubview(toggle)
|
|
50
|
+
|
|
51
|
+
alert.accessoryView = stack
|
|
52
|
+
alert.window.initialFirstResponder = secureField
|
|
53
|
+
|
|
54
|
+
let response = alert.runModal()
|
|
55
|
+
if response == .alertFirstButtonReturn {
|
|
56
|
+
return toggle.state == .on ? plainField.stringValue : secureField.stringValue
|
|
57
|
+
}
|
|
58
|
+
return nil
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let app = NSApplication.shared
|
|
63
|
+
app.setActivationPolicy(.accessory)
|
|
64
|
+
|
|
65
|
+
let dialog = InputDialog()
|
|
66
|
+
let prompt = CommandLine.arguments.count > 1 ? CommandLine.arguments[1] : "Enter passphrase"
|
|
67
|
+
if let value = dialog.run(prompt: prompt) {
|
|
68
|
+
print(value)
|
|
69
|
+
} else {
|
|
70
|
+
fputs("CANCELLED\n", stderr)
|
|
71
|
+
exit(1)
|
|
72
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
3
6
|
|
|
4
7
|
const execFileAsync = promisify(execFile);
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
9
|
|
|
6
10
|
export class DialogCancelledError extends Error {
|
|
7
11
|
constructor() { super('User cancelled dialog'); this.code = 'CANCELLED'; }
|
|
@@ -27,7 +31,34 @@ function escapeAppleScript(str) {
|
|
|
27
31
|
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
async function
|
|
34
|
+
async function ensureNativeDialog() {
|
|
35
|
+
const cacheDir = join(process.env.HOME || '/tmp', '.claude-cabinet', 'bin');
|
|
36
|
+
const binPath = join(cacheDir, 'secure-input-dialog');
|
|
37
|
+
if (existsSync(binPath)) return binPath;
|
|
38
|
+
|
|
39
|
+
const srcPath = join(__dirname, 'secure-input-dialog.swift');
|
|
40
|
+
if (!existsSync(srcPath)) return null;
|
|
41
|
+
|
|
42
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
43
|
+
try {
|
|
44
|
+
await execFileAsync('swiftc', ['-o', binPath, srcPath], { timeout: 30000 });
|
|
45
|
+
return binPath;
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function captureViaNativeDialog(prompt, binPath) {
|
|
52
|
+
try {
|
|
53
|
+
const { stdout } = await execFileAsync(binPath, [prompt], { timeout: 120000 });
|
|
54
|
+
return stdout.trim();
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (err.stderr?.includes('CANCELLED')) throw new DialogCancelledError();
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function captureViaAppleScript(prompt) {
|
|
31
62
|
const escaped = escapeAppleScript(prompt);
|
|
32
63
|
const script = `display dialog "${escaped}" with hidden answer default answer ""`;
|
|
33
64
|
try {
|
|
@@ -43,6 +74,12 @@ async function captureViaMacOS(prompt) {
|
|
|
43
74
|
}
|
|
44
75
|
}
|
|
45
76
|
|
|
77
|
+
async function captureViaMacOS(prompt) {
|
|
78
|
+
const binPath = await ensureNativeDialog();
|
|
79
|
+
if (binPath) return captureViaNativeDialog(prompt, binPath);
|
|
80
|
+
return captureViaAppleScript(prompt);
|
|
81
|
+
}
|
|
82
|
+
|
|
46
83
|
async function captureViaZenity(prompt) {
|
|
47
84
|
try {
|
|
48
85
|
const { stdout } = await execFileAsync('zenity', ['--password', '--title', prompt]);
|
|
@@ -83,6 +120,12 @@ async function captureViaPowerShell(prompt) {
|
|
|
83
120
|
|
|
84
121
|
/**
|
|
85
122
|
* Capture a secret via a secure OS dialog.
|
|
123
|
+
*
|
|
124
|
+
* On macOS: uses a native Cocoa dialog with a "Show passphrase" toggle
|
|
125
|
+
* checkbox (compiled from secure-input-dialog.swift on first use, cached
|
|
126
|
+
* at ~/.claude-cabinet/bin/). Falls back to AppleScript (masked, no toggle)
|
|
127
|
+
* if Swift compilation fails.
|
|
128
|
+
*
|
|
86
129
|
* @param {string} prompt - what to ask
|
|
87
130
|
* @param {{ requireSecure?: boolean }} opts
|
|
88
131
|
* requireSecure (default false): when true, REFUSE to fall back to the
|
|
@@ -100,7 +143,7 @@ export async function captureSecureInput(prompt, { requireSecure = false } = {})
|
|
|
100
143
|
return await captureViaZenity(prompt);
|
|
101
144
|
} catch (err) {
|
|
102
145
|
if (err instanceof DialogUnavailableError) {
|
|
103
|
-
if (requireSecure) throw err;
|
|
146
|
+
if (requireSecure) throw err;
|
|
104
147
|
return captureViaTerminal(prompt);
|
|
105
148
|
}
|
|
106
149
|
throw err;
|