@zcloak/ai-agent 1.0.23 → 1.0.25
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/SKILL.md +88 -117
- package/dist/bind.js +9 -8
- package/dist/bind.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +61 -37
- package/dist/cli.js.map +1 -1
- package/dist/compat.d.ts +32 -0
- package/dist/compat.js +91 -0
- package/dist/compat.js.map +1 -0
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +14 -24
- package/dist/daemon.js +44 -83
- package/dist/daemon.js.map +1 -1
- package/dist/delete.js +8 -7
- package/dist/delete.js.map +1 -1
- package/dist/doc.js +4 -3
- package/dist/doc.js.map +1 -1
- package/dist/feed.js +2 -1
- package/dist/feed.js.map +1 -1
- package/dist/identity.js +2 -1
- package/dist/identity.js.map +1 -1
- package/dist/identity_cmd.js +2 -1
- package/dist/identity_cmd.js.map +1 -1
- package/dist/log.js +3 -6
- package/dist/log.js.map +1 -1
- package/dist/mailbox-store.d.ts +92 -0
- package/dist/mailbox-store.js +166 -0
- package/dist/mailbox-store.js.map +1 -0
- package/dist/paths.d.ts +39 -0
- package/dist/paths.js +77 -0
- package/dist/paths.js.map +1 -0
- package/dist/pow.js +2 -1
- package/dist/pow.js.map +1 -1
- package/dist/pre-check.d.ts +4 -4
- package/dist/pre-check.js +25 -9
- package/dist/pre-check.js.map +1 -1
- package/dist/register.js +200 -35
- package/dist/register.js.map +1 -1
- package/dist/rpc.d.ts +4 -6
- package/dist/rpc.js +3 -3
- package/dist/rpc.js.map +1 -1
- package/dist/serve.d.ts +4 -30
- package/dist/serve.js +22 -90
- package/dist/serve.js.map +1 -1
- package/dist/session.js +4 -3
- package/dist/session.js.map +1 -1
- package/dist/sign.js +9 -8
- package/dist/sign.js.map +1 -1
- package/dist/social.js +6 -5
- package/dist/social.js.map +1 -1
- package/dist/types/registry.d.ts +1 -1
- package/dist/types/registry.js +1 -1
- package/dist/types/sign-event.d.ts +1 -1
- package/dist/types/sign-event.js +1 -1
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/verify.js +3 -2
- package/dist/verify.js.map +1 -1
- package/dist/vetkey.d.ts +18 -15
- package/dist/vetkey.js +182 -91
- package/dist/vetkey.js.map +1 -1
- package/dist/zmail.d.ts +7 -3
- package/dist/zmail.js +316 -20
- package/dist/zmail.js.map +1 -1
- package/package.json +1 -1
package/dist/verify.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import fs from 'fs';
|
|
18
18
|
import path from 'path';
|
|
19
19
|
import { hashFile, verifyManifestEntries, formatSignEvent, formatSignEvents, } from './utils.js';
|
|
20
|
+
import * as log from './log.js';
|
|
20
21
|
// ========== Help Information ==========
|
|
21
22
|
function showHelp() {
|
|
22
23
|
console.log('zCloak.ai Verification Tool');
|
|
@@ -135,7 +136,7 @@ async function cmdVerifyFolder(session, folderPath) {
|
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
if (!allPassed) {
|
|
138
|
-
|
|
139
|
+
log.error('Local verification failed! Some files may have been modified.');
|
|
139
140
|
process.exit(1);
|
|
140
141
|
}
|
|
141
142
|
console.log('\nLocal verification passed!');
|
|
@@ -194,7 +195,7 @@ export async function run(session) {
|
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
catch (err) {
|
|
197
|
-
|
|
198
|
+
log.error(`Operation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
198
199
|
process.exit(1);
|
|
199
200
|
}
|
|
200
201
|
}
|
package/dist/verify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EACL,QAAQ,EACR,qBAAqB,EACrB,eAAe,EACf,gBAAgB,GACjB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EACL,QAAQ,EACR,qBAAqB,EACrB,eAAe,EACf,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAEhC,yCAAyC;AACzC,SAAS,QAAQ;IACf,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtB,OAAO,CAAC,GAAG,CAAC,oEAAoE,CAAC,CAAC;IAClF,OAAO,CAAC,GAAG,CAAC,0EAA0E,CAAC,CAAC;IACxF,OAAO,CAAC,GAAG,CAAC,mFAAmF,CAAC,CAAC;IACjG,OAAO,CAAC,GAAG,CAAC,2EAA2E,CAAC,CAAC;IACzF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACzB,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;AACvD,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,cAAc,CAAC,OAAgB,EAAE,MAAmB;IACjE,MAAM,WAAW,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAE5C,4BAA4B;IAC5B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAChB,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAC9C,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,yBAAyB,EAAE,CAAC;IAExD,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,EAAE,CAAC,CAAC;QAE1C,mBAAmB;QACnB,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAC;YAE/D,IAAI,UAAU,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACxC,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAE,CAAC;gBAChC,OAAO,CAAC,GAAG,CAAC,eAAe,QAAQ,EAAE,CAAC,CAAC;gBACvC,OAAO,CAAC,GAAG,CAAC,gBAAgB,WAAW,GAAG,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC5E,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;AACH,CAAC;AAED,gDAAgD;AAEhD,6BAA6B;AAC7B,KAAK,UAAU,gBAAgB,CAAC,OAAgB,EAAE,OAA2B;IAC3E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,qBAAqB,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IAEnD,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;IACtC,MAAM,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AAED,mCAAmC;AACnC,KAAK,UAAU,aAAa,CAAC,OAAgB,EAAE,QAA4B;IACzE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,+BAA+B,QAAQ,EAAE,CAAC,CAAC;QACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,oBAAoB;IACpB,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACpC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,WAAW,QAAQ,EAAE,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,wBAAwB;IACxB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,qBAAqB,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAEtD,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;IACtC,MAAM,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AAED,4CAA4C;AAC5C,KAAK,UAAU,eAAe,CAAC,OAAgB,EAAE,UAA8B;IAC7E,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QACzE,OAAO,CAAC,KAAK,CAAC,oCAAoC,UAAU,EAAE,CAAC,CAAC;QAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;IAC1D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,KAAK,CAAC,iCAAiC,YAAY,EAAE,CAAC,CAAC;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,yEAAyE;IACzE,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC;IACjE,MAAM,eAAe,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,qBAAqB,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;IAEnE,IAAI,SAAS,GAAG,IAAI,CAAC;IACrB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC;QACvC,CAAC;aAAM,CAAC;YACN,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,YAAY,GAAG,MAAM,EAAE,CAAC,CAAC;YAClD,SAAS,GAAG,KAAK,CAAC;QACpB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,GAAG,CAAC,KAAK,CAAC,+DAA+D,CAAC,CAAC;QAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IAE5C,oDAAoD;IACpD,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC;IACjE,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,oBAAoB,YAAY,EAAE,CAAC,CAAC;IAEhD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,qBAAqB,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;IAE1D,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;IACtC,MAAM,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AAED,oCAAoC;AACpC,KAAK,UAAU,gBAAgB,CAAC,OAAgB,EAAE,SAA6B;IAC7E,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,qBAAqB,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,4BAA4B,CAAC,SAAS,CAAC,CAAC;IAEnE,mCAAmC;IACnC,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,QAAQ,eAAe,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC;AACH,CAAC;AAED,0DAA0D;AAE1D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,OAAgB;IACxC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEtC,IAAI,CAAC;QACH,QAAQ,OAAO,EAAE,CAAC;YAChB,KAAK,SAAS;gBACZ,MAAM,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBACvD,MAAM;YACR,KAAK,MAAM;gBACT,MAAM,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBACpD,MAAM;YACR,KAAK,QAAQ;gBACX,MAAM,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBACtD,MAAM;YACR,KAAK,SAAS;gBACZ,MAAM,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBACvD,MAAM;YACR;gBACE,QAAQ,EAAE,CAAC;gBACX,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO,CAAC,KAAK,CAAC,sBAAsB,OAAO,EAAE,CAAC,CAAC;gBACjD,CAAC;gBACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,KAAK,CAAC,qBAAqB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
package/dist/vetkey.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* decrypt Decrypt Kind5 PrivatePost by event ID
|
|
12
12
|
* encrypt-only Encrypt locally without canister sign
|
|
13
13
|
* pubkey Get IBE public key from canister
|
|
14
|
-
* serve Start daemon (
|
|
14
|
+
* serve Start daemon (Unix Domain Socket)
|
|
15
15
|
* stop Stop a running daemon
|
|
16
16
|
* status Query daemon status
|
|
17
17
|
* grant Grant Kind5 decryption access to another user
|
|
@@ -31,24 +31,27 @@ import type { Session } from './session.js';
|
|
|
31
31
|
* @param session - CLI session with parsed args and canister access
|
|
32
32
|
*/
|
|
33
33
|
export declare function run(session: Session): Promise<void>;
|
|
34
|
+
/** Key names for the two standard daemons that should always be kept alive */
|
|
35
|
+
export declare const STANDARD_DAEMON_KEY_NAMES: readonly ["default", "Mail"];
|
|
34
36
|
/**
|
|
35
|
-
*
|
|
37
|
+
* Stop ALL running daemons by scanning the runtime directory for .sock files.
|
|
36
38
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*
|
|
39
|
+
* This covers all daemons regardless of principal or key name (standard,
|
|
40
|
+
* custom, or any --identity), ensuring no stale daemon survives a CLI upgrade.
|
|
41
|
+
* Errors are silently ignored (best-effort shutdown).
|
|
42
|
+
*/
|
|
43
|
+
export declare function stopAllDaemons(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Spawn a daemon process in the background for the given key name.
|
|
44
46
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
+
* The child process is fully detached (survives parent exit) with stderr
|
|
48
|
+
* redirected to a log file under ~/.config/zcloak/run/.
|
|
47
49
|
*
|
|
48
|
-
* @param pemPath
|
|
49
|
-
* @param
|
|
50
|
+
* @param pemPath - Path to the identity PEM file
|
|
51
|
+
* @param keyName - Daemon key name (e.g. "default", "Mail")
|
|
52
|
+
* @returns The child process PID (or undefined if spawn failed)
|
|
50
53
|
*/
|
|
51
|
-
export declare function
|
|
54
|
+
export declare function startDaemonBackground(pemPath: string, keyName: string): number | undefined;
|
|
52
55
|
/** Tag entry in a Kind17 envelope: ["to", principal], ["payload_type", "text"], etc. */
|
|
53
56
|
export type EnvelopeTag = [string, ...string[]];
|
|
54
57
|
/**
|
|
@@ -70,7 +73,7 @@ export interface Kind17Envelope {
|
|
|
70
73
|
created_at: number;
|
|
71
74
|
/** Tags carrying recipient and optional metadata */
|
|
72
75
|
tags: EnvelopeTag[];
|
|
73
|
-
/** Encrypted content:
|
|
76
|
+
/** Encrypted content: JSON string per zmail-skill spec {"v":1,"type":"text"|"file","ct":"<base64>"} */
|
|
74
77
|
content: string;
|
|
75
78
|
/** BIP-340 Schnorr signature over the envelope ID */
|
|
76
79
|
sig: string;
|
package/dist/vetkey.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* decrypt Decrypt Kind5 PrivatePost by event ID
|
|
12
12
|
* encrypt-only Encrypt locally without canister sign
|
|
13
13
|
* pubkey Get IBE public key from canister
|
|
14
|
-
* serve Start daemon (
|
|
14
|
+
* serve Start daemon (Unix Domain Socket)
|
|
15
15
|
* stop Stop a running daemon
|
|
16
16
|
* status Query daemon status
|
|
17
17
|
* grant Grant Kind5 decryption access to another user
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
*
|
|
24
24
|
* Usage: zcloak-ai vetkey <sub-command> [options]
|
|
25
25
|
*/
|
|
26
|
-
import { readFileSync, statSync, writeFileSync, existsSync, mkdirSync, openSync, closeSync } from 'fs';
|
|
26
|
+
import { readFileSync, readdirSync, statSync, writeFileSync, existsSync, mkdirSync, openSync, closeSync, unlinkSync } from 'fs';
|
|
27
27
|
import { basename, dirname } from 'path';
|
|
28
28
|
import { createConnection } from 'net';
|
|
29
29
|
import { spawn } from 'child_process';
|
|
30
|
-
import {
|
|
30
|
+
import { daemonLogPath } from './paths.js';
|
|
31
31
|
import { join } from 'path';
|
|
32
32
|
import { fileURLToPath } from 'url';
|
|
33
33
|
import { createInterface } from 'readline';
|
|
@@ -37,12 +37,13 @@ import { schnorr } from '@noble/curves/secp256k1';
|
|
|
37
37
|
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
|
|
38
38
|
import * as cryptoOps from './crypto.js';
|
|
39
39
|
import { KeyStore } from './key-store.js';
|
|
40
|
-
import { runDaemonUds
|
|
41
|
-
import {
|
|
40
|
+
import { runDaemonUds } from './serve.js';
|
|
41
|
+
import { isDaemonAlive, socketPath, runtimeDir } from './daemon.js';
|
|
42
42
|
import { canisterCallError } from './error.js';
|
|
43
|
+
import * as log from './log.js';
|
|
43
44
|
/**
|
|
44
45
|
* Absolute path to cli.js (the CLI entry script).
|
|
45
|
-
* Used by
|
|
46
|
+
* Used by startDaemonBackground() to spawn daemon child processes via
|
|
46
47
|
* `process.execPath` (the current Node binary) + this script path, so that
|
|
47
48
|
* daemon spawning works regardless of how the CLI was invoked (global install,
|
|
48
49
|
* npx, node dist/cli.js, etc.).
|
|
@@ -115,11 +116,6 @@ function showHelp() {
|
|
|
115
116
|
console.log(' encrypt-only Encrypt locally without canister sign');
|
|
116
117
|
console.log(' pubkey Get IBE public key from canister');
|
|
117
118
|
console.log('');
|
|
118
|
-
console.log('Daemon Commands (AES-256-GCM):');
|
|
119
|
-
console.log(' serve Start encryption daemon');
|
|
120
|
-
console.log(' stop Stop a running daemon');
|
|
121
|
-
console.log(' status Query daemon status');
|
|
122
|
-
console.log('');
|
|
123
119
|
console.log('Kind5 Access Control:');
|
|
124
120
|
console.log(' grant Grant decryption access to another user');
|
|
125
121
|
console.log(' revoke Revoke an access grant');
|
|
@@ -135,8 +131,7 @@ function showHelp() {
|
|
|
135
131
|
console.log(' --file=<path> File to encrypt');
|
|
136
132
|
console.log(' --event-id=<id> Event ID for decryption');
|
|
137
133
|
console.log(' --output=<path> Output file path');
|
|
138
|
-
|
|
139
|
-
console.log(' --stdio Use stdin/stdout mode for daemon');
|
|
134
|
+
// --key-name is an internal daemon option, not shown to users
|
|
140
135
|
console.log(' --public-key=<hex> IBE public key for offline encryption');
|
|
141
136
|
console.log(' --ibe-identity=<id> IBE identity for offline encryption');
|
|
142
137
|
console.log(' --tags=<json> Tags as JSON array');
|
|
@@ -146,6 +141,7 @@ function showHelp() {
|
|
|
146
141
|
console.log(' --duration=<dur> Grant duration: 30d, 1y, permanent (for grant)');
|
|
147
142
|
console.log(' --grant-id=<id> Grant ID (for revoke)');
|
|
148
143
|
console.log(' --to=<AI-ID|principal> Recipient AI-ID or principal (for send-msg)');
|
|
144
|
+
console.log(' --reply=<msg_id> Reply to a parent message (for send-msg)');
|
|
149
145
|
console.log(' --data=<json> Encrypted message JSON envelope (for recv-msg)');
|
|
150
146
|
console.log(' --no-zmail Skip auto-POST to zMail (send-msg only)');
|
|
151
147
|
console.log(' --zmail-url=<url> Override zMail server URL');
|
|
@@ -394,19 +390,22 @@ async function cmdGetPubkey(session) {
|
|
|
394
390
|
}
|
|
395
391
|
}
|
|
396
392
|
/**
|
|
397
|
-
* serve: Start daemon
|
|
393
|
+
* serve: Start daemon over Unix Domain Socket.
|
|
398
394
|
*
|
|
399
395
|
* Creates its own long-lived actor for the daemon lifecycle,
|
|
400
396
|
* using the Session's identity for authentication.
|
|
401
397
|
*/
|
|
402
398
|
async function cmdServe(session) {
|
|
403
399
|
const args = session.args;
|
|
400
|
+
// --stdio mode has been removed; reject explicitly so old callers fail fast
|
|
401
|
+
if (args['stdio']) {
|
|
402
|
+
throw new Error("--stdio mode is no longer supported. The daemon now uses Unix Domain Socket (UDS) exclusively.");
|
|
403
|
+
}
|
|
404
404
|
const rawKeyName = args['key-name'];
|
|
405
|
-
// Guard against boolean flag (e.g. --key-name
|
|
405
|
+
// Guard against boolean flag (e.g. --key-name without value parses as true)
|
|
406
406
|
if (rawKeyName === true)
|
|
407
407
|
throw new Error("--key-name requires a value (e.g. --key-name=mykey)");
|
|
408
408
|
const keyName = rawKeyName || 'default';
|
|
409
|
-
const stdio = !!args['stdio'];
|
|
410
409
|
// Validate key_name
|
|
411
410
|
if (keyName.includes(":"))
|
|
412
411
|
throw new Error("key_name must not contain ':' (reserved as separator)");
|
|
@@ -418,15 +417,10 @@ async function cmdServe(session) {
|
|
|
418
417
|
throw new Error(`derivation_id exceeds 256 bytes (${derivationId.length}); use a shorter key_name`);
|
|
419
418
|
}
|
|
420
419
|
// Derive AES-256 key from VetKey via the sign actor
|
|
421
|
-
|
|
420
|
+
log.info(`Deriving AES-256 key from VetKey (derivation_id: ${derivationId})...`);
|
|
422
421
|
const keyStore = await KeyStore.deriveFromActor(actor, derivationId);
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
await runDaemonStdio(keyStore, principal, derivationId);
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
await runDaemonUds(keyStore, principal, derivationId);
|
|
429
|
-
}
|
|
422
|
+
log.info("Key derived successfully. Starting JSON-RPC daemon...");
|
|
423
|
+
await runDaemonUds(keyStore, principal, derivationId);
|
|
430
424
|
}
|
|
431
425
|
/**
|
|
432
426
|
* stop: Send shutdown to a running daemon.
|
|
@@ -439,21 +433,60 @@ async function cmdStop(session) {
|
|
|
439
433
|
const jsonOutput = !!args['json'];
|
|
440
434
|
const principal = session.getPrincipal();
|
|
441
435
|
const derivationId = `${principal}:${keyName}`;
|
|
442
|
-
const sockPath =
|
|
443
|
-
//
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
436
|
+
const sockPath = socketPath(derivationId);
|
|
437
|
+
// Try to stop via socket if:
|
|
438
|
+
// 1. isDaemonAlive() says yes (PID + socket both present), OR
|
|
439
|
+
// 2. PID file is missing/corrupt but socket file still exists — the daemon
|
|
440
|
+
// process may be alive with an orphaned socket. Attempt a shutdown via
|
|
441
|
+
// the socket so we don't leave an unmanageable orphan process.
|
|
442
|
+
const alive = isDaemonAlive(derivationId);
|
|
443
|
+
const socketExists = existsSync(sockPath);
|
|
444
|
+
if (!alive && !socketExists) {
|
|
445
|
+
if (jsonOutput) {
|
|
446
|
+
console.log(JSON.stringify({ status: "not_running", key_name: keyName }));
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
console.log(`Daemon '${keyName}' is not running. Nothing to stop.`);
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
450
452
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
+
try {
|
|
454
|
+
const response = await sendRpcToSocket(sockPath, {
|
|
455
|
+
id: 1,
|
|
456
|
+
method: "shutdown",
|
|
457
|
+
});
|
|
458
|
+
if (jsonOutput) {
|
|
459
|
+
console.log(JSON.stringify(response));
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
console.log("Daemon stopped successfully.");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch (e) {
|
|
466
|
+
// Socket file exists but connection failed — stale socket, clean it up
|
|
467
|
+
if (socketExists && !alive) {
|
|
468
|
+
safeUnlinkPath(sockPath);
|
|
469
|
+
if (jsonOutput) {
|
|
470
|
+
console.log(JSON.stringify({ status: "not_running", key_name: keyName, note: "cleaned up stale socket" }));
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
console.log(`Daemon '${keyName}' is not running (cleaned up stale socket).`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
throw e;
|
|
478
|
+
}
|
|
453
479
|
}
|
|
454
480
|
}
|
|
455
481
|
/**
|
|
456
|
-
* status: Query
|
|
482
|
+
* status: Query daemon status.
|
|
483
|
+
*
|
|
484
|
+
* For standard daemons (default, Mail): auto-starts if not running, so users
|
|
485
|
+
* never need to manually launch a daemon before querying its status.
|
|
486
|
+
*
|
|
487
|
+
* For custom key names: only reports the current state — does NOT auto-start,
|
|
488
|
+
* since custom daemons require explicit `vetkey serve` to start. This keeps
|
|
489
|
+
* `status` side-effect-free for non-standard daemons.
|
|
457
490
|
*/
|
|
458
491
|
async function cmdStatus(session) {
|
|
459
492
|
const args = session.args;
|
|
@@ -461,9 +494,27 @@ async function cmdStatus(session) {
|
|
|
461
494
|
throw new Error("--key-name requires a value (e.g. --key-name=mykey)");
|
|
462
495
|
const keyName = args['key-name'] || 'default';
|
|
463
496
|
const jsonOutput = !!args['json'];
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
497
|
+
const isStandard = STANDARD_DAEMON_KEY_NAMES.includes(keyName);
|
|
498
|
+
let sockPath;
|
|
499
|
+
if (isStandard) {
|
|
500
|
+
// Standard daemon: auto-start if needed
|
|
501
|
+
sockPath = await ensureDaemon(session, keyName);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
// Custom daemon: report-only, no auto-start
|
|
505
|
+
const principal = session.getPrincipal();
|
|
506
|
+
const derivationId = `${principal}:${keyName}`;
|
|
507
|
+
if (!isDaemonAlive(derivationId)) {
|
|
508
|
+
if (jsonOutput) {
|
|
509
|
+
console.log(JSON.stringify({ status: "not_running", key_name: keyName }));
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
console.log(`Daemon '${keyName}' is not running. Use 'zcloak-ai vetkey serve --key-name=${keyName}' to start it.`);
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
sockPath = socketPath(derivationId);
|
|
517
|
+
}
|
|
467
518
|
// Connect to socket and send status
|
|
468
519
|
const response = await sendRpcToSocket(sockPath, {
|
|
469
520
|
id: 1,
|
|
@@ -480,13 +531,10 @@ async function cmdStatus(session) {
|
|
|
480
531
|
console.log(` Derivation ID: ${result.derivation_id}`);
|
|
481
532
|
console.log(` Principal: ${result.principal}`);
|
|
482
533
|
console.log(` Started At: ${result.started_at}`);
|
|
483
|
-
console.log(`
|
|
484
|
-
if (result.socket_path) {
|
|
485
|
-
console.log(` Socket: ${result.socket_path}`);
|
|
486
|
-
}
|
|
534
|
+
console.log(` Socket: ${result.socket_path}`);
|
|
487
535
|
}
|
|
488
536
|
else if (response.error) {
|
|
489
|
-
|
|
537
|
+
log.error(`Error: ${response.error}`);
|
|
490
538
|
}
|
|
491
539
|
}
|
|
492
540
|
}
|
|
@@ -498,20 +546,48 @@ const DAEMON_READY_TIMEOUT_MS = 30000;
|
|
|
498
546
|
/** Polling interval when waiting for daemon socket to appear (ms) */
|
|
499
547
|
const DAEMON_POLL_INTERVAL_MS = 500;
|
|
500
548
|
/** Key names for the two standard daemons that should always be kept alive */
|
|
501
|
-
const STANDARD_DAEMON_KEY_NAMES = ['default', 'Mail'];
|
|
549
|
+
export const STANDARD_DAEMON_KEY_NAMES = ['default', 'Mail'];
|
|
550
|
+
/**
|
|
551
|
+
* Stop ALL running daemons by scanning the runtime directory for .sock files.
|
|
552
|
+
*
|
|
553
|
+
* This covers all daemons regardless of principal or key name (standard,
|
|
554
|
+
* custom, or any --identity), ensuring no stale daemon survives a CLI upgrade.
|
|
555
|
+
* Errors are silently ignored (best-effort shutdown).
|
|
556
|
+
*/
|
|
557
|
+
export async function stopAllDaemons() {
|
|
558
|
+
const dir = runtimeDir();
|
|
559
|
+
let entries;
|
|
560
|
+
try {
|
|
561
|
+
entries = readdirSync(dir);
|
|
562
|
+
}
|
|
563
|
+
catch {
|
|
564
|
+
return; // Runtime directory doesn't exist — no daemons running
|
|
565
|
+
}
|
|
566
|
+
const sockFiles = entries.filter(e => e.endsWith('.sock'));
|
|
567
|
+
for (const sockFile of sockFiles) {
|
|
568
|
+
const sock = join(dir, sockFile);
|
|
569
|
+
try {
|
|
570
|
+
await sendRpcToSocket(sock, { id: 1, method: "shutdown" });
|
|
571
|
+
log.info(`Daemon stopped (${sockFile}) before upgrade.`);
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
// Best-effort — daemon may already be gone or socket is stale
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
502
578
|
/**
|
|
503
579
|
* Spawn a daemon process in the background for the given key name.
|
|
504
580
|
*
|
|
505
581
|
* The child process is fully detached (survives parent exit) with stderr
|
|
506
|
-
* redirected to a log file under ~/.
|
|
582
|
+
* redirected to a log file under ~/.config/zcloak/run/.
|
|
507
583
|
*
|
|
508
584
|
* @param pemPath - Path to the identity PEM file
|
|
509
585
|
* @param keyName - Daemon key name (e.g. "default", "Mail")
|
|
510
586
|
* @returns The child process PID (or undefined if spawn failed)
|
|
511
587
|
*/
|
|
512
|
-
function
|
|
513
|
-
const
|
|
514
|
-
const
|
|
588
|
+
export function startDaemonBackground(pemPath, keyName) {
|
|
589
|
+
const logPath = daemonLogPath(keyName);
|
|
590
|
+
const logDir = dirname(logPath);
|
|
515
591
|
try {
|
|
516
592
|
mkdirSync(logDir, { recursive: true });
|
|
517
593
|
}
|
|
@@ -568,59 +644,33 @@ async function ensureDaemon(session, keyName) {
|
|
|
568
644
|
if (isDaemonAlive(derivationId)) {
|
|
569
645
|
return socketPath(derivationId);
|
|
570
646
|
}
|
|
571
|
-
|
|
572
|
-
const pid =
|
|
573
|
-
|
|
647
|
+
log.info(`${keyName} daemon is not running. Starting it automatically...`);
|
|
648
|
+
const pid = startDaemonBackground(session.getPemPath(), keyName);
|
|
649
|
+
log.info(`${keyName} daemon spawned (PID: ${pid ?? 'unknown'}). Waiting for ready...`);
|
|
574
650
|
// Poll for the socket file to appear (daemon writes PID + creates socket on ready)
|
|
575
651
|
const sock = socketPath(derivationId);
|
|
576
|
-
const logPath =
|
|
652
|
+
const logPath = daemonLogPath(keyName);
|
|
577
653
|
const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
|
|
578
654
|
while (Date.now() < deadline) {
|
|
579
655
|
await new Promise((resolve) => setTimeout(resolve, DAEMON_POLL_INTERVAL_MS));
|
|
580
656
|
if (isDaemonAlive(derivationId) && existsSync(sock)) {
|
|
581
|
-
|
|
657
|
+
log.info(`${keyName} daemon is ready. Socket: ${sock}`);
|
|
582
658
|
return sock;
|
|
583
659
|
}
|
|
584
660
|
}
|
|
585
661
|
throw new Error(`${keyName} daemon failed to start within ${DAEMON_READY_TIMEOUT_MS / 1000}s. ` +
|
|
586
662
|
`Check the log at ${logPath} for details.`);
|
|
587
663
|
}
|
|
588
|
-
/**
|
|
589
|
-
* Background daemon health check — fire-and-forget.
|
|
590
|
-
*
|
|
591
|
-
* Called by cli.ts after Session creation to keep both standard daemons
|
|
592
|
-
* ("default" and "Mail") alive. If a daemon is dead, spawns it in the
|
|
593
|
-
* background WITHOUT waiting for it to be ready (non-blocking).
|
|
594
|
-
*
|
|
595
|
-
* Prerequisites:
|
|
596
|
-
* - The PEM file must exist (user has already created an identity)
|
|
597
|
-
* - If PEM doesn't exist, silently skips (no identity = no daemon possible)
|
|
598
|
-
*
|
|
599
|
-
* All errors are silently swallowed — this is a best-effort health check
|
|
600
|
-
* and must never block or fail the main command.
|
|
601
|
-
*
|
|
602
|
-
* @param pemPath - Path to the identity PEM file
|
|
603
|
-
* @param principal - The principal ID derived from the PEM
|
|
604
|
-
*/
|
|
605
|
-
export function ensureDaemonsBackground(pemPath, principal) {
|
|
606
|
-
for (const keyName of STANDARD_DAEMON_KEY_NAMES) {
|
|
607
|
-
const derivationId = `${principal}:${keyName}`;
|
|
608
|
-
try {
|
|
609
|
-
if (!isDaemonAlive(derivationId)) {
|
|
610
|
-
const pid = spawnDaemonBackground(pemPath, keyName);
|
|
611
|
-
if (pid) {
|
|
612
|
-
console.error(`[zcloak-ai] ${keyName} daemon was not running — auto-started (PID: ${pid})`);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
catch {
|
|
617
|
-
// Silently ignore — daemon health check must never block the main command
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
664
|
// ============================================================================
|
|
622
665
|
// Helper Functions
|
|
623
666
|
// ============================================================================
|
|
667
|
+
/** Safely delete a file, ignoring errors if it doesn't exist */
|
|
668
|
+
function safeUnlinkPath(filePath) {
|
|
669
|
+
try {
|
|
670
|
+
unlinkSync(filePath);
|
|
671
|
+
}
|
|
672
|
+
catch { /* ignore */ }
|
|
673
|
+
}
|
|
624
674
|
/**
|
|
625
675
|
* Generate default output path for encrypted files.
|
|
626
676
|
* - If an input file was provided, appends ".enc" suffix (e.g. "data.txt" → "data.txt.enc")
|
|
@@ -1022,7 +1072,7 @@ export function schnorrPubkeyFromSpki(spkiHex) {
|
|
|
1022
1072
|
*
|
|
1023
1073
|
* @param session - CLI session (provides identity for Schnorr signing)
|
|
1024
1074
|
* @param tags - Envelope tags (must include at least one ["to", ...])
|
|
1025
|
-
* @param content - Encrypted content string
|
|
1075
|
+
* @param content - Encrypted content JSON string: {"v":1,"type":"text"|"file","ct":"<base64>"}
|
|
1026
1076
|
* @returns Signed Kind17Envelope ready for JSON serialization
|
|
1027
1077
|
*/
|
|
1028
1078
|
function buildSignedEnvelope(session, tags, content) {
|
|
@@ -1052,6 +1102,7 @@ function buildSignedEnvelope(session, tags, content) {
|
|
|
1052
1102
|
* --to=<AI-ID or principal> (required) Recipient identifier
|
|
1053
1103
|
* --text=<content> Text message to encrypt
|
|
1054
1104
|
* --file=<path> File to encrypt
|
|
1105
|
+
* --reply=<msg_id> Parent message ID (for reply threads)
|
|
1055
1106
|
* --json Output in JSON format (default: true for send-msg)
|
|
1056
1107
|
*/
|
|
1057
1108
|
async function cmdSendMsg(session) {
|
|
@@ -1062,6 +1113,13 @@ async function cmdSendMsg(session) {
|
|
|
1062
1113
|
const to = rawTo;
|
|
1063
1114
|
const text = args['text'];
|
|
1064
1115
|
const file = args['file'];
|
|
1116
|
+
const rawReply = args['reply'];
|
|
1117
|
+
if (rawReply === true)
|
|
1118
|
+
throw new Error('--reply requires a message ID value');
|
|
1119
|
+
if (typeof rawReply === 'string' && rawReply.length === 0) {
|
|
1120
|
+
throw new Error('--reply requires a non-empty message ID');
|
|
1121
|
+
}
|
|
1122
|
+
const replyMsgId = rawReply;
|
|
1065
1123
|
if (!to) {
|
|
1066
1124
|
throw new Error('--to=<AI-ID or principal> is required');
|
|
1067
1125
|
}
|
|
@@ -1100,6 +1158,10 @@ async function cmdSendMsg(session) {
|
|
|
1100
1158
|
// IBE-encrypt the plaintext for the recipient's Mail identity
|
|
1101
1159
|
const ciphertext = cryptoOps.ibeEncrypt(dpkBytes, ibeIdentity, plaintext);
|
|
1102
1160
|
const contentBase64 = Buffer.from(ciphertext).toString('base64');
|
|
1161
|
+
// Wrap ciphertext in the standardized message composition format per zmail-skill spec.
|
|
1162
|
+
// Shape: {"v":1,"type":"text"|"file","ct":"<base64-ciphertext>"}
|
|
1163
|
+
// The outer Kind17 envelope carries routing metadata; only the body is encrypted.
|
|
1164
|
+
const content = JSON.stringify({ v: 1, type: payloadType, ct: contentBase64 });
|
|
1103
1165
|
// Build tags: recipient + metadata
|
|
1104
1166
|
const tags = [
|
|
1105
1167
|
['to', recipientPrincipal],
|
|
@@ -1109,8 +1171,12 @@ async function cmdSendMsg(session) {
|
|
|
1109
1171
|
if (filename) {
|
|
1110
1172
|
tags.push(['filename', filename]);
|
|
1111
1173
|
}
|
|
1174
|
+
// Include reply tag when responding to an existing message
|
|
1175
|
+
if (replyMsgId) {
|
|
1176
|
+
tags.push(['reply', replyMsgId]);
|
|
1177
|
+
}
|
|
1112
1178
|
// Build and sign the Kind17 envelope
|
|
1113
|
-
const envelope = buildSignedEnvelope(session, tags,
|
|
1179
|
+
const envelope = buildSignedEnvelope(session, tags, content);
|
|
1114
1180
|
// Output the envelope as JSON (always JSON for machine consumption)
|
|
1115
1181
|
console.log(JSON.stringify(envelope));
|
|
1116
1182
|
// Auto-POST to zMail if not explicitly disabled with --no-zmail.
|
|
@@ -1126,10 +1192,10 @@ async function cmdSendMsg(session) {
|
|
|
1126
1192
|
: (await import('./config.js')).default.zmail_url;
|
|
1127
1193
|
const { postEnvelopeToZmail } = await import('./zmail.js');
|
|
1128
1194
|
const result = await postEnvelopeToZmail(zmailUrl, envelope);
|
|
1129
|
-
|
|
1195
|
+
log.info(`zMail: delivered (msg_id=${result.msg_id}, to=${result.delivered_to})`);
|
|
1130
1196
|
}
|
|
1131
1197
|
catch (err) {
|
|
1132
|
-
|
|
1198
|
+
log.error(`zMail: delivery failed — ${err instanceof Error ? err.message : String(err)}`);
|
|
1133
1199
|
}
|
|
1134
1200
|
}
|
|
1135
1201
|
}
|
|
@@ -1175,6 +1241,31 @@ async function cmdRecvMsg(session) {
|
|
|
1175
1241
|
// Verify envelope integrity and sender authentication.
|
|
1176
1242
|
// Returns true only if full Schnorr signature + principal binding was verified.
|
|
1177
1243
|
const verifiedSender = verifyKind17Signature(envelope);
|
|
1244
|
+
// Extract the IBE ciphertext from the content field.
|
|
1245
|
+
// Content follows the zmail-skill message composition spec:
|
|
1246
|
+
// {"v":1,"type":"text"|"file","ct":"<base64-ciphertext>"}
|
|
1247
|
+
let ciphertextBase64;
|
|
1248
|
+
try {
|
|
1249
|
+
const parsed = JSON.parse(envelope.content);
|
|
1250
|
+
if (!parsed || typeof parsed !== 'object' || parsed.v !== 1 || typeof parsed.ct !== 'string') {
|
|
1251
|
+
throw new Error('Invalid message composition format: missing v=1 or ct field');
|
|
1252
|
+
}
|
|
1253
|
+
// Validate type field exists and matches one of the allowed payload types
|
|
1254
|
+
if (parsed.type !== 'text' && parsed.type !== 'file') {
|
|
1255
|
+
throw new Error(`Invalid message composition format: type must be "text" or "file", got "${String(parsed.type)}"`);
|
|
1256
|
+
}
|
|
1257
|
+
// Verify consistency between the composition type and the envelope payload_type tag
|
|
1258
|
+
if (parsed.type !== payloadType) {
|
|
1259
|
+
throw new Error(`Message composition type "${parsed.type}" does not match envelope payload_type tag "${payloadType}"`);
|
|
1260
|
+
}
|
|
1261
|
+
ciphertextBase64 = parsed.ct;
|
|
1262
|
+
}
|
|
1263
|
+
catch (e) {
|
|
1264
|
+
if (e instanceof SyntaxError) {
|
|
1265
|
+
throw new Error('Invalid message composition format: content is not valid JSON');
|
|
1266
|
+
}
|
|
1267
|
+
throw e;
|
|
1268
|
+
}
|
|
1178
1269
|
// Ensure Mail daemon is running (auto-start if needed, wait for ready), then decrypt
|
|
1179
1270
|
const sockPath = await ensureDaemon(session, 'Mail');
|
|
1180
1271
|
const response = await sendRpcToSocket(sockPath, {
|
|
@@ -1182,7 +1273,7 @@ async function cmdRecvMsg(session) {
|
|
|
1182
1273
|
method: 'ibe-decrypt',
|
|
1183
1274
|
params: {
|
|
1184
1275
|
ibe_identity: ibeId,
|
|
1185
|
-
ciphertext_base64:
|
|
1276
|
+
ciphertext_base64: ciphertextBase64,
|
|
1186
1277
|
},
|
|
1187
1278
|
});
|
|
1188
1279
|
if (response.error) {
|