neozip-mcp 0.1.0-beta

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.
Files changed (113) hide show
  1. package/.cursor/mcp.json.global.example +10 -0
  2. package/CHANGELOG.md +16 -0
  3. package/DOCUMENTATION.md +40 -0
  4. package/LICENSE +16 -0
  5. package/README.md +223 -0
  6. package/SECURITY.md +37 -0
  7. package/dist/account/account-state.js +86 -0
  8. package/dist/account/format-account-status.js +37 -0
  9. package/dist/account/identity-provision.js +75 -0
  10. package/dist/account/identity-wrap.js +69 -0
  11. package/dist/account/profile-crypto.js +47 -0
  12. package/dist/account/profile-store.js +108 -0
  13. package/dist/account/require-account.js +29 -0
  14. package/dist/account/token-service-identity.js +395 -0
  15. package/dist/account/types.js +2 -0
  16. package/dist/account/wallet-evm.js +39 -0
  17. package/dist/archive/blockchain-status.js +303 -0
  18. package/dist/archive/crypto-self.js +114 -0
  19. package/dist/archive/detect-text.js +56 -0
  20. package/dist/archive/embed-metadata.js +283 -0
  21. package/dist/archive/encryption.js +166 -0
  22. package/dist/archive/extract-entry-buffer.js +18 -0
  23. package/dist/archive/find-entry.js +21 -0
  24. package/dist/archive/grep-content.js +141 -0
  25. package/dist/archive/identity-key.js +176 -0
  26. package/dist/archive/manifest.js +55 -0
  27. package/dist/archive/merkle.js +31 -0
  28. package/dist/archive/metadata-paths.js +14 -0
  29. package/dist/archive/mint-archive.js +61 -0
  30. package/dist/archive/open-archive.js +23 -0
  31. package/dist/archive/read-entry-buffer.js +11 -0
  32. package/dist/archive/read-entry-content.js +51 -0
  33. package/dist/archive/recipient-access.js +26 -0
  34. package/dist/archive/recipient-decrypt.js +21 -0
  35. package/dist/archive/recipient-lookup.js +55 -0
  36. package/dist/archive/timestamp-network.js +54 -0
  37. package/dist/config/capabilities.js +37 -0
  38. package/dist/config/index.js +74 -0
  39. package/dist/connect-cli.js +312 -0
  40. package/dist/connection/coordinator.js +74 -0
  41. package/dist/connection/credentials.js +29 -0
  42. package/dist/connection/crypto.js +56 -0
  43. package/dist/connection/dump.js +79 -0
  44. package/dist/connection/incomplete-setup.js +81 -0
  45. package/dist/connection/interactive.js +814 -0
  46. package/dist/connection/legacy-profile-reader.js +47 -0
  47. package/dist/connection/magic-link.js +138 -0
  48. package/dist/connection/migrate.js +76 -0
  49. package/dist/connection/onboarding.js +524 -0
  50. package/dist/connection/origin.js +63 -0
  51. package/dist/connection/phase.js +93 -0
  52. package/dist/connection/phone.js +20 -0
  53. package/dist/connection/promote-active.js +53 -0
  54. package/dist/connection/reset.js +20 -0
  55. package/dist/connection/setup-guidance.js +154 -0
  56. package/dist/connection/status-report.js +40 -0
  57. package/dist/connection/store.js +352 -0
  58. package/dist/connection/token-auth.js +42 -0
  59. package/dist/connection/types.js +2 -0
  60. package/dist/connection/wallet-setup.js +70 -0
  61. package/dist/constants/wallet-identity.js +11 -0
  62. package/dist/index.js +47 -0
  63. package/dist/load-env.js +16 -0
  64. package/dist/neozipkit-node.js +11 -0
  65. package/dist/register/resources.js +14 -0
  66. package/dist/register/tools.js +77 -0
  67. package/dist/resources/zip-resource.js +40 -0
  68. package/dist/resources/zip-uri.js +23 -0
  69. package/dist/security/auth.js +28 -0
  70. package/dist/security/capabilities.js +85 -0
  71. package/dist/security/rate-limiter.js +43 -0
  72. package/dist/security/resource-limiter.js +44 -0
  73. package/dist/security/sandbox.js +61 -0
  74. package/dist/server.js +32 -0
  75. package/dist/startup-account-gate.js +101 -0
  76. package/dist/startup-summary.js +40 -0
  77. package/dist/token-service/require-configured.js +23 -0
  78. package/dist/tools/account.js +504 -0
  79. package/dist/tools/compress.js +237 -0
  80. package/dist/tools/connect-status.js +143 -0
  81. package/dist/tools/extract.js +62 -0
  82. package/dist/tools/grep-entries.js +42 -0
  83. package/dist/tools/identity-status.js +157 -0
  84. package/dist/tools/info.js +147 -0
  85. package/dist/tools/list.js +118 -0
  86. package/dist/tools/lookup-recipient.js +37 -0
  87. package/dist/tools/mint.js +41 -0
  88. package/dist/tools/read-entry.js +35 -0
  89. package/dist/tools/search-entries.js +71 -0
  90. package/dist/tools/stamp.js +60 -0
  91. package/dist/tools/test.js +90 -0
  92. package/dist/tools/token-service-account.js +143 -0
  93. package/dist/tools/upgrade.js +60 -0
  94. package/dist/tools/verify.js +75 -0
  95. package/dist/tools/wallet-config-status.js +119 -0
  96. package/dist/tools/wallet-info.js +64 -0
  97. package/dist/translators/index.js +106 -0
  98. package/dist/types/index.js +7 -0
  99. package/dist/util/mask.js +30 -0
  100. package/dist/util/token-service-fetch.js +23 -0
  101. package/dist/vendor/neozipkit-pro.js +3 -0
  102. package/docs/NEOZIP_CONNECTION_STORE.md +238 -0
  103. package/docs/NEOZIP_CONNECT_CLI.md +185 -0
  104. package/docs/OPERATIONS.md +992 -0
  105. package/docs/examples/CLAUDE.md.example +22 -0
  106. package/docs/examples/claude/skills/neozip-mcp/SKILL.md +54 -0
  107. package/docs/examples/claude/skills/neozip-notarization/SKILL.md +75 -0
  108. package/docs/examples/mcp.json.claude.example +11 -0
  109. package/docs/examples/neozip-mcp-cursor-rule.mdc +31 -0
  110. package/docs/installation-guides/INSTALL_CLAUDE_CODE.md +286 -0
  111. package/docs/installation-guides/INSTALL_CLAUDE_WORKSPACE.md +301 -0
  112. package/docs/installation-guides/README.md +76 -0
  113. package/package.json +99 -0
@@ -0,0 +1,524 @@
1
+ import { provisionWalletIdentityKey } from "../account/identity-provision.js";
2
+ import { registerEmailCli, exchangeMagicLinkToken, requestPhoneOtp, runWalletEnsureAndAttach, verifyEmailAndExtractToken, verifyPhoneOtp, } from "../account/token-service-identity.js";
3
+ import { generateEvmIdentity } from "../account/wallet-evm.js";
4
+ import { assertCoordinatorWrapSupported, fetchCoordinatorConfigParsed, } from "./coordinator.js";
5
+ import { activeWalletContext, createLocalWalletIdentity, importWalletFromMnemonic, importWalletFromPrivateKey, requirePhonePreconditions, requireWalletPreconditions, saveImportedWallet, validateWalletImportAgainstServer, WALLET_SETUP_HINT, walletAlreadyConfigured, } from "./wallet-setup.js";
6
+ import { migrateFromMcpProfileStore, hasLegacyMcpProfiles } from "./migrate.js";
7
+ import { extractEmailCodePair, normalizeMagicLinkTokenInput, } from "./magic-link.js";
8
+ import { buildAllAccountDetails, buildAccountDetails, } from "./status-report.js";
9
+ import { computeConnectionPhase, computeConnectionPhaseForId, nextConnectCommand, resolveConnectionPhase } from "./phase.js";
10
+ import { resolveTokenServiceOrigin } from "./origin.js";
11
+ import { isPhoneVerified, needsPhoneVerification, validatePhoneE164, } from "./phone.js";
12
+ import { applyActiveConnectionToEnv, deleteConnection, ensureActiveConnection, getActiveConnection, getConnectionDir, logoutConnection, readActiveConnectionSecrets, saveAccessToken, saveEvmWallet, saveIdentityProvision, savePhoneVerified, saveWalletLink, selectConnection, updateConnection, } from "./store.js";
13
+ function result(phase, extra = {}) {
14
+ const meta = getActiveConnection();
15
+ return {
16
+ success: phase === "ready",
17
+ phase,
18
+ connectionId: meta?.id ?? null,
19
+ tokenServiceOrigin: meta?.tokenServiceOrigin ?? resolveTokenServiceOrigin(),
20
+ nextCommand: nextConnectCommand(phase, {
21
+ email: meta?.email,
22
+ phoneE164: meta?.phoneE164,
23
+ }),
24
+ ...extra,
25
+ };
26
+ }
27
+ export async function connectStatus(options) {
28
+ if (options?.autoMigrate !== false && hasLegacyMcpProfiles()) {
29
+ migrateFromMcpProfileStore();
30
+ }
31
+ const origin = resolveTokenServiceOrigin(options?.tokenServiceUrl);
32
+ ensureActiveConnection({ tokenServiceUrl: origin });
33
+ const phase = await resolveConnectionPhase(origin);
34
+ const reachable = await fetchCoordinatorConfigParsed(origin);
35
+ const reportPhase = reachable === null && phase !== "ready" ? "degraded" : phase;
36
+ const accounts = options?.verbose ? buildAllAccountDetails() : undefined;
37
+ const active = getActiveConnection();
38
+ return {
39
+ ...result(reportPhase, {
40
+ message: reportPhase === "degraded"
41
+ ? "Token Service unreachable."
42
+ : reportPhase === "ready"
43
+ ? "Connection ready."
44
+ : undefined,
45
+ }),
46
+ connectionId: active?.id ?? null,
47
+ tokenServiceOrigin: origin,
48
+ accounts,
49
+ };
50
+ }
51
+ export async function connectRegister(input) {
52
+ if (hasLegacyMcpProfiles())
53
+ migrateFromMcpProfileStore();
54
+ const origin = resolveTokenServiceOrigin(input.tokenServiceUrl);
55
+ const email = input.email.trim().toLowerCase();
56
+ ensureActiveConnection({ label: input.label, tokenServiceUrl: origin });
57
+ const reg = await registerEmailCli(origin, email);
58
+ if (!reg.success) {
59
+ return result("degraded", {
60
+ success: false,
61
+ message: reg.error ?? "Failed to send verification email.",
62
+ });
63
+ }
64
+ return result("awaiting_code", {
65
+ success: true,
66
+ message: input.resend
67
+ ? `Verification code re-sent to ${email}. Run neozip-connect verify with the 6-digit code.`
68
+ : "Verification code sent. Run neozip-connect verify with the 6-digit code.",
69
+ });
70
+ }
71
+ async function finalizeEmailVerification(origin, connId, accessToken, email, expiresAt) {
72
+ saveAccessToken(connId, accessToken, email, expiresAt);
73
+ applyActiveConnectionToEnv();
74
+ const updated = getActiveConnection();
75
+ if (await needsPhoneVerification(updated, origin)) {
76
+ return result("phone_required", {
77
+ success: true,
78
+ message: "Email verified. Run neozip-connect phone request --phone +14155551234, then phone verify with the SMS code.",
79
+ });
80
+ }
81
+ const phase = computeConnectionPhase();
82
+ return result(phase, {
83
+ success: true,
84
+ message: phase === "ready"
85
+ ? "Email verified and connection ready."
86
+ : "Email verified. Configure a Data Wallet, then run neozip-connect finish.",
87
+ });
88
+ }
89
+ export async function connectVerify(input) {
90
+ if (hasLegacyMcpProfiles())
91
+ migrateFromMcpProfileStore();
92
+ const origin = resolveTokenServiceOrigin(input.tokenServiceUrl);
93
+ const conn = ensureActiveConnection({ tokenServiceUrl: origin });
94
+ let email = input.email?.trim().toLowerCase();
95
+ let code = input.code?.trim();
96
+ if (input.magicToken?.trim()) {
97
+ const pair = extractEmailCodePair(input.magicToken);
98
+ email = email ?? pair.email;
99
+ code = code ?? pair.code;
100
+ if (!email || !code) {
101
+ const opaque = normalizeMagicLinkTokenInput(input.magicToken);
102
+ if (opaque) {
103
+ try {
104
+ const exchanged = await exchangeMagicLinkToken(origin, opaque);
105
+ return finalizeEmailVerification(origin, conn.id, exchanged.accessToken, exchanged.email, exchanged.expiresAt);
106
+ }
107
+ catch (err) {
108
+ return result("awaiting_code", {
109
+ success: false,
110
+ message: err instanceof Error ? err.message : String(err),
111
+ });
112
+ }
113
+ }
114
+ }
115
+ }
116
+ if (!email || !code) {
117
+ return result("awaiting_code", {
118
+ success: false,
119
+ message: "Provide --email and --code, or --magic-token (full link, email=…&code=…, or opaque token).",
120
+ });
121
+ }
122
+ try {
123
+ const verified = await verifyEmailAndExtractToken(origin, email, code);
124
+ return finalizeEmailVerification(origin, conn.id, verified.accessToken, verified.email, verified.expiresAt);
125
+ }
126
+ catch (err) {
127
+ return result("awaiting_code", {
128
+ success: false,
129
+ message: err instanceof Error ? err.message : String(err),
130
+ });
131
+ }
132
+ }
133
+ export function connectSelect(input) {
134
+ if (hasLegacyMcpProfiles())
135
+ migrateFromMcpProfileStore();
136
+ try {
137
+ const meta = selectConnection(input.connectionId.trim());
138
+ applyActiveConnectionToEnv();
139
+ return result(computeConnectionPhase(), {
140
+ success: true,
141
+ message: `Active connection: ${meta.label} (${meta.id})`,
142
+ accounts: [buildAccountDetails(meta, meta.id)],
143
+ });
144
+ }
145
+ catch (err) {
146
+ return result(computeConnectionPhase(), {
147
+ success: false,
148
+ message: err instanceof Error ? err.message : String(err),
149
+ });
150
+ }
151
+ }
152
+ export function connectRename(input) {
153
+ if (hasLegacyMcpProfiles())
154
+ migrateFromMcpProfileStore();
155
+ const id = input.connectionId?.trim() || getActiveConnection()?.id;
156
+ if (!id) {
157
+ return result("no_profile", {
158
+ success: false,
159
+ message: "No connection to rename.",
160
+ });
161
+ }
162
+ const nextLabel = input.label.trim();
163
+ if (!nextLabel) {
164
+ return result(computeConnectionPhase(), {
165
+ success: false,
166
+ message: "Label is required.",
167
+ });
168
+ }
169
+ try {
170
+ const meta = updateConnection(id, { label: nextLabel });
171
+ return result(computeConnectionPhaseForId(id), {
172
+ success: true,
173
+ message: `Renamed connection to "${meta.label}".`,
174
+ accounts: [buildAccountDetails(meta, getActiveConnection()?.id ?? null)],
175
+ });
176
+ }
177
+ catch (err) {
178
+ return result(computeConnectionPhase(), {
179
+ success: false,
180
+ message: err instanceof Error ? err.message : String(err),
181
+ });
182
+ }
183
+ }
184
+ export async function connectPhoneRequest(input) {
185
+ if (hasLegacyMcpProfiles())
186
+ migrateFromMcpProfileStore();
187
+ const origin = resolveTokenServiceOrigin(input.tokenServiceUrl);
188
+ const conn = ensureActiveConnection({ tokenServiceUrl: origin });
189
+ const secrets = readActiveConnectionSecrets();
190
+ if (!secrets?.accessToken || !conn.emailVerified) {
191
+ return result("email_unverified", {
192
+ success: false,
193
+ message: "Verify email first. Run neozip-connect verify.",
194
+ });
195
+ }
196
+ if (isPhoneVerified(conn)) {
197
+ const phase = computeConnectionPhase();
198
+ return result(phase, {
199
+ success: true,
200
+ message: "Phone already verified.",
201
+ });
202
+ }
203
+ try {
204
+ validatePhoneE164(input.phone);
205
+ }
206
+ catch (err) {
207
+ return result("phone_required", {
208
+ success: false,
209
+ message: err instanceof Error ? err.message : String(err),
210
+ });
211
+ }
212
+ const otp = await requestPhoneOtp(origin, secrets.accessToken, input.phone);
213
+ if (!otp.success) {
214
+ return result("phone_required", {
215
+ success: false,
216
+ message: otp.error ?? "Failed to send phone verification SMS.",
217
+ });
218
+ }
219
+ return result("phone_required", {
220
+ success: true,
221
+ message: `SMS code sent to ${input.phone.trim()}. Run neozip-connect phone verify --phone ${input.phone.trim()} --code <6-digit-code>.`,
222
+ });
223
+ }
224
+ export async function connectPhoneVerify(input) {
225
+ if (hasLegacyMcpProfiles())
226
+ migrateFromMcpProfileStore();
227
+ const origin = resolveTokenServiceOrigin(input.tokenServiceUrl);
228
+ const conn = ensureActiveConnection({ tokenServiceUrl: origin });
229
+ const secrets = readActiveConnectionSecrets();
230
+ if (!secrets?.accessToken || !conn.emailVerified) {
231
+ return result("email_unverified", {
232
+ success: false,
233
+ message: "Verify email first. Run neozip-connect verify.",
234
+ });
235
+ }
236
+ try {
237
+ validatePhoneE164(input.phone);
238
+ }
239
+ catch (err) {
240
+ return result("phone_required", {
241
+ success: false,
242
+ message: err instanceof Error ? err.message : String(err),
243
+ });
244
+ }
245
+ const verified = await verifyPhoneOtp(origin, secrets.accessToken, input.phone, input.code);
246
+ if (!verified.success) {
247
+ return result("phone_required", {
248
+ success: false,
249
+ message: verified.error ?? "Phone verification failed.",
250
+ });
251
+ }
252
+ savePhoneVerified(conn.id, input.phone);
253
+ applyActiveConnectionToEnv();
254
+ const phase = computeConnectionPhase();
255
+ return result(phase, {
256
+ success: true,
257
+ message: phase === "ready"
258
+ ? "Phone verified and connection ready."
259
+ : "Phone verified. Configure a Data Wallet, then run neozip-connect finish.",
260
+ });
261
+ }
262
+ export async function connectWalletCreate(input) {
263
+ if (hasLegacyMcpProfiles())
264
+ migrateFromMcpProfileStore();
265
+ const origin = resolveTokenServiceOrigin(input.tokenServiceUrl);
266
+ ensureActiveConnection({ tokenServiceUrl: origin });
267
+ const { conn, secrets } = activeWalletContext();
268
+ try {
269
+ requireWalletPreconditions(conn, secrets);
270
+ await requirePhonePreconditions(conn, origin);
271
+ }
272
+ catch (err) {
273
+ const message = err instanceof Error ? err.message : String(err);
274
+ const phase = message.includes("Phone") ? "phone_required" : "email_unverified";
275
+ return result(phase, { success: false, message });
276
+ }
277
+ if (walletAlreadyConfigured(conn, secrets)) {
278
+ const phase = computeConnectionPhase();
279
+ return result(phase, {
280
+ success: true,
281
+ message: `Data Wallet already configured (${conn.evmAddress}). Run neozip-connect finish.`,
282
+ });
283
+ }
284
+ if (!input.ackBackup) {
285
+ return result("no_wallet", {
286
+ success: false,
287
+ message: "Creating a wallet requires --ack-backup (recovery phrase is shown once on stderr). " +
288
+ WALLET_SETUP_HINT,
289
+ });
290
+ }
291
+ const evm = createLocalWalletIdentity();
292
+ console.error(`
293
+ Back up your Data Wallet recovery phrase (shown once):
294
+ Address: ${evm.address}
295
+
296
+ Recovery phrase:
297
+ ${evm.mnemonic}
298
+ `);
299
+ saveImportedWallet(conn.id, evm);
300
+ applyActiveConnectionToEnv();
301
+ const phase = computeConnectionPhase();
302
+ return result(phase, {
303
+ success: true,
304
+ message: `Data Wallet created (${evm.address}). Run neozip-connect finish.`,
305
+ });
306
+ }
307
+ export async function connectWalletImportMnemonic(input) {
308
+ if (hasLegacyMcpProfiles())
309
+ migrateFromMcpProfileStore();
310
+ const origin = resolveTokenServiceOrigin(input.tokenServiceUrl);
311
+ ensureActiveConnection({ tokenServiceUrl: origin });
312
+ const { conn, secrets } = activeWalletContext();
313
+ try {
314
+ requireWalletPreconditions(conn, secrets);
315
+ await requirePhonePreconditions(conn, origin);
316
+ }
317
+ catch (err) {
318
+ const message = err instanceof Error ? err.message : String(err);
319
+ const phase = message.includes("Phone") ? "phone_required" : "email_unverified";
320
+ return result(phase, { success: false, message });
321
+ }
322
+ if (walletAlreadyConfigured(conn, secrets)) {
323
+ const phase = computeConnectionPhase();
324
+ return result(phase, {
325
+ success: true,
326
+ message: `Data Wallet already configured (${conn.evmAddress}). Run neozip-connect finish.`,
327
+ });
328
+ }
329
+ const mnemonic = input.mnemonic.trim();
330
+ if (!mnemonic) {
331
+ return result("no_wallet", { success: false, message: "--mnemonic is required." });
332
+ }
333
+ try {
334
+ const evm = importWalletFromMnemonic(mnemonic);
335
+ await validateWalletImportAgainstServer({
336
+ origin,
337
+ accessToken: secrets.accessToken,
338
+ derivedAddress: evm.address,
339
+ force: input.force,
340
+ });
341
+ saveImportedWallet(conn.id, evm);
342
+ applyActiveConnectionToEnv();
343
+ const phase = computeConnectionPhase();
344
+ return result(phase, {
345
+ success: true,
346
+ message: `Imported Data Wallet (${evm.address}). Run neozip-connect finish.`,
347
+ });
348
+ }
349
+ catch (err) {
350
+ return result("no_wallet", {
351
+ success: false,
352
+ message: err instanceof Error ? err.message : String(err),
353
+ });
354
+ }
355
+ }
356
+ export async function connectWalletImportKey(input) {
357
+ if (hasLegacyMcpProfiles())
358
+ migrateFromMcpProfileStore();
359
+ const origin = resolveTokenServiceOrigin(input.tokenServiceUrl);
360
+ ensureActiveConnection({ tokenServiceUrl: origin });
361
+ const { conn, secrets } = activeWalletContext();
362
+ try {
363
+ requireWalletPreconditions(conn, secrets);
364
+ await requirePhonePreconditions(conn, origin);
365
+ }
366
+ catch (err) {
367
+ const message = err instanceof Error ? err.message : String(err);
368
+ const phase = message.includes("Phone") ? "phone_required" : "email_unverified";
369
+ return result(phase, { success: false, message });
370
+ }
371
+ if (walletAlreadyConfigured(conn, secrets)) {
372
+ const phase = computeConnectionPhase();
373
+ return result(phase, {
374
+ success: true,
375
+ message: `Data Wallet already configured (${conn.evmAddress}). Run neozip-connect finish.`,
376
+ });
377
+ }
378
+ const privateKey = input.privateKey.trim();
379
+ if (!privateKey) {
380
+ return result("no_wallet", { success: false, message: "--private-key is required." });
381
+ }
382
+ try {
383
+ const evm = importWalletFromPrivateKey(privateKey);
384
+ await validateWalletImportAgainstServer({
385
+ origin,
386
+ accessToken: secrets.accessToken,
387
+ derivedAddress: evm.address,
388
+ force: input.force,
389
+ });
390
+ saveImportedWallet(conn.id, evm);
391
+ applyActiveConnectionToEnv();
392
+ const phase = computeConnectionPhase();
393
+ return result(phase, {
394
+ success: true,
395
+ message: `Imported Data Wallet (${evm.address}). Run neozip-connect finish.`,
396
+ });
397
+ }
398
+ catch (err) {
399
+ return result("no_wallet", {
400
+ success: false,
401
+ message: err instanceof Error ? err.message : String(err),
402
+ });
403
+ }
404
+ }
405
+ export async function connectFinish(input) {
406
+ if (hasLegacyMcpProfiles())
407
+ migrateFromMcpProfileStore();
408
+ const origin = resolveTokenServiceOrigin(input?.tokenServiceUrl);
409
+ const conn = ensureActiveConnection({ tokenServiceUrl: origin });
410
+ const secrets = readActiveConnectionSecrets();
411
+ if (!secrets?.accessToken || !conn.emailVerified) {
412
+ return result("email_unverified", {
413
+ success: false,
414
+ message: "Email not verified. Run neozip-connect to continue setup.",
415
+ });
416
+ }
417
+ const coordinator = await fetchCoordinatorConfigParsed(origin);
418
+ assertCoordinatorWrapSupported(coordinator);
419
+ if (await needsPhoneVerification(conn, origin)) {
420
+ return result("phone_required", {
421
+ success: false,
422
+ message: "Phone verification required. Run neozip-connect phone request --phone +14155551234, then phone verify.",
423
+ });
424
+ }
425
+ let meta = conn;
426
+ let sec = secrets;
427
+ if (!sec.evmPrivateKeyHex || !meta.evmAddress) {
428
+ if (!input?.createWallet) {
429
+ return result("no_wallet", {
430
+ success: false,
431
+ message: WALLET_SETUP_HINT,
432
+ });
433
+ }
434
+ const evm = generateEvmIdentity();
435
+ meta = saveEvmWallet(meta.id, evm.privateKey, evm.address);
436
+ sec = readActiveConnectionSecrets();
437
+ }
438
+ if (!meta.walletId || meta.linkId == null) {
439
+ const linked = await runWalletEnsureAndAttach(origin, sec.accessToken, sec.evmPrivateKeyHex, meta.evmAddress);
440
+ meta = saveWalletLink(meta.id, linked.walletId, linked.linkId, linked.evmAddress);
441
+ }
442
+ if (!meta.identityKeyId) {
443
+ const provisioned = await provisionWalletIdentityKey({
444
+ baseUrl: origin,
445
+ accessToken: sec.accessToken,
446
+ walletId: meta.walletId,
447
+ evmPrivateKey: sec.evmPrivateKeyHex,
448
+ evmAddress: meta.evmAddress,
449
+ });
450
+ meta = saveIdentityProvision(meta.id, provisioned.identityKeyId, provisioned.x25519PublicKey);
451
+ }
452
+ applyActiveConnectionToEnv();
453
+ return result("ready", {
454
+ success: true,
455
+ message: `Connection ready. Profile stored under ${getConnectionDir()}. Reload MCP in Cursor.`,
456
+ });
457
+ }
458
+ export async function connectSetup(input) {
459
+ if (hasLegacyMcpProfiles())
460
+ migrateFromMcpProfileStore();
461
+ const origin = resolveTokenServiceOrigin(input.tokenServiceUrl);
462
+ ensureActiveConnection({
463
+ label: input.label,
464
+ tokenServiceUrl: origin,
465
+ });
466
+ const ver = await connectVerify({
467
+ email: input.email,
468
+ code: input.code,
469
+ tokenServiceUrl: input.tokenServiceUrl,
470
+ });
471
+ if (ver.phase === "phone_required")
472
+ return ver;
473
+ if (ver.phase === "ready")
474
+ return ver;
475
+ const phase = computeConnectionPhase();
476
+ if (phase === "no_wallet" || phase === "wallet_unlinked" || phase === "identity_missing") {
477
+ return result(phase, {
478
+ success: true,
479
+ message: `Email verified (phase: ${phase}). ${WALLET_SETUP_HINT}`,
480
+ });
481
+ }
482
+ return connectFinish({ tokenServiceUrl: input.tokenServiceUrl });
483
+ }
484
+ export function connectLogout() {
485
+ const out = logoutConnection();
486
+ return result(computeConnectionPhase(), {
487
+ success: true,
488
+ message: `Logged out connection "${out.label}". Run neozip-connect to set up again.`,
489
+ });
490
+ }
491
+ export function connectDelete(input) {
492
+ const id = input?.connectionId?.trim() || getActiveConnection()?.id;
493
+ if (!id) {
494
+ return result("no_profile", {
495
+ success: false,
496
+ message: "No connection to delete.",
497
+ });
498
+ }
499
+ const out = deleteConnection(id);
500
+ return result(computeConnectionPhase(), {
501
+ success: true,
502
+ connectionId: out.newActiveConnectionId,
503
+ message: out.newActiveConnectionId != null
504
+ ? `Deleted connection "${out.label}". Active connection updated.`
505
+ : `Deleted connection "${out.label}". No accounts remain.`,
506
+ });
507
+ }
508
+ export function connectMigrate() {
509
+ const out = migrateFromMcpProfileStore();
510
+ return {
511
+ success: out.migrated > 0,
512
+ phase: computeConnectionPhase(),
513
+ connectionId: out.activeConnectionId,
514
+ tokenServiceOrigin: resolveTokenServiceOrigin(),
515
+ message: out.migrated > 0
516
+ ? `Migrated ${out.migrated} profile(s) to ${out.connectionDir}.`
517
+ : "Nothing to migrate (connection store already populated or no legacy profiles).",
518
+ nextCommand: nextConnectCommand(computeConnectionPhase(), {
519
+ email: getActiveConnection()?.email,
520
+ phoneE164: getActiveConnection()?.phoneE164,
521
+ }),
522
+ };
523
+ }
524
+ //# sourceMappingURL=onboarding.js.map
@@ -0,0 +1,63 @@
1
+ /** Default token service when none is configured. */
2
+ export const NEOZIP_TOKEN_SERVICE_DEFAULT_URL = "https://testnet.token-service.neozip.io";
3
+ /** Local token service URL for development. */
4
+ export const NEOZIP_TOKEN_SERVICE_LOCALHOST = "http://localhost:14789";
5
+ /** Production token service URL. */
6
+ export const NEOZIP_TOKEN_SERVICE_PRODUCTION_URL = "https://token-service.neozip.io";
7
+ /** Interactive menu order for Token Service deployment picker. */
8
+ export const KNOWN_TOKEN_SERVICE_PRESETS = [
9
+ {
10
+ name: "NeoZip Testnet",
11
+ url: NEOZIP_TOKEN_SERVICE_DEFAULT_URL,
12
+ description: "NeoZip Token Service (testnet)",
13
+ },
14
+ {
15
+ name: "NeoZip Production",
16
+ url: NEOZIP_TOKEN_SERVICE_PRODUCTION_URL,
17
+ description: "NeoZip Token Service (production)",
18
+ },
19
+ {
20
+ name: "Local Development",
21
+ url: NEOZIP_TOKEN_SERVICE_LOCALHOST,
22
+ description: "Local NeoZip Token Service",
23
+ },
24
+ ];
25
+ export function defaultTokenServiceMenuChoice(serverUrl) {
26
+ const raw = serverUrl?.trim() || process.env.TOKEN_SERVICE_URL?.trim();
27
+ if (!raw)
28
+ return "1";
29
+ const normalized = normalizeTokenServiceOrigin(raw);
30
+ const presetIdx = KNOWN_TOKEN_SERVICE_PRESETS.findIndex((p) => sameTokenServiceOrigin(p.url, normalized));
31
+ if (presetIdx >= 0)
32
+ return String(presetIdx + 1);
33
+ return "4";
34
+ }
35
+ export function connectionLabelForOrigin(origin) {
36
+ const normalized = normalizeTokenServiceOrigin(origin);
37
+ const preset = KNOWN_TOKEN_SERVICE_PRESETS.find((p) => sameTokenServiceOrigin(p.url, normalized));
38
+ return preset?.name ?? normalized;
39
+ }
40
+ export function normalizeTokenServiceOrigin(url) {
41
+ const trimmed = (url || "").trim();
42
+ if (!trimmed)
43
+ return "";
44
+ const noSlash = trimmed.replace(/\/+$/, "");
45
+ try {
46
+ const u = new URL(noSlash);
47
+ const host = u.hostname.toLowerCase();
48
+ return `${u.protocol}//${host}${u.port ? `:${u.port}` : ""}${u.pathname.replace(/\/+$/, "") || ""}`;
49
+ }
50
+ catch {
51
+ return noSlash;
52
+ }
53
+ }
54
+ export function sameTokenServiceOrigin(a, b) {
55
+ return normalizeTokenServiceOrigin(a) === normalizeTokenServiceOrigin(b);
56
+ }
57
+ export function resolveTokenServiceOrigin(serverUrl) {
58
+ const raw = serverUrl?.trim() ||
59
+ process.env.TOKEN_SERVICE_URL?.trim() ||
60
+ NEOZIP_TOKEN_SERVICE_DEFAULT_URL;
61
+ return normalizeTokenServiceOrigin(raw);
62
+ }
63
+ //# sourceMappingURL=origin.js.map
@@ -0,0 +1,93 @@
1
+ import { needsPhoneVerification } from "./phone.js";
2
+ import { getActiveConnection, getActiveConnectionId, listConnectionIds, listConnections, readActiveConnectionSecrets, readConnectionPublic, readConnectionSecrets, } from "./store.js";
3
+ function shellQuote(value) {
4
+ if (/^[a-zA-Z0-9@._+-]+$/.test(value))
5
+ return value;
6
+ return `"${value.replace(/"/g, '\\"')}"`;
7
+ }
8
+ function phaseFromConnection(meta, secrets) {
9
+ if (!meta || !secrets)
10
+ return "no_profile";
11
+ if (!secrets.accessToken || !meta.emailVerified) {
12
+ return "email_unverified";
13
+ }
14
+ if (meta.phoneE164 && !meta.phoneVerifiedAt) {
15
+ return "phone_required";
16
+ }
17
+ if (!secrets.evmPrivateKeyHex || !meta.evmAddress) {
18
+ return "no_wallet";
19
+ }
20
+ if (!meta.walletId || meta.linkId == null) {
21
+ return "wallet_unlinked";
22
+ }
23
+ if (!meta.identityKeyId) {
24
+ return "identity_missing";
25
+ }
26
+ return "ready";
27
+ }
28
+ export function computeConnectionPhaseForId(connectionId) {
29
+ return phaseFromConnection(readConnectionPublic(connectionId), readConnectionSecrets(connectionId));
30
+ }
31
+ export function listReadyConnectionIds() {
32
+ return listConnectionIds().filter((id) => computeConnectionPhaseForId(id) === "ready");
33
+ }
34
+ export function computeConnectionPhase() {
35
+ const connectionId = getActiveConnectionId();
36
+ if (!connectionId) {
37
+ return listConnections().length > 0 ? "no_profile" : "no_profile";
38
+ }
39
+ return phaseFromConnection(getActiveConnection(), readActiveConnectionSecrets());
40
+ }
41
+ /** Async phase that accounts for coordinator phone requirement. */
42
+ export async function resolveConnectionPhase(tokenServiceUrl) {
43
+ const phase = computeConnectionPhase();
44
+ if (phase === "phone_required")
45
+ return phase;
46
+ const meta = getActiveConnection();
47
+ if (!meta)
48
+ return phase;
49
+ const origin = tokenServiceUrl?.trim() || meta.tokenServiceOrigin;
50
+ if (await needsPhoneVerification(meta, origin)) {
51
+ return "phone_required";
52
+ }
53
+ return phase;
54
+ }
55
+ export function accountPhaseFromConnection() {
56
+ const phase = computeConnectionPhase();
57
+ if (phase === "awaiting_code" || phase === "phone_required") {
58
+ return "email_unverified";
59
+ }
60
+ return phase;
61
+ }
62
+ /** Scriptable next step for incomplete phases (used by CLI JSON and MCP errors). */
63
+ export function nextConnectCommand(phase, context) {
64
+ const email = context?.email?.trim().toLowerCase();
65
+ const phone = context?.phoneE164?.trim();
66
+ switch (phase) {
67
+ case "ready":
68
+ return undefined;
69
+ case "no_profile":
70
+ return email
71
+ ? `neozip-connect register --email ${shellQuote(email)}`
72
+ : "neozip-connect register --email user@example.com";
73
+ case "awaiting_code":
74
+ case "email_unverified":
75
+ return email
76
+ ? `neozip-connect verify --email ${shellQuote(email)} --code 123456`
77
+ : "neozip-connect register --email user@example.com";
78
+ case "phone_required":
79
+ return phone
80
+ ? `neozip-connect phone verify --phone ${shellQuote(phone)} --code 123456`
81
+ : "neozip-connect phone request --phone +14155551234";
82
+ case "no_wallet":
83
+ return "neozip-connect wallet create --ack-backup";
84
+ case "wallet_unlinked":
85
+ case "identity_missing":
86
+ return "neozip-connect finish";
87
+ case "degraded":
88
+ return "neozip-connect status";
89
+ default:
90
+ return "neozip-connect";
91
+ }
92
+ }
93
+ //# sourceMappingURL=phase.js.map