@kin-tio/cli 0.6.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/.env.example +46 -0
- package/CHANGELOG.md +95 -0
- package/LICENSE +202 -0
- package/README.md +150 -0
- package/README.zh-CN.md +79 -0
- package/THIRD_PARTY_NOTICES +31 -0
- package/assets/ilink-login-card.png +0 -0
- package/bin/kintio.js +3 -0
- package/codex-workspace/.agents/skills/wechat-kf-reply-sop/SKILL.md +58 -0
- package/dist/cli.js +3 -0
- package/dist/daemon.js +28 -0
- package/dist/index.js +70 -0
- package/dist/mcp-relay.js +11 -0
- package/dist/src/agent/runtime.js +1 -0
- package/dist/src/app.js +34 -0
- package/dist/src/cli.js +578 -0
- package/dist/src/config.js +237 -0
- package/dist/src/domain/message.js +23 -0
- package/dist/src/domain/send-contract.js +205 -0
- package/dist/src/domain/wecom-message.js +281 -0
- package/dist/src/ilink/executor.js +306 -0
- package/dist/src/ilink/inbound-image.js +310 -0
- package/dist/src/ilink/listener.js +306 -0
- package/dist/src/ilink/login-manager.js +198 -0
- package/dist/src/ilink/login-store.js +197 -0
- package/dist/src/ilink/media-gateway.js +83 -0
- package/dist/src/ilink/media.js +267 -0
- package/dist/src/ilink/message.js +247 -0
- package/dist/src/ilink/protocol/client.js +464 -0
- package/dist/src/ilink/protocol/types.js +35 -0
- package/dist/src/ilink/qr.js +109 -0
- package/dist/src/ilink/secret-box.js +143 -0
- package/dist/src/ilink/sqlite-store.js +1194 -0
- package/dist/src/ilink/store-types.js +63 -0
- package/dist/src/lib/image-format.js +23 -0
- package/dist/src/lib/path-identity.js +38 -0
- package/dist/src/lib/private-directory.js +51 -0
- package/dist/src/lib/text.js +19 -0
- package/dist/src/lib/wecom-crypto.js +74 -0
- package/dist/src/lib/xml.js +8 -0
- package/dist/src/mcp/conversation-memory-server.js +179 -0
- package/dist/src/mcp/ilink-server.js +158 -0
- package/dist/src/mcp/ipc-host.js +275 -0
- package/dist/src/mcp/ipc-protocol.js +226 -0
- package/dist/src/mcp/stdio-relay.js +122 -0
- package/dist/src/mcp/wechat-kf-executor.js +295 -0
- package/dist/src/mcp/wechat-kf-server.js +208 -0
- package/dist/src/routes/wecom.js +89 -0
- package/dist/src/runtime/daemon-protocol.js +202 -0
- package/dist/src/runtime/managed-skill.js +49 -0
- package/dist/src/runtime/native-daemon.js +325 -0
- package/dist/src/runtime/single-instance-lock.js +167 -0
- package/dist/src/runtime.js +503 -0
- package/dist/src/services/codex-agent.js +542 -0
- package/dist/src/services/codex-app-server.js +436 -0
- package/dist/src/services/conversation-processor.js +762 -0
- package/dist/src/services/image-stager.js +49 -0
- package/dist/src/services/media-gateway.js +83 -0
- package/dist/src/services/wecom-api.js +311 -0
- package/dist/src/services/wecom-sync.js +316 -0
- package/dist/src/state/persistence.js +124 -0
- package/dist/src/state/sqlite-store.js +3102 -0
- package/dist/src/supervisor.js +212 -0
- package/dist/src/types.js +1 -0
- package/dist/src/version.js +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, createSecretKey, randomBytes, } from 'node:crypto';
|
|
2
|
+
const KEY_BYTES = 32;
|
|
3
|
+
const NONCE_BYTES = 12;
|
|
4
|
+
const AUTH_TAG_BYTES = 16;
|
|
5
|
+
const MAX_SCOPE_BYTES = 512;
|
|
6
|
+
export class IlinkSecretBoxError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(code, message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'IlinkSecretBoxError';
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function invalidKey() {
|
|
15
|
+
return new IlinkSecretBoxError('invalid_key', 'iLink secret key must be a canonical 32-byte base64url value');
|
|
16
|
+
}
|
|
17
|
+
function decodeConfiguredKey(configuredKey) {
|
|
18
|
+
if (typeof configuredKey !== 'string' ||
|
|
19
|
+
!/^[A-Za-z0-9_-]{43}$/u.test(configuredKey)) {
|
|
20
|
+
throw invalidKey();
|
|
21
|
+
}
|
|
22
|
+
const decoded = Buffer.from(configuredKey, 'base64url');
|
|
23
|
+
if (decoded.length !== KEY_BYTES ||
|
|
24
|
+
decoded.toString('base64url') !== configuredKey) {
|
|
25
|
+
decoded.fill(0);
|
|
26
|
+
throw invalidKey();
|
|
27
|
+
}
|
|
28
|
+
return decoded;
|
|
29
|
+
}
|
|
30
|
+
function boundedScopeText(value) {
|
|
31
|
+
return typeof value === 'string' &&
|
|
32
|
+
value.length > 0 &&
|
|
33
|
+
Buffer.byteLength(value, 'utf8') <= MAX_SCOPE_BYTES;
|
|
34
|
+
}
|
|
35
|
+
function additionalAuthenticatedData(scope) {
|
|
36
|
+
if (!scope ||
|
|
37
|
+
typeof scope !== 'object' ||
|
|
38
|
+
!/^[a-z][a-z0-9_]{0,63}$/u.test(scope.secretKind) ||
|
|
39
|
+
!boundedScopeText(scope.accountId) ||
|
|
40
|
+
!boundedScopeText(scope.peerId) ||
|
|
41
|
+
!Number.isSafeInteger(scope.generation) ||
|
|
42
|
+
scope.generation < 0) {
|
|
43
|
+
throw new IlinkSecretBoxError('invalid_scope', 'Invalid iLink secret scope');
|
|
44
|
+
}
|
|
45
|
+
return Buffer.from(JSON.stringify([
|
|
46
|
+
1,
|
|
47
|
+
scope.secretKind,
|
|
48
|
+
scope.accountId,
|
|
49
|
+
scope.peerId,
|
|
50
|
+
scope.generation,
|
|
51
|
+
]), 'utf8');
|
|
52
|
+
}
|
|
53
|
+
function decodeEnvelopeField(value, expectedBytes) {
|
|
54
|
+
if (typeof value !== 'string' ||
|
|
55
|
+
value.length === 0 ||
|
|
56
|
+
!/^[A-Za-z0-9_-]+$/u.test(value)) {
|
|
57
|
+
throw new IlinkSecretBoxError('invalid_envelope', 'Invalid iLink secret envelope');
|
|
58
|
+
}
|
|
59
|
+
const decoded = Buffer.from(value, 'base64url');
|
|
60
|
+
if (decoded.toString('base64url') !== value ||
|
|
61
|
+
(expectedBytes !== undefined && decoded.length !== expectedBytes)) {
|
|
62
|
+
decoded.fill(0);
|
|
63
|
+
throw new IlinkSecretBoxError('invalid_envelope', 'Invalid iLink secret envelope');
|
|
64
|
+
}
|
|
65
|
+
return decoded;
|
|
66
|
+
}
|
|
67
|
+
export class IlinkSecretBox {
|
|
68
|
+
#key;
|
|
69
|
+
constructor(configuredKey) {
|
|
70
|
+
const keyBytes = decodeConfiguredKey(configuredKey);
|
|
71
|
+
try {
|
|
72
|
+
this.#key = createSecretKey(keyBytes);
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
keyBytes.fill(0);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
seal(secret, scope) {
|
|
79
|
+
if (typeof secret !== 'string' || secret.length === 0) {
|
|
80
|
+
throw new IlinkSecretBoxError('invalid_secret', 'iLink secret must be a non-empty string');
|
|
81
|
+
}
|
|
82
|
+
const aad = additionalAuthenticatedData(scope);
|
|
83
|
+
const nonce = randomBytes(NONCE_BYTES);
|
|
84
|
+
const plaintext = Buffer.from(secret, 'utf8');
|
|
85
|
+
try {
|
|
86
|
+
const cipher = createCipheriv('aes-256-gcm', this.#key, nonce, {
|
|
87
|
+
authTagLength: AUTH_TAG_BYTES,
|
|
88
|
+
});
|
|
89
|
+
cipher.setAAD(aad);
|
|
90
|
+
const ciphertext = Buffer.concat([
|
|
91
|
+
cipher.update(plaintext),
|
|
92
|
+
cipher.final(),
|
|
93
|
+
]);
|
|
94
|
+
return {
|
|
95
|
+
nonce: nonce.toString('base64url'),
|
|
96
|
+
ciphertext: ciphertext.toString('base64url'),
|
|
97
|
+
authTag: cipher.getAuthTag().toString('base64url'),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
plaintext.fill(0);
|
|
102
|
+
aad.fill(0);
|
|
103
|
+
nonce.fill(0);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
open(envelope, scope) {
|
|
107
|
+
const aad = additionalAuthenticatedData(scope);
|
|
108
|
+
let nonce;
|
|
109
|
+
let ciphertext;
|
|
110
|
+
let authTag;
|
|
111
|
+
let plaintext;
|
|
112
|
+
try {
|
|
113
|
+
if (!envelope || typeof envelope !== 'object') {
|
|
114
|
+
throw new IlinkSecretBoxError('invalid_envelope', 'Invalid iLink secret envelope');
|
|
115
|
+
}
|
|
116
|
+
nonce = decodeEnvelopeField(envelope.nonce, NONCE_BYTES);
|
|
117
|
+
ciphertext = decodeEnvelopeField(envelope.ciphertext);
|
|
118
|
+
authTag = decodeEnvelopeField(envelope.authTag, AUTH_TAG_BYTES);
|
|
119
|
+
const decipher = createDecipheriv('aes-256-gcm', this.#key, nonce, {
|
|
120
|
+
authTagLength: AUTH_TAG_BYTES,
|
|
121
|
+
});
|
|
122
|
+
decipher.setAAD(aad);
|
|
123
|
+
decipher.setAuthTag(authTag);
|
|
124
|
+
plaintext = Buffer.concat([
|
|
125
|
+
decipher.update(ciphertext),
|
|
126
|
+
decipher.final(),
|
|
127
|
+
]);
|
|
128
|
+
return plaintext.toString('utf8');
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error instanceof IlinkSecretBoxError)
|
|
132
|
+
throw error;
|
|
133
|
+
throw new IlinkSecretBoxError('decryption_failed', 'Unable to decrypt iLink secret');
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
aad.fill(0);
|
|
137
|
+
nonce?.fill(0);
|
|
138
|
+
ciphertext?.fill(0);
|
|
139
|
+
authTag?.fill(0);
|
|
140
|
+
plaintext?.fill(0);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|