@hellolynk-ai/codex-plugin 0.2.104-macos.1 → 0.2.104

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 CHANGED
@@ -35,35 +35,35 @@
35
35
 
36
36
  Codex、OpenClaw、TRAE Work、WorkBuddy。装哪个客户端,就选哪个包。同一个账号下,它们共用你的灵信号。
37
37
 
38
- 源码见 [Codex 客户端源码](https://github.com/haoyun18881-beep/HelloLynk/tree/main/clients/codex-macos),完整介绍见 [HelloLynk on GitHub](https://github.com/haoyun18881-beep/HelloLynk)。
38
+ 源码见 [Codex 客户端源码](https://github.com/haoyun18881-beep/HelloLynk/tree/main/clients/codex),完整介绍见 [HelloLynk on GitHub](https://github.com/haoyun18881-beep/HelloLynk)。
39
39
 
40
40
  ## 前提
41
41
 
42
- Apple 芯片、macOS 13 或更高版本、Node.js 22.16 或更高版本,以及安装在「应用程序」中的 ChatGPT(已打开过 Codex)。
42
+ Windows 10/11、Node.js 22.16 或更高版本、PowerShell 7,以及打开过一次的 Codex。
43
43
 
44
44
  ## 安装
45
45
 
46
46
  1. 打开 [HelloLynk 官网(Official site)](https://hellolynk.com)。新用户注册账号;已有账号登录后,查看自己的公开灵信号。
47
47
  2. 两种装法任选一种。
48
48
 
49
- 推荐用 npm,在终端运行:
49
+ 用安装包,在官网或 [GitHub Release](https://github.com/haoyun18881-beep/HelloLynk/releases) 下载 Codex 安装包,完整解压,在包含 `package` 文件夹的目录打开 PowerShell,运行:
50
50
 
51
- ```sh
52
- npm install -g @hellolynk-ai/codex-plugin@macos
53
- hellolynk-codex init
51
+ ```powershell
52
+ node .\package\bin\hellolynk-client-setup.mjs init
54
53
  ```
55
54
 
56
- 用安装包,在官网或 [GitHub Release](https://github.com/haoyun18881-beep/HelloLynk/releases) 下载 Codex for Mac 安装包,完整解压,在包含 `package` 文件夹的目录打开 终端,运行:
55
+ 用 npm,在 PowerShell 运行:
57
56
 
58
- ```sh
59
- node ./package/bin/hellolynk-client-setup.mjs init
57
+ ```powershell
58
+ npm install -g @hellolynk-ai/codex-plugin
59
+ hellolynk-codex init
60
60
  ```
61
61
 
62
62
  3. 完全退出并重新打开 Codex。
63
63
 
64
64
  可以把本说明交给你的本地 AI,请它帮你安装;用安装包时,把解压目录也告诉它。
65
65
 
66
- 下文命令按安装包写法给出。用 npm 装的,把 `node ./package/bin/hellolynk-client-setup.mjs` 换成 `hellolynk-codex`,在任意目录运行。
66
+ 下文命令按安装包写法给出。用 npm 装的,把 `node .\package\bin\hellolynk-client-setup.mjs` 换成 `hellolynk-codex`,在任意目录运行。
67
67
 
68
68
  ## 首次连接
69
69
 
@@ -73,8 +73,8 @@ Apple 芯片、macOS 13 或更高版本、Node.js 22.16 或更高版本,以及
73
73
 
74
74
  也可以让 AI 用命令连接,把示例换成自己的公开灵信号:
75
75
 
76
- ```sh
77
- node ./package/bin/hellolynk-client-setup.mjs connect --hellolynk-id hellolynk:intl:your-name
76
+ ```powershell
77
+ node .\package\bin\hellolynk-client-setup.mjs connect --hellolynk-id hellolynk:intl:your-name
78
78
  ```
79
79
 
80
80
  首次连接这台电脑时,命令会给出官网确认链接。AI 把链接给用户;用户确认后,AI 继续执行同一条连接命令。
@@ -83,20 +83,20 @@ node ./package/bin/hellolynk-client-setup.mjs connect --hellolynk-id hellolynk:i
83
83
 
84
84
  插件会自动更新,不用操作。要手动更新,下载新版安装包,在包含 `package` 文件夹的目录运行:
85
85
 
86
- ```sh
87
- node ./package/bin/hellolynk-client-setup.mjs setup
86
+ ```powershell
87
+ node .\package\bin\hellolynk-client-setup.mjs setup
88
88
  ```
89
89
 
90
- 用 npm 装的,先运行 `npm install -g @hellolynk-ai/codex-plugin@macos` 装上新版,再运行 `hellolynk-codex setup`。
90
+ 用 npm 装的,先运行 `npm install -g @hellolynk-ai/codex-plugin` 装上新版,再运行 `hellolynk-codex setup`。
91
91
 
92
92
  ## 卸载
93
93
 
94
- 在包含 `package` 文件夹的目录运行 `node ./package/bin/hellolynk-client-setup.mjs uninstall`。账号、联系人和聊天记录留在这台电脑上,重新安装后不用重新连接。
94
+ 在包含 `package` 文件夹的目录运行 `node .\package\bin\hellolynk-client-setup.mjs uninstall`。账号、联系人和聊天记录留在这台电脑上,重新安装后不用重新连接。
95
95
 
96
96
  用 npm 装的,运行 `hellolynk-codex uninstall`,再运行 `npm uninstall -g @hellolynk-ai/codex-plugin`。
97
97
 
98
98
  ## 求助
99
99
 
100
- 运行 `node ./package/bin/hellolynk-client-setup.mjs doctor` 检查安装,把结果和错误码发到 [haoyun18881@gmail.com](mailto:haoyun18881@gmail.com)。
100
+ 运行 `node .\package\bin\hellolynk-client-setup.mjs doctor` 检查安装,把结果和错误码发到 [haoyun18881@gmail.com](mailto:haoyun18881@gmail.com)。
101
101
 
102
102
  许可:PolyForm Shield License 1.0.0,只覆盖本包内的客户端源码,完整条款见 LICENSE。
File without changes
@@ -2,7 +2,7 @@
2
2
  "contract": "hellolynk-client-npm-pack-manifest-v1",
3
3
  "client": "codex",
4
4
  "packageName": "@hellolynk-ai/codex-plugin",
5
- "version": "0.2.104-macos.1",
5
+ "version": "0.2.104",
6
6
  "workbench": {
7
7
  "path": "plugins/hellolynk-codex/runtime/workbench/hellolynk-workbench-build.json",
8
8
  "contract": "hellolynk-shared-workbench-build-v1",
@@ -32,8 +32,8 @@
32
32
  },
33
33
  {
34
34
  "path": "package.json",
35
- "bytes": 1776,
36
- "sha256": "9485c043ba6f164976d9256d03da20c8db9a5c688d10359fc006a5b17ffeb917"
35
+ "bytes": 1713,
36
+ "sha256": "f23764582fac615af1adb4865b2559084a04fd18ca146c7c3f26d90e64709a92"
37
37
  },
38
38
  {
39
39
  "path": "plugins/hellolynk-codex/.app.json",
@@ -47,8 +47,8 @@
47
47
  },
48
48
  {
49
49
  "path": "plugins/hellolynk-codex/.mcp.json",
50
- "bytes": 438,
51
- "sha256": "510ae3b6f417c1cd8e4712704d9e76643d30e7b3d1a8d3363dda19eb30fe4580"
50
+ "bytes": 373,
51
+ "sha256": "c349aeb9545e8ed97f2eb5b7f4809880dae2c545b01ec049057d8ebd3a2221cc"
52
52
  },
53
53
  {
54
54
  "path": "plugins/hellolynk-codex/assets/hellolynk-mark.png",
@@ -115,16 +115,6 @@
115
115
  "bytes": 24643,
116
116
  "sha256": "43d576d9f774409f4a735793c1503a6f397e787b10c59a4ac9cc9257299f5efe"
117
117
  },
118
- {
119
- "path": "plugins/hellolynk-codex/runtime/bootstrap/platform/darwin/hellolynk-keychain-helper.bin",
120
- "bytes": 110784,
121
- "sha256": "f1a782c08229ebc0e8d1704b6390e91c5695e084b4df0a1a6dc043f6b23700ab"
122
- },
123
- {
124
- "path": "plugins/hellolynk-codex/runtime/bootstrap/platform/darwin/hellolynk-keychain-helper.swift",
125
- "bytes": 7771,
126
- "sha256": "5d933589a1d739217bc164f73ab9afadc9e27cc512e1c00c0be4b38975fb6d5b"
127
- },
128
118
  {
129
119
  "path": "plugins/hellolynk-codex/runtime/bootstrap/platform/windows/hellolynk-host-protected-store.ps1",
130
120
  "bytes": 152700,
@@ -407,23 +397,13 @@
407
397
  },
408
398
  {
409
399
  "path": "plugins/hellolynk-codex/runtime/neutral-host-release-manifest.json",
410
- "bytes": 14698,
411
- "sha256": "96b171b53d91da097e2c36c36dbe9e6e6b0e2ca0fb34c1741b0df2cb5ac38e9b"
400
+ "bytes": 14038,
401
+ "sha256": "0e89b6c9c41d49aadc8c02d9d1dee76ef7a7bc41e9e54db4b5932b992619829e"
412
402
  },
413
403
  {
414
404
  "path": "plugins/hellolynk-codex/runtime/neutral-host-release-pin.json",
415
405
  "bytes": 146,
416
- "sha256": "f68a4fe31eba6adc1c31aa1c2ccf399e584e02ea9556371f2b8363cf5633c7f3"
417
- },
418
- {
419
- "path": "plugins/hellolynk-codex/runtime/platform/darwin/hellolynk-keychain-helper.bin",
420
- "bytes": 110784,
421
- "sha256": "f1a782c08229ebc0e8d1704b6390e91c5695e084b4df0a1a6dc043f6b23700ab"
422
- },
423
- {
424
- "path": "plugins/hellolynk-codex/runtime/platform/darwin/hellolynk-keychain-helper.swift",
425
- "bytes": 7771,
426
- "sha256": "5d933589a1d739217bc164f73ab9afadc9e27cc512e1c00c0be4b38975fb6d5b"
406
+ "sha256": "8390f0cef1c6ce5e44ededf084f626a8e7dc91cc5690370365e3b93aa637bf7c"
427
407
  },
428
408
  {
429
409
  "path": "plugins/hellolynk-codex/runtime/platform/windows/configure-agent.ps1",
@@ -593,7 +573,7 @@
593
573
  {
594
574
  "path": "plugins/hellolynk-codex/server/codex-neutral-production.mjs",
595
575
  "bytes": 27551,
596
- "sha256": "2c2f209339385cc14f27fe985f4deb75f4bd48e689035311847cc3b08e36ac52"
576
+ "sha256": "e1106264be25cd79fc12f1b774726089eead0f7e237aff231cd739f40e0c1f40"
597
577
  },
598
578
  {
599
579
  "path": "plugins/hellolynk-codex/server/mcp-proxy.mjs",
@@ -647,8 +627,8 @@
647
627
  },
648
628
  {
649
629
  "path": "README.md",
650
- "bytes": 4578,
651
- "sha256": "95cd3887026e6a275dce26e50667c1422fad356b59409ce15d6f84ef2207ed92"
630
+ "bytes": 4536,
631
+ "sha256": "6876323b3a86b6cce25b84f6c49232553ca4f8b9abd0c12f595a9b3b971f4e76"
652
632
  },
653
633
  {
654
634
  "path": "tools/hellolynk-activate-neutral-host-clients.mjs",
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "contract": "hellolynk-client-npm-pack-pin-v1",
3
- "manifestSha256": "bb5217cd79d40f6b12e7361bc4f655ec7a1918b6f17d3138cfdcd0d6d7c25644"
3
+ "manifestSha256": "7fe9d14ce8e467f8ff5228dfbf339694cb47ef1c77f25bc63b022dcb3282a279"
4
4
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@hellolynk-ai/codex-plugin",
3
- "version": "0.2.104-macos.1",
3
+ "version": "0.2.104",
4
4
  "description": "HelloLynk Messaging for Codex: open conversations and let your AI message other people's AI assistants.",
5
5
  "homepage": "https://hellolynk.com",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/haoyun18881-beep/HelloLynk.git",
9
- "directory": "clients/codex-macos"
9
+ "directory": "clients/codex"
10
10
  },
11
11
  "bugs": {
12
12
  "url": "https://github.com/haoyun18881-beep/HelloLynk/issues"
@@ -20,10 +20,7 @@
20
20
  "license": "SEE LICENSE IN LICENSE",
21
21
  "type": "module",
22
22
  "os": [
23
- "darwin"
24
- ],
25
- "cpu": [
26
- "arm64"
23
+ "win32"
27
24
  ],
28
25
  "engines": {
29
26
  "node": ">=22.16.0"
@@ -50,8 +47,7 @@
50
47
  },
51
48
  "publishConfig": {
52
49
  "access": "public",
53
- "registry": "https://registry.npmjs.org/",
54
- "tag": "macos"
50
+ "registry": "https://registry.npmjs.org/"
55
51
  },
56
52
  "hellolynkClient": {
57
53
  "client": "codex",
@@ -1,20 +1,19 @@
1
- {
2
- "mcpServers": {
3
- "hellolynk-codex": {
4
- "command": "/Applications/ChatGPT.app/Contents/Resources/cua_node/bin/node",
5
- "args": [
6
- "./server/mcp-proxy.mjs"
7
- ],
8
- "cwd": ".",
9
- "env": {
10
- "HELLOLYNK_HOST_MODE": "neutral",
11
- "TMPDIR": "/tmp"
12
- },
13
- "env_vars": [
14
- "HELLOLYNK_NEUTRAL_STORE_ROOT",
15
- "CODEX_APP_TOOLS_PIPE_PATH",
16
- "CODEX_THREAD_ID"
17
- ]
18
- }
19
- }
20
- }
1
+ {
2
+ "mcpServers": {
3
+ "hellolynk-codex": {
4
+ "command": "node",
5
+ "args": [
6
+ "./server/mcp-proxy.mjs"
7
+ ],
8
+ "cwd": ".",
9
+ "env": {
10
+ "HELLOLYNK_HOST_MODE": "neutral"
11
+ },
12
+ "env_vars": [
13
+ "HELLOLYNK_NEUTRAL_STORE_ROOT",
14
+ "CODEX_APP_TOOLS_PIPE_PATH",
15
+ "CODEX_THREAD_ID"
16
+ ]
17
+ }
18
+ }
19
+ }
@@ -358,25 +358,9 @@
358
358
  "path": "bootstrap/platform/windows/hellolynk-host-protected-store.ps1",
359
359
  "sha256": "24357e8ce54090b4ec68e457f02c9e834a80f3273ef3c03a8c0fa7258797fb49"
360
360
  },
361
- {
362
- "path": "platform/darwin/hellolynk-keychain-helper.swift",
363
- "sha256": "5d933589a1d739217bc164f73ab9afadc9e27cc512e1c00c0be4b38975fb6d5b"
364
- },
365
- {
366
- "path": "platform/darwin/hellolynk-keychain-helper.bin",
367
- "sha256": "f1a782c08229ebc0e8d1704b6390e91c5695e084b4df0a1a6dc043f6b23700ab"
368
- },
369
- {
370
- "path": "bootstrap/platform/darwin/hellolynk-keychain-helper.swift",
371
- "sha256": "5d933589a1d739217bc164f73ab9afadc9e27cc512e1c00c0be4b38975fb6d5b"
372
- },
373
- {
374
- "path": "bootstrap/platform/darwin/hellolynk-keychain-helper.bin",
375
- "sha256": "f1a782c08229ebc0e8d1704b6390e91c5695e084b4df0a1a6dc043f6b23700ab"
376
- },
377
361
  {
378
362
  "path": "release/server/codex-neutral-production.mjs",
379
- "sha256": "2c2f209339385cc14f27fe985f4deb75f4bd48e689035311847cc3b08e36ac52"
363
+ "sha256": "e1106264be25cd79fc12f1b774726089eead0f7e237aff231cd739f40e0c1f40"
380
364
  },
381
365
  {
382
366
  "path": "release/server/neutral-host-adapter.mjs",
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "contract": "hellolynk-neutral-host-release-pin-v1",
3
- "manifestSha256": "96b171b53d91da097e2c36c36dbe9e6e6b0e2ca0fb34c1741b0df2cb5ac38e9b"
3
+ "manifestSha256": "0e89b6c9c41d49aadc8c02d9d1dee76ef7a7bc41e9e54db4b5932b992619829e"
4
4
  }
@@ -34,7 +34,7 @@ const TRUSTED_BOOTSTRAP_FILES = Object.freeze(new Map([
34
34
  ["runtime/bootstrap/core/hellolynk-client-core.mjs", "94bbad1d0ccee969bbffed176449e427188f1b892512a64167ab31f63d258e2b"],
35
35
  ["runtime/bootstrap/platform/windows/hellolynk-host-protected-store.ps1", "24357e8ce54090b4ec68e457f02c9e834a80f3273ef3c03a8c0fa7258797fb49"],
36
36
  ...(process.platform === "darwin" ? [
37
- ["runtime/bootstrap/platform/darwin/hellolynk-keychain-helper.bin", "f1a782c08229ebc0e8d1704b6390e91c5695e084b4df0a1a6dc043f6b23700ab"],
37
+ ["runtime/bootstrap/platform/darwin/hellolynk-keychain-helper.bin", "0000000000000000000000000000000000000000000000000000000000000000"],
38
38
  ] : []),
39
39
  ]));
40
40
 
@@ -1,149 +0,0 @@
1
- // Rebuild in this directory: xcrun swiftc -O -target arm64-apple-macos13.0 -module-name HelloLynkKeychain hellolynk-keychain-helper.swift -o hellolynk-keychain-helper.bin
2
- import AppKit
3
- import CryptoKit
4
- import Foundation
5
- import Security
6
-
7
- func fail(_ code: String) -> Never {
8
- FileHandle.standardError.write(Data((code + "\n").utf8))
9
- exit(1)
10
- }
11
-
12
- func requireNoGrantACL(_ path: String) {
13
- guard let acl = acl_get_file(path, ACL_TYPE_EXTENDED) else {
14
- if errno == ENOENT { return } // A first write creates the file with mode 0600.
15
- fail("macos_vault_acl_unavailable")
16
- }
17
- defer { acl_free(UnsafeMutableRawPointer(acl)) }
18
- var entry: acl_entry_t?
19
- var selector = ACL_FIRST_ENTRY
20
- while acl_get_entry(acl, Int32(selector.rawValue), &entry) == 0 {
21
- var tag = ACL_UNDEFINED_TAG
22
- guard acl_get_tag_type(entry, &tag) == 0, tag == ACL_EXTENDED_DENY else {
23
- fail("macos_vault_acl_invalid")
24
- }
25
- selector = ACL_NEXT_ENTRY
26
- }
27
- }
28
-
29
- // The caller verifies the release path's ancestors and package hashes. Walk
30
- // with lstat so a symlink or hard link cannot redirect a permission change.
31
- func secureRelease(_ path: String, verifyOnly: Bool) {
32
- var pending = [path]
33
- while let entry = pending.popLast() {
34
- var st = stat()
35
- guard lstat(entry, &st) == 0, st.st_uid == getuid() else { fail("macos_vault_release_invalid") }
36
- let type = st.st_mode & S_IFMT
37
- guard type == S_IFDIR || (type == S_IFREG && st.st_nlink == 1) else { fail("macos_vault_release_invalid") }
38
- let mode: mode_t = type == S_IFDIR || entry.hasSuffix(".bin") ? 0o700 : 0o600
39
- if (st.st_mode & 0o7777) != mode {
40
- guard !verifyOnly, chmod(entry, mode) == 0 else { fail("macos_vault_release_permissions_invalid") }
41
- }
42
- requireNoGrantACL(entry)
43
- if type == S_IFDIR {
44
- guard let names = try? FileManager.default.contentsOfDirectory(atPath: entry) else { fail("macos_vault_release_invalid") }
45
- pending.append(contentsOf: names.map { entry + "/" + $0 })
46
- }
47
- }
48
- }
49
-
50
- func masterKey(_ root: String, create: Bool) -> Data {
51
- let account = SHA256.hash(data: Data(root.utf8)).map { String(format: "%02x", $0) }.joined()
52
- let base: [String: Any] = [
53
- kSecClass as String: kSecClassGenericPassword,
54
- kSecAttrService as String: "com.hellolynk.codex.macos-vault.v1",
55
- kSecAttrAccount as String: account
56
- ]
57
- var lookup = base
58
- lookup[kSecReturnData as String] = true
59
- lookup[kSecMatchLimit as String] = kSecMatchLimitOne
60
- var result: CFTypeRef?
61
- let status = SecItemCopyMatching(lookup as CFDictionary, &result)
62
- if status == errSecSuccess {
63
- guard let key = result as? Data, key.count == 32 else { fail("macos_vault_key_invalid") }
64
- return key
65
- }
66
- guard status == errSecItemNotFound else { fail("macos_keychain_access_\(abs(Int(status)))") }
67
- guard create else { fail("macos_vault_key_missing") }
68
- let key = SymmetricKey(size: .bits256).withUnsafeBytes { Data($0) }
69
- var add = base
70
- add[kSecValueData as String] = key
71
- add[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
72
- let inserted = SecItemAdd(add as CFDictionary, nil)
73
- if inserted == errSecDuplicateItem { return masterKey(root, create: false) }
74
- guard inserted == errSecSuccess else { fail("macos_keychain_access_\(abs(Int(inserted)))") }
75
- return key
76
- }
77
-
78
- func nativeConfirmation() {
79
- let raw = FileHandle.standardInput.readDataToEndOfFile()
80
- guard raw.count <= 20480 * 6 + 8192,
81
- var v = (try? JSONSerialization.jsonObject(with: raw)) as? [String: Any],
82
- let body = v["displayText"] as? String, !body.isEmpty, body.utf8.count <= 20480,
83
- let encoded = v["hmacKey"] as? String, encoded.count == 43,
84
- let key = Data(base64Encoded: encoded.replacingOccurrences(of: "-", with: "+")
85
- .replacingOccurrences(of: "_", with: "/") + "="), key.count == 32,
86
- let recipient = v["recipientHelloLynkId"] as? String,
87
- v["contract"] as? String == "hellolynk-native-human-confirmation-v1",
88
- v["textDigest"] as? String == SHA256.hash(data: Data(body.utf8)).map({ String(format: "%02x", $0) }).joined()
89
- else { fail("human_confirmation_request_invalid") }
90
-
91
- var fields = ["contract", "actionId", "decision", "principalId", "delegateId", "hostInstallationId",
92
- "adapterId", "adapterInstanceId", "hostGeneration", "recipientHelloLynkId"]
93
- var label = "收件人:" + recipient
94
- if v["recipientDelegateId"] != nil || v["recipientInstanceDisplayName"] != nil {
95
- guard let name = v["recipientInstanceDisplayName"] as? String, !name.isEmpty, name.utf8.count <= 512,
96
- name.range(of: #"[\x00-\x1F\x7F-\x9F\u2028\u2029\u202A-\u202E\u2066-\u2069]"#, options: .regularExpression) == nil,
97
- v["recipientDelegateId"] is String else { fail("human_confirmation_request_invalid") }
98
- fields += ["recipientDelegateId", "recipientInstanceDisplayName"]
99
- label += "\n目标 AI:" + name
100
- }
101
- fields += ["clientMessageId", "textDigest", "createdAtMs", "expiresAtMs", "challenge"]
102
- var expected = Set(fields)
103
- expected.remove("decision")
104
- expected.formUnion(["displayText", "hmacKey"])
105
- guard expected == Set(v.keys) else { fail("human_confirmation_request_invalid") }
106
- let app = NSApplication.shared
107
- app.setActivationPolicy(.accessory)
108
- app.activate(ignoringOtherApps: true)
109
- let dialog = NSAlert()
110
- dialog.messageText = "是否允许 HelloLynk 发送以下文本?"
111
- dialog.informativeText = label
112
- dialog.addButton(withTitle: "取消")
113
- dialog.addButton(withTitle: "批准发送")
114
- let scroll = NSScrollView(frame: NSRect(x: 0, y: 0, width: 500, height: 230))
115
- scroll.hasVerticalScroller = true
116
- let view = NSTextView(frame: scroll.contentView.bounds)
117
- view.isEditable = false
118
- view.isRichText = false
119
- view.isVerticallyResizable = true
120
- view.autoresizingMask = [.width]
121
- view.textContainer?.widthTracksTextView = true
122
- view.string = body
123
- scroll.documentView = view
124
- dialog.accessoryView = scroll
125
- v["decision"] = dialog.runModal() == .alertSecondButtonReturn ? "approve" : "cancel"
126
- let message = fields.map { field -> String in
127
- if let text = v[field] as? String { return text }
128
- if ["createdAtMs","expiresAtMs"].contains(field), let n = v[field] as? NSNumber { return n.stringValue }
129
- fail("human_confirmation_request_invalid")
130
- }.joined(separator: "\n")
131
- let proof = HMAC<SHA256>.authenticationCode(for: Data(message.utf8), using: SymmetricKey(data: key))
132
- v["proof"] = proof.map { String(format: "%02x", $0) }.joined()
133
- for field in ["hmacKey", "challenge", "displayText"] { v.removeValue(forKey: field) }
134
- guard let output = try? JSONSerialization.data(withJSONObject: v) else { fail("human_confirmation_response_invalid") }
135
- FileHandle.standardOutput.write(output)
136
- }
137
-
138
- let args = Array(CommandLine.arguments.dropFirst())
139
- if args == ["confirm"] { nativeConfirmation() }
140
- else if args.count == 3, args[0] == "key", args[1].hasPrefix("/"), ["load","create"].contains(args[2]) {
141
- requireNoGrantACL(args[1])
142
- requireNoGrantACL(args[1] + "/protected-v1.sqlite")
143
- let key = masterKey(args[1], create: args[2] == "create")
144
- FileHandle(fileDescriptor: 3, closeOnDealloc: false).write(key)
145
- } else if args.count == 2, ["verify-tree", "secure-tree"].contains(args[0]), args[1].hasPrefix("/") {
146
- secureRelease(args[1], verifyOnly: args[0] == "verify-tree")
147
- } else if args.count == 2, args[0] == "verify-acl", args[1].hasPrefix("/") {
148
- requireNoGrantACL(args[1])
149
- } else { fail("macos_vault_helper_arguments_invalid") }
@@ -1,149 +0,0 @@
1
- // Rebuild in this directory: xcrun swiftc -O -target arm64-apple-macos13.0 -module-name HelloLynkKeychain hellolynk-keychain-helper.swift -o hellolynk-keychain-helper.bin
2
- import AppKit
3
- import CryptoKit
4
- import Foundation
5
- import Security
6
-
7
- func fail(_ code: String) -> Never {
8
- FileHandle.standardError.write(Data((code + "\n").utf8))
9
- exit(1)
10
- }
11
-
12
- func requireNoGrantACL(_ path: String) {
13
- guard let acl = acl_get_file(path, ACL_TYPE_EXTENDED) else {
14
- if errno == ENOENT { return } // A first write creates the file with mode 0600.
15
- fail("macos_vault_acl_unavailable")
16
- }
17
- defer { acl_free(UnsafeMutableRawPointer(acl)) }
18
- var entry: acl_entry_t?
19
- var selector = ACL_FIRST_ENTRY
20
- while acl_get_entry(acl, Int32(selector.rawValue), &entry) == 0 {
21
- var tag = ACL_UNDEFINED_TAG
22
- guard acl_get_tag_type(entry, &tag) == 0, tag == ACL_EXTENDED_DENY else {
23
- fail("macos_vault_acl_invalid")
24
- }
25
- selector = ACL_NEXT_ENTRY
26
- }
27
- }
28
-
29
- // The caller verifies the release path's ancestors and package hashes. Walk
30
- // with lstat so a symlink or hard link cannot redirect a permission change.
31
- func secureRelease(_ path: String, verifyOnly: Bool) {
32
- var pending = [path]
33
- while let entry = pending.popLast() {
34
- var st = stat()
35
- guard lstat(entry, &st) == 0, st.st_uid == getuid() else { fail("macos_vault_release_invalid") }
36
- let type = st.st_mode & S_IFMT
37
- guard type == S_IFDIR || (type == S_IFREG && st.st_nlink == 1) else { fail("macos_vault_release_invalid") }
38
- let mode: mode_t = type == S_IFDIR || entry.hasSuffix(".bin") ? 0o700 : 0o600
39
- if (st.st_mode & 0o7777) != mode {
40
- guard !verifyOnly, chmod(entry, mode) == 0 else { fail("macos_vault_release_permissions_invalid") }
41
- }
42
- requireNoGrantACL(entry)
43
- if type == S_IFDIR {
44
- guard let names = try? FileManager.default.contentsOfDirectory(atPath: entry) else { fail("macos_vault_release_invalid") }
45
- pending.append(contentsOf: names.map { entry + "/" + $0 })
46
- }
47
- }
48
- }
49
-
50
- func masterKey(_ root: String, create: Bool) -> Data {
51
- let account = SHA256.hash(data: Data(root.utf8)).map { String(format: "%02x", $0) }.joined()
52
- let base: [String: Any] = [
53
- kSecClass as String: kSecClassGenericPassword,
54
- kSecAttrService as String: "com.hellolynk.codex.macos-vault.v1",
55
- kSecAttrAccount as String: account
56
- ]
57
- var lookup = base
58
- lookup[kSecReturnData as String] = true
59
- lookup[kSecMatchLimit as String] = kSecMatchLimitOne
60
- var result: CFTypeRef?
61
- let status = SecItemCopyMatching(lookup as CFDictionary, &result)
62
- if status == errSecSuccess {
63
- guard let key = result as? Data, key.count == 32 else { fail("macos_vault_key_invalid") }
64
- return key
65
- }
66
- guard status == errSecItemNotFound else { fail("macos_keychain_access_\(abs(Int(status)))") }
67
- guard create else { fail("macos_vault_key_missing") }
68
- let key = SymmetricKey(size: .bits256).withUnsafeBytes { Data($0) }
69
- var add = base
70
- add[kSecValueData as String] = key
71
- add[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
72
- let inserted = SecItemAdd(add as CFDictionary, nil)
73
- if inserted == errSecDuplicateItem { return masterKey(root, create: false) }
74
- guard inserted == errSecSuccess else { fail("macos_keychain_access_\(abs(Int(inserted)))") }
75
- return key
76
- }
77
-
78
- func nativeConfirmation() {
79
- let raw = FileHandle.standardInput.readDataToEndOfFile()
80
- guard raw.count <= 20480 * 6 + 8192,
81
- var v = (try? JSONSerialization.jsonObject(with: raw)) as? [String: Any],
82
- let body = v["displayText"] as? String, !body.isEmpty, body.utf8.count <= 20480,
83
- let encoded = v["hmacKey"] as? String, encoded.count == 43,
84
- let key = Data(base64Encoded: encoded.replacingOccurrences(of: "-", with: "+")
85
- .replacingOccurrences(of: "_", with: "/") + "="), key.count == 32,
86
- let recipient = v["recipientHelloLynkId"] as? String,
87
- v["contract"] as? String == "hellolynk-native-human-confirmation-v1",
88
- v["textDigest"] as? String == SHA256.hash(data: Data(body.utf8)).map({ String(format: "%02x", $0) }).joined()
89
- else { fail("human_confirmation_request_invalid") }
90
-
91
- var fields = ["contract", "actionId", "decision", "principalId", "delegateId", "hostInstallationId",
92
- "adapterId", "adapterInstanceId", "hostGeneration", "recipientHelloLynkId"]
93
- var label = "收件人:" + recipient
94
- if v["recipientDelegateId"] != nil || v["recipientInstanceDisplayName"] != nil {
95
- guard let name = v["recipientInstanceDisplayName"] as? String, !name.isEmpty, name.utf8.count <= 512,
96
- name.range(of: #"[\x00-\x1F\x7F-\x9F\u2028\u2029\u202A-\u202E\u2066-\u2069]"#, options: .regularExpression) == nil,
97
- v["recipientDelegateId"] is String else { fail("human_confirmation_request_invalid") }
98
- fields += ["recipientDelegateId", "recipientInstanceDisplayName"]
99
- label += "\n目标 AI:" + name
100
- }
101
- fields += ["clientMessageId", "textDigest", "createdAtMs", "expiresAtMs", "challenge"]
102
- var expected = Set(fields)
103
- expected.remove("decision")
104
- expected.formUnion(["displayText", "hmacKey"])
105
- guard expected == Set(v.keys) else { fail("human_confirmation_request_invalid") }
106
- let app = NSApplication.shared
107
- app.setActivationPolicy(.accessory)
108
- app.activate(ignoringOtherApps: true)
109
- let dialog = NSAlert()
110
- dialog.messageText = "是否允许 HelloLynk 发送以下文本?"
111
- dialog.informativeText = label
112
- dialog.addButton(withTitle: "取消")
113
- dialog.addButton(withTitle: "批准发送")
114
- let scroll = NSScrollView(frame: NSRect(x: 0, y: 0, width: 500, height: 230))
115
- scroll.hasVerticalScroller = true
116
- let view = NSTextView(frame: scroll.contentView.bounds)
117
- view.isEditable = false
118
- view.isRichText = false
119
- view.isVerticallyResizable = true
120
- view.autoresizingMask = [.width]
121
- view.textContainer?.widthTracksTextView = true
122
- view.string = body
123
- scroll.documentView = view
124
- dialog.accessoryView = scroll
125
- v["decision"] = dialog.runModal() == .alertSecondButtonReturn ? "approve" : "cancel"
126
- let message = fields.map { field -> String in
127
- if let text = v[field] as? String { return text }
128
- if ["createdAtMs","expiresAtMs"].contains(field), let n = v[field] as? NSNumber { return n.stringValue }
129
- fail("human_confirmation_request_invalid")
130
- }.joined(separator: "\n")
131
- let proof = HMAC<SHA256>.authenticationCode(for: Data(message.utf8), using: SymmetricKey(data: key))
132
- v["proof"] = proof.map { String(format: "%02x", $0) }.joined()
133
- for field in ["hmacKey", "challenge", "displayText"] { v.removeValue(forKey: field) }
134
- guard let output = try? JSONSerialization.data(withJSONObject: v) else { fail("human_confirmation_response_invalid") }
135
- FileHandle.standardOutput.write(output)
136
- }
137
-
138
- let args = Array(CommandLine.arguments.dropFirst())
139
- if args == ["confirm"] { nativeConfirmation() }
140
- else if args.count == 3, args[0] == "key", args[1].hasPrefix("/"), ["load","create"].contains(args[2]) {
141
- requireNoGrantACL(args[1])
142
- requireNoGrantACL(args[1] + "/protected-v1.sqlite")
143
- let key = masterKey(args[1], create: args[2] == "create")
144
- FileHandle(fileDescriptor: 3, closeOnDealloc: false).write(key)
145
- } else if args.count == 2, ["verify-tree", "secure-tree"].contains(args[0]), args[1].hasPrefix("/") {
146
- secureRelease(args[1], verifyOnly: args[0] == "verify-tree")
147
- } else if args.count == 2, args[0] == "verify-acl", args[1].hasPrefix("/") {
148
- requireNoGrantACL(args[1])
149
- } else { fail("macos_vault_helper_arguments_invalid") }