@cakemail-org/cakemail-cli 1.5.0 → 2.0.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.
Files changed (234) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.env.example +40 -0
  3. package/.env.test.example +45 -0
  4. package/CHANGELOG.md +1031 -0
  5. package/README.md +319 -15
  6. package/audit-formats.js +128 -0
  7. package/cakemail.rb +20 -0
  8. package/dist/cli.js +27 -10
  9. package/dist/cli.js.map +1 -1
  10. package/dist/client.d.ts +2 -0
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +16 -6
  13. package/dist/client.js.map +1 -1
  14. package/dist/commands/account.js +1 -1
  15. package/dist/commands/account.js.map +1 -1
  16. package/dist/commands/attributes.js +1 -1
  17. package/dist/commands/attributes.js.map +1 -1
  18. package/dist/commands/campaigns.d.ts.map +1 -1
  19. package/dist/commands/campaigns.js +103 -8
  20. package/dist/commands/campaigns.js.map +1 -1
  21. package/dist/commands/config.d.ts.map +1 -1
  22. package/dist/commands/config.js +63 -4
  23. package/dist/commands/config.js.map +1 -1
  24. package/dist/commands/contacts.d.ts.map +1 -1
  25. package/dist/commands/contacts.js +91 -12
  26. package/dist/commands/contacts.js.map +1 -1
  27. package/dist/commands/emails.js +1 -1
  28. package/dist/commands/emails.js.map +1 -1
  29. package/dist/commands/interests.d.ts +5 -0
  30. package/dist/commands/interests.d.ts.map +1 -0
  31. package/dist/commands/interests.js +172 -0
  32. package/dist/commands/interests.js.map +1 -0
  33. package/dist/commands/lists.d.ts.map +1 -1
  34. package/dist/commands/lists.js +6 -8
  35. package/dist/commands/lists.js.map +1 -1
  36. package/dist/commands/logs.d.ts +5 -0
  37. package/dist/commands/logs.d.ts.map +1 -0
  38. package/dist/commands/logs.js +237 -0
  39. package/dist/commands/logs.js.map +1 -0
  40. package/dist/commands/reports.js +1 -1
  41. package/dist/commands/reports.js.map +1 -1
  42. package/dist/commands/segments.js +1 -1
  43. package/dist/commands/segments.js.map +1 -1
  44. package/dist/commands/senders.d.ts.map +1 -1
  45. package/dist/commands/senders.js +11 -8
  46. package/dist/commands/senders.js.map +1 -1
  47. package/dist/commands/suppressed.js +1 -1
  48. package/dist/commands/suppressed.js.map +1 -1
  49. package/dist/commands/tags.d.ts +5 -0
  50. package/dist/commands/tags.d.ts.map +1 -0
  51. package/dist/commands/tags.js +124 -0
  52. package/dist/commands/tags.js.map +1 -0
  53. package/dist/commands/templates.js +1 -1
  54. package/dist/commands/templates.js.map +1 -1
  55. package/dist/commands/transactional-templates.d.ts +5 -0
  56. package/dist/commands/transactional-templates.d.ts.map +1 -0
  57. package/dist/commands/transactional-templates.js +354 -0
  58. package/dist/commands/transactional-templates.js.map +1 -0
  59. package/dist/commands/webhooks.js +1 -1
  60. package/dist/commands/webhooks.js.map +1 -1
  61. package/dist/utils/auth.d.ts +8 -1
  62. package/dist/utils/auth.d.ts.map +1 -1
  63. package/dist/utils/auth.js +39 -11
  64. package/dist/utils/auth.js.map +1 -1
  65. package/dist/utils/config-file.d.ts +7 -0
  66. package/dist/utils/config-file.d.ts.map +1 -1
  67. package/dist/utils/config-file.js +15 -0
  68. package/dist/utils/config-file.js.map +1 -1
  69. package/dist/utils/config.d.ts +2 -0
  70. package/dist/utils/config.d.ts.map +1 -1
  71. package/dist/utils/config.js +12 -4
  72. package/dist/utils/config.js.map +1 -1
  73. package/dist/utils/errors.js +1 -1
  74. package/dist/utils/errors.js.map +1 -1
  75. package/dist/utils/list-defaults.d.ts +33 -0
  76. package/dist/utils/list-defaults.d.ts.map +1 -0
  77. package/dist/utils/list-defaults.js +52 -0
  78. package/dist/utils/list-defaults.js.map +1 -0
  79. package/dist/utils/output.d.ts.map +1 -1
  80. package/dist/utils/output.js +36 -13
  81. package/dist/utils/output.js.map +1 -1
  82. package/dist/utils/progress.d.ts.map +1 -1
  83. package/dist/utils/progress.js +32 -4
  84. package/dist/utils/progress.js.map +1 -1
  85. package/dist/utils/spinner.d.ts +17 -0
  86. package/dist/utils/spinner.d.ts.map +1 -0
  87. package/dist/utils/spinner.js +43 -0
  88. package/dist/utils/spinner.js.map +1 -0
  89. package/docs/DOCUMENTATION-STANDARD.md +1068 -0
  90. package/docs/README.md +161 -0
  91. package/docs/developer/ARCHITECTURE.md +516 -0
  92. package/docs/developer/AUTH.md +204 -0
  93. package/docs/developer/CONTRIBUTING.md +227 -0
  94. package/docs/developer/DOCUMENTATION_SUMMARY.md +346 -0
  95. package/docs/developer/PROJECT_INDEX.md +365 -0
  96. package/docs/planning/API_COVERAGE.md +1045 -0
  97. package/docs/planning/BACKLOG.md +1159 -0
  98. package/docs/planning/PROFILE_SYSTEM_TASKS.md +287 -0
  99. package/docs/planning/UX_IMPLEMENTATION_PLAN.md +691 -0
  100. package/docs/planning/archive/RELEASE_CHECKLIST_v1.3.0.md +332 -0
  101. package/docs/planning/archive/RELEASE_v1.3.0.md +428 -0
  102. package/docs/planning/archive/cakemail-cli-ux-improvements.md +438 -0
  103. package/docs/planning/cakemail-profile-system-plan.md +1121 -0
  104. package/docs/testing/AI_USER_SIMULATION_DESIGN.md +1342 -0
  105. package/docs/testing/KENOGAMI_BIDIRECTIONAL_FLOW.md +1517 -0
  106. package/docs/testing/KENOGAMI_TRUTH_RECONCILIATION_SYSTEM.md +1369 -0
  107. package/docs/user-manual/.obsidian/app.json +1 -0
  108. package/docs/user-manual/.obsidian/appearance.json +1 -0
  109. package/docs/user-manual/.obsidian/core-plugins.json +33 -0
  110. package/docs/user-manual/.obsidian/workspace.json +167 -0
  111. package/docs/user-manual/01-getting-started/01-installation.md +214 -0
  112. package/docs/user-manual/01-getting-started/02-quick-start.md +432 -0
  113. package/docs/user-manual/01-getting-started/03-authentication.md +448 -0
  114. package/docs/user-manual/01-getting-started/04-configuration.md +430 -0
  115. package/docs/user-manual/01-getting-started/05-output-formats.md +447 -0
  116. package/docs/user-manual/02-core-concepts/01-accounts.md +514 -0
  117. package/docs/user-manual/02-core-concepts/02-profile-system.md +771 -0
  118. package/docs/user-manual/02-core-concepts/03-smart-defaults.md +485 -0
  119. package/docs/user-manual/02-core-concepts/04-authentication-methods.md +435 -0
  120. package/docs/user-manual/02-core-concepts/05-pagination-filtering.md +600 -0
  121. package/docs/user-manual/02-core-concepts/06-error-handling.md +718 -0
  122. package/docs/user-manual/02-core-concepts/07-api-coverage.md +483 -0
  123. package/docs/user-manual/03-email-operations/01-senders.md +490 -0
  124. package/docs/user-manual/03-email-operations/02-templates.md +444 -0
  125. package/docs/user-manual/03-email-operations/03-transactional-emails.md +706 -0
  126. package/docs/user-manual/03-email-operations/04-email-tracking.md +407 -0
  127. package/docs/user-manual/04-campaign-management/01-campaigns-basics.md +394 -0
  128. package/docs/user-manual/04-campaign-management/02-campaign-scheduling.md +630 -0
  129. package/docs/user-manual/04-campaign-management/03-campaign-testing.md +997 -0
  130. package/docs/user-manual/04-campaign-management/04-campaign-lifecycle.md +709 -0
  131. package/docs/user-manual/04-campaign-management/05-campaign-links.md +934 -0
  132. package/docs/user-manual/05-contact-management/01-lists.md +836 -0
  133. package/docs/user-manual/05-contact-management/02-contacts.md +1035 -0
  134. package/docs/user-manual/05-contact-management/03-custom-attributes.md +788 -0
  135. package/docs/user-manual/05-contact-management/04-segments.md +1028 -0
  136. package/docs/user-manual/05-contact-management/05-contact-import-export.md +1031 -0
  137. package/docs/user-manual/06-analytics-reporting/01-campaign-analytics.md +867 -0
  138. package/docs/user-manual/06-analytics-reporting/02-account-reports.md +227 -0
  139. package/docs/user-manual/07-integrations/01-webhooks-integration.md +259 -0
  140. package/docs/user-manual/07-integrations/02-automation.md +326 -0
  141. package/docs/user-manual/08-advanced-usage/01-scripting-patterns.md +672 -0
  142. package/docs/user-manual/08-advanced-usage/02-bulk-operations.md +932 -0
  143. package/docs/user-manual/08-advanced-usage/03-ci-cd-integration.md +892 -0
  144. package/docs/user-manual/08-advanced-usage/04-performance-optimization.md +766 -0
  145. package/docs/user-manual/09-command-reference/01-config.md +776 -0
  146. package/docs/user-manual/09-command-reference/02-account.md +652 -0
  147. package/docs/user-manual/09-command-reference/03-lists.md +958 -0
  148. package/docs/user-manual/09-command-reference/04-contacts.md +1408 -0
  149. package/docs/user-manual/09-command-reference/05-attributes.md +617 -0
  150. package/docs/user-manual/09-command-reference/06-segments.md +894 -0
  151. package/docs/user-manual/09-command-reference/07-senders.md +803 -0
  152. package/docs/user-manual/09-command-reference/08-templates.md +818 -0
  153. package/docs/user-manual/09-command-reference/09-campaigns.md +1250 -0
  154. package/docs/user-manual/09-command-reference/10-emails.md +807 -0
  155. package/docs/user-manual/09-command-reference/11-reports.md +1135 -0
  156. package/docs/user-manual/09-command-reference/12-webhooks.md +773 -0
  157. package/docs/user-manual/09-command-reference/13-suppressed.md +797 -0
  158. package/docs/user-manual/09-command-reference/14-interests.md +630 -0
  159. package/docs/user-manual/09-command-reference/15-tags.md +584 -0
  160. package/docs/user-manual/09-command-reference/16-logs.md +656 -0
  161. package/docs/user-manual/09-command-reference/17-transactional-templates.md +850 -0
  162. package/docs/user-manual/10-troubleshooting/01-common-errors.md +457 -0
  163. package/docs/user-manual/10-troubleshooting/02-authentication-issues.md +558 -0
  164. package/docs/user-manual/10-troubleshooting/03-connection-problems.md +634 -0
  165. package/docs/user-manual/10-troubleshooting/04-debugging.md +725 -0
  166. package/docs/user-manual/11-appendix/04-faq.md +484 -0
  167. package/docs/user-manual/11-appendix/05-glossary.md +250 -0
  168. package/docs/user-manual/README.md +0 -0
  169. package/package.json +13 -47
  170. package/src/cli.ts +125 -0
  171. package/src/client.ts +16 -0
  172. package/src/commands/account.ts +267 -0
  173. package/src/commands/accounts.ts +78 -0
  174. package/src/commands/actions.ts +249 -0
  175. package/src/commands/attributes.ts +139 -0
  176. package/src/commands/campaign-blueprints.ts +106 -0
  177. package/src/commands/campaigns.ts +469 -0
  178. package/src/commands/config.ts +77 -0
  179. package/src/commands/contacts.ts +612 -0
  180. package/src/commands/custom-attributes.ts +127 -0
  181. package/src/commands/dkims.ts +117 -0
  182. package/src/commands/domains.ts +82 -0
  183. package/src/commands/email-apis.ts +569 -0
  184. package/src/commands/emails.ts +197 -0
  185. package/src/commands/forms.ts +283 -0
  186. package/src/commands/interests.ts +155 -0
  187. package/src/commands/links.ts +38 -0
  188. package/src/commands/lists.ts +406 -0
  189. package/src/commands/logos.ts +71 -0
  190. package/src/commands/logs.ts +386 -0
  191. package/src/commands/reports.ts +306 -0
  192. package/src/commands/segments.ts +158 -0
  193. package/src/commands/senders.ts +204 -0
  194. package/src/commands/sub-accounts.ts +271 -0
  195. package/src/commands/suppressed-emails.ts +234 -0
  196. package/src/commands/suppressed.ts +198 -0
  197. package/src/commands/system-emails.ts +85 -0
  198. package/src/commands/tags.ts +146 -0
  199. package/src/commands/tasks.ts +116 -0
  200. package/src/commands/templates.ts +189 -0
  201. package/src/commands/tokens.ts +83 -0
  202. package/src/commands/transactional-emails.ts +374 -0
  203. package/src/commands/transactional-templates.ts +385 -0
  204. package/src/commands/users.ts +506 -0
  205. package/src/commands/webhooks.ts +172 -0
  206. package/src/commands/workflow-blueprints.ts +123 -0
  207. package/src/commands/workflows.ts +265 -0
  208. package/src/types/profile.ts +93 -0
  209. package/src/utils/auth.ts +272 -0
  210. package/src/utils/config-file.ts +96 -0
  211. package/src/utils/config.ts +134 -0
  212. package/src/utils/confirm.ts +32 -0
  213. package/src/utils/defaults.ts +99 -0
  214. package/src/utils/errors.ts +116 -0
  215. package/src/utils/interactive.ts +91 -0
  216. package/src/utils/list-defaults.ts +74 -0
  217. package/src/utils/output.ts +190 -0
  218. package/src/utils/progress.ts +320 -0
  219. package/src/utils/spinner.ts +22 -0
  220. package/tests/IMPLEMENTATION_STATUS.md +258 -0
  221. package/tests/PTY_SETUP.md +118 -0
  222. package/tests/PTY_TESTING_GUIDE.md +507 -0
  223. package/tests/README.md +244 -0
  224. package/tests/fixtures/api-responses/campaigns.json +34 -0
  225. package/tests/fixtures/test-config.json +13 -0
  226. package/tests/helpers/cli-runner.ts +128 -0
  227. package/tests/helpers/mock-server.ts +301 -0
  228. package/tests/helpers/pty-runner.ts +181 -0
  229. package/tests/integration/campaigns-real-api.test.ts +196 -0
  230. package/tests/integration/setup-integration.ts +50 -0
  231. package/tests/pty/campaigns.test.ts +241 -0
  232. package/tests/setup.ts +34 -0
  233. package/tsconfig.json +15 -0
  234. package/vitest.config.ts +28 -0
package/package.json CHANGED
@@ -1,59 +1,25 @@
1
1
  {
2
2
  "name": "@cakemail-org/cakemail-cli",
3
- "version": "1.5.0",
4
- "description": "Official Cakemail CLI - Command-line interface for the Cakemail API",
3
+ "version": "2.0.0",
4
+ "description": "CLI for the Cakemail API API",
5
5
  "type": "module",
6
- "main": "./dist/index.js",
7
6
  "bin": {
8
7
  "cakemail": "./dist/cli.js"
9
8
  },
10
- "files": [
11
- "dist",
12
- "README.md",
13
- "LICENSE"
14
- ],
15
9
  "scripts": {
16
- "build": "tsc && chmod +x dist/cli.js",
17
- "dev": "tsc --watch",
18
- "start": "node dist/cli.js",
19
- "clean": "rm -rf dist",
20
- "rebuild": "npm run clean && npm run build",
21
- "generate:types": "openapi-typescript https://api.cakemail.com/openapi.json -o src/types/api.ts",
22
- "test": "echo \"Tests not yet implemented\" && exit 0"
10
+ "build": "tsc",
11
+ "dev": "tsx src/cli.ts"
23
12
  },
24
- "keywords": [
25
- "cakemail",
26
- "cli",
27
- "email",
28
- "email-marketing",
29
- "api",
30
- "command-line",
31
- "campaigns",
32
- "transactional-email",
33
- "analytics"
34
- ],
35
- "author": {
36
- "name": "François Lane",
37
- "email": "francois@cakemail.com"
38
- },
39
- "license": "MIT",
40
13
  "dependencies": {
41
- "@cakemail-org/cakemail-sdk": "^2.0.0",
42
- "axios": "^1.6.0",
43
- "chalk": "^5.3.0",
44
- "cli-table3": "^0.6.3",
45
- "commander": "^12.0.0",
46
- "dotenv": "^16.4.0",
47
- "inquirer": "^9.3.8",
48
- "ora": "^8.0.1"
14
+ "@cakemail-org/cakemail-sdk": "^2.2.0",
15
+ "chalk": "^5.4.1",
16
+ "cli-table3": "^0.6.5",
17
+ "commander": "^13.1.0",
18
+ "ora": "^8.2.0"
49
19
  },
50
20
  "devDependencies": {
51
- "@types/inquirer": "^9.0.9",
52
- "@types/node": "^20.11.0",
53
- "openapi-typescript": "^7.8.0",
54
- "typescript": "^5.8.0"
55
- },
56
- "engines": {
57
- "node": ">=18.0.0"
21
+ "@types/node": "^22.15.21",
22
+ "tsx": "^4.19.4",
23
+ "typescript": "^5.8.3"
58
24
  }
59
- }
25
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ // ABOUTME: CLI entry point for cakemail.
3
+ // ABOUTME: Registers all command groups and handles global options.
4
+
5
+ import { Command } from 'commander';
6
+ import { createClient } from './client.js';
7
+ import { OutputFormatter } from './utils/output.js';
8
+ import { getProfileConfig, type ProfileType, type ProfileConfig } from './types/profile.js';
9
+ import { displayError } from './utils/errors.js';
10
+ import { createAccountsCommand } from './commands/accounts.js';
11
+ import { createActionsCommand } from './commands/actions.js';
12
+ import { createCampaignBlueprintsCommand } from './commands/campaign-blueprints.js';
13
+ import { createCampaignsCommand } from './commands/campaigns.js';
14
+ import { createContactsCommand } from './commands/contacts.js';
15
+ import { createCustomAttributesCommand } from './commands/custom-attributes.js';
16
+ import { createDkimsCommand } from './commands/dkims.js';
17
+ import { createDomainsCommand } from './commands/domains.js';
18
+ import { createEmailApisCommand } from './commands/email-apis.js';
19
+ import { createFormsCommand } from './commands/forms.js';
20
+ import { createInterestsCommand } from './commands/interests.js';
21
+ import { createLinksCommand } from './commands/links.js';
22
+ import { createListsCommand } from './commands/lists.js';
23
+ import { createLogosCommand } from './commands/logos.js';
24
+ import { createLogsCommand } from './commands/logs.js';
25
+ import { createReportsCommand } from './commands/reports.js';
26
+ import { createSegmentsCommand } from './commands/segments.js';
27
+ import { createSendersCommand } from './commands/senders.js';
28
+ import { createSubAccountsCommand } from './commands/sub-accounts.js';
29
+ import { createSuppressedEmailsCommand } from './commands/suppressed-emails.js';
30
+ import { createSystemEmailsCommand } from './commands/system-emails.js';
31
+ import { createTagsCommand } from './commands/tags.js';
32
+ import { createTasksCommand } from './commands/tasks.js';
33
+ import { createTemplatesCommand } from './commands/templates.js';
34
+ import { createTokensCommand } from './commands/tokens.js';
35
+ import { createTransactionalEmailsCommand } from './commands/transactional-emails.js';
36
+ import { createUsersCommand } from './commands/users.js';
37
+ import { createWebhooksCommand } from './commands/webhooks.js';
38
+ import { createWorkflowBlueprintsCommand } from './commands/workflow-blueprints.js';
39
+ import { createWorkflowsCommand } from './commands/workflows.js';
40
+
41
+ async function main() {
42
+ const program = new Command();
43
+
44
+ program
45
+ .name('cakemail')
46
+ .description('CLI for the Cakemail API API')
47
+ .version('1.0.0')
48
+ .option('-f, --format <format>', 'Output format (json|table|compact)')
49
+ .option('--profile <type>', 'Profile (developer|marketer|balanced)')
50
+ .option('--access-token <token>', 'Access token (overrides env)')
51
+ .option('--batch', 'Run in batch/scripting mode');
52
+
53
+ const args = process.argv.slice(2);
54
+ const isHelp = args.includes('-h') || args.includes('--help');
55
+ const isVersion = args.length === 1 && (args[0] === '-V' || args[0] === '--version');
56
+
57
+ if (isVersion) {
58
+ await program.parseAsync(process.argv);
59
+ return;
60
+ }
61
+
62
+ try {
63
+ // Parse to get options before command execution
64
+ program.parseOptions(process.argv);
65
+ const opts = program.opts();
66
+
67
+ // Resolve profile
68
+ const profileType = (opts.profile || 'balanced') as ProfileType;
69
+ const profileConfig = getProfileConfig(profileType);
70
+
71
+ // Override format from CLI flag or use profile default
72
+ const format = opts.format || profileConfig.output.format;
73
+
74
+ // Apply batch mode
75
+ if (opts.batch) {
76
+ profileConfig.behavior.interactive_prompts = false;
77
+ profileConfig.behavior.confirm_destructive = false;
78
+ profileConfig.behavior.show_progress = false;
79
+ }
80
+
81
+ const client = createClient({
82
+ accessToken: opts.accessToken || process.env.CAKEMAIL_ACCESS_TOKEN,
83
+ });
84
+
85
+ const formatter = new OutputFormatter(format, profileConfig);
86
+
87
+ program.addCommand(createAccountsCommand(client, formatter));
88
+ program.addCommand(createActionsCommand(client, formatter));
89
+ program.addCommand(createCampaignBlueprintsCommand(client, formatter));
90
+ program.addCommand(createCampaignsCommand(client, formatter));
91
+ program.addCommand(createContactsCommand(client, formatter));
92
+ program.addCommand(createCustomAttributesCommand(client, formatter));
93
+ program.addCommand(createDkimsCommand(client, formatter));
94
+ program.addCommand(createDomainsCommand(client, formatter));
95
+ program.addCommand(createEmailApisCommand(client, formatter));
96
+ program.addCommand(createFormsCommand(client, formatter));
97
+ program.addCommand(createInterestsCommand(client, formatter));
98
+ program.addCommand(createLinksCommand(client, formatter));
99
+ program.addCommand(createListsCommand(client, formatter));
100
+ program.addCommand(createLogosCommand(client, formatter));
101
+ program.addCommand(createLogsCommand(client, formatter));
102
+ program.addCommand(createReportsCommand(client, formatter));
103
+ program.addCommand(createSegmentsCommand(client, formatter));
104
+ program.addCommand(createSendersCommand(client, formatter));
105
+ program.addCommand(createSubAccountsCommand(client, formatter));
106
+ program.addCommand(createSuppressedEmailsCommand(client, formatter));
107
+ program.addCommand(createSystemEmailsCommand(client, formatter));
108
+ program.addCommand(createTagsCommand(client, formatter));
109
+ program.addCommand(createTasksCommand(client, formatter));
110
+ program.addCommand(createTemplatesCommand(client, formatter));
111
+ program.addCommand(createTokensCommand(client, formatter));
112
+ program.addCommand(createTransactionalEmailsCommand(client, formatter));
113
+ program.addCommand(createUsersCommand(client, formatter));
114
+ program.addCommand(createWebhooksCommand(client, formatter));
115
+ program.addCommand(createWorkflowBlueprintsCommand(client, formatter));
116
+ program.addCommand(createWorkflowsCommand(client, formatter));
117
+
118
+ await program.parseAsync(process.argv);
119
+ } catch (error: any) {
120
+ displayError(error, { command: 'cakemail' });
121
+ process.exit(1);
122
+ }
123
+ }
124
+
125
+ main();
package/src/client.ts ADDED
@@ -0,0 +1,16 @@
1
+ // ABOUTME: SDK wrapper for CLI usage.
2
+ // ABOUTME: Configures the SDK client with authentication from CLI options or env vars.
3
+
4
+ import { createClient as createSdkClient } from '@cakemail-org/cakemail-sdk';
5
+
6
+ export interface ClientConfig {
7
+ accessToken?: string;
8
+ baseUrl?: string;
9
+ }
10
+
11
+ export function createClient(config: ClientConfig) {
12
+ return createSdkClient({
13
+ accessToken: config.accessToken,
14
+ baseUrl: config.baseUrl,
15
+ });
16
+ }
@@ -0,0 +1,267 @@
1
+ import { Command } from 'commander';
2
+ import { CakemailClient } from '../client.js';
3
+ import { OutputFormatter } from '../utils/output.js';
4
+ import { saveCredentials, testCredentials } from '../utils/auth.js';
5
+ import { readFileSync, existsSync, writeFileSync } from 'fs';
6
+ import { join } from 'path';
7
+ import ora from '../utils/spinner.js';
8
+ import chalk from 'chalk';
9
+ import { confirmAction } from '../utils/confirm.js';
10
+
11
+ export function createAccountCommand(client: CakemailClient, formatter: OutputFormatter): Command {
12
+ const account = new Command('account')
13
+ .description('Manage account context and multi-tenant access');
14
+
15
+ // Show current account
16
+ account
17
+ .command('show')
18
+ .description('Display current account details')
19
+ .action(async () => {
20
+ const spinner = ora('Fetching account details...').start();
21
+ try {
22
+ const data = await client.sdk.accountService.getSelfAccount();
23
+ spinner.stop();
24
+
25
+ const currentAccountId = process.env.CAKEMAIL_CURRENT_ACCOUNT_ID;
26
+ if (currentAccountId) {
27
+ formatter.info(`Current account context: ${currentAccountId}`);
28
+ }
29
+
30
+ formatter.output(data);
31
+ } catch (error: any) {
32
+ spinner.stop();
33
+ formatter.error(error.message);
34
+ process.exit(1);
35
+ }
36
+ });
37
+
38
+ // List all accessible accounts
39
+ account
40
+ .command('list')
41
+ .description('List all accessible accounts (parent and sub-accounts)')
42
+ .option('-r, --recursive', 'Include nested sub-accounts')
43
+ .action(async (options) => {
44
+ const spinner = ora('Fetching accessible accounts...').start();
45
+ try {
46
+ // Get main account
47
+ const mainAccount = await client.sdk.accountService.getSelfAccount();
48
+ const accounts: Array<{ id: string; name?: string }> = [mainAccount.data];
49
+
50
+ // Get sub-accounts
51
+ const subAccounts = await client.sdk.subAccountService.listAccounts({
52
+ partnerAccountId: parseInt(mainAccount.data.id),
53
+ recursive: options.recursive !== false
54
+ });
55
+
56
+ if (subAccounts.data && subAccounts.data.length > 0) {
57
+ accounts.push(...subAccounts.data);
58
+ }
59
+
60
+ spinner.stop();
61
+
62
+ // Mark current account
63
+ const currentAccountId = process.env.CAKEMAIL_CURRENT_ACCOUNT_ID || mainAccount.data.id;
64
+
65
+ // Add current marker to each account for display
66
+ const accountsWithMarker = accounts.map(acc => ({
67
+ ...acc,
68
+ current: acc.id === currentAccountId
69
+ }));
70
+
71
+ formatter.output({ data: accountsWithMarker, count: accounts.length });
72
+ } catch (error: any) {
73
+ spinner.stop();
74
+ formatter.error(error.message);
75
+ process.exit(1);
76
+ }
77
+ });
78
+
79
+ // Switch active account
80
+ account
81
+ .command('use <id>')
82
+ .description('Switch active account context')
83
+ .action(async (id: string) => {
84
+ const spinner = ora('Switching account context...').start();
85
+ try {
86
+ const accountId = id;
87
+
88
+ // Verify the account exists and is accessible
89
+ // First check if it's the main account
90
+ const mainAccount = await client.sdk.accountService.getSelfAccount();
91
+
92
+ if (mainAccount.data.id === accountId) {
93
+ // It's the main account
94
+ spinner.stop();
95
+ } else {
96
+ // Check sub-accounts
97
+ const subAccounts = await client.sdk.subAccountService.listAccounts({
98
+ partnerAccountId: parseInt(mainAccount.data.id),
99
+ recursive: true
100
+ });
101
+
102
+ const hasAccess = subAccounts.data?.some(acc => acc.id === accountId);
103
+
104
+ if (!hasAccess) {
105
+ spinner.stop();
106
+ formatter.error(`Account ${accountId} not found or not accessible`);
107
+ process.exit(1);
108
+ }
109
+
110
+ spinner.stop();
111
+ }
112
+
113
+ // Update .env file with new account context
114
+ const envPath = join(process.cwd(), '.env');
115
+ let envContent = '';
116
+
117
+ if (existsSync(envPath)) {
118
+ envContent = readFileSync(envPath, 'utf-8');
119
+ }
120
+
121
+ // Parse existing env variables
122
+ const envLines = envContent.split('\n');
123
+ const envVars: Record<string, string> = {};
124
+
125
+ envLines.forEach(line => {
126
+ const match = line.match(/^([^=]+)=(.*)$/);
127
+ if (match) {
128
+ envVars[match[1].trim()] = match[2].trim();
129
+ }
130
+ });
131
+
132
+ // Update account ID
133
+ envVars['CAKEMAIL_CURRENT_ACCOUNT_ID'] = accountId;
134
+
135
+ // Rebuild .env content
136
+ const newEnvContent = Object.entries(envVars)
137
+ .map(([key, value]) => `${key}=${value}`)
138
+ .join('\n') + '\n';
139
+
140
+ writeFileSync(envPath, newEnvContent, 'utf-8');
141
+
142
+ // Get account name for confirmation
143
+ let accountName = `Account ${accountId}`;
144
+ if (mainAccount.data.id === accountId) {
145
+ accountName = mainAccount.data.name || accountName;
146
+ } else {
147
+ const subAccounts = await client.sdk.subAccountService.listAccounts({
148
+ partnerAccountId: parseInt(mainAccount.data.id),
149
+ recursive: true
150
+ });
151
+ const targetAccount = subAccounts.data?.find(acc => acc.id === accountId);
152
+ if (targetAccount) {
153
+ accountName = targetAccount.name || accountName;
154
+ }
155
+ }
156
+
157
+ formatter.success(`Now using: ${accountName} (ID: ${accountId})`);
158
+ } catch (error: any) {
159
+ spinner.stop();
160
+ formatter.error(error.message);
161
+ process.exit(1);
162
+ }
163
+ });
164
+
165
+ // Test current credentials
166
+ account
167
+ .command('test')
168
+ .description('Validate current credentials')
169
+ .action(async () => {
170
+ const spinner = ora('Testing credentials...').start();
171
+ try {
172
+ const email = process.env.CAKEMAIL_EMAIL;
173
+ const password = process.env.CAKEMAIL_PASSWORD;
174
+
175
+ if (!email || !password) {
176
+ spinner.stop();
177
+ formatter.error('No credentials found in .env file');
178
+ process.exit(1);
179
+ }
180
+
181
+ const isValid = await testCredentials(email, password);
182
+
183
+ spinner.stop();
184
+
185
+ if (isValid) {
186
+ formatter.success('Credentials are valid');
187
+
188
+ // Show account info
189
+ const accountData = await client.sdk.accountService.getSelfAccount();
190
+ const account = accountData.data;
191
+ formatter.output({
192
+ authenticated_as: email,
193
+ account: account.name || `ID ${account.id}`,
194
+ ...account
195
+ });
196
+ } else {
197
+ formatter.error('Invalid credentials');
198
+ process.exit(1);
199
+ }
200
+ } catch (error: any) {
201
+ spinner.stop();
202
+ formatter.error(error.message);
203
+ process.exit(1);
204
+ }
205
+ });
206
+
207
+ // Logout (remove credentials)
208
+ account
209
+ .command('logout')
210
+ .description('Remove credentials from .env file')
211
+ .option('-f, --force', 'Skip confirmation prompt')
212
+ .action(async (options) => {
213
+ // Interactive confirmation (unless --force is used)
214
+ if (!options.force) {
215
+ const confirmed = await confirmAction('Log out and remove credentials?', [
216
+ 'Credentials will be removed from .env file',
217
+ 'You will need to re-authenticate'
218
+ ], 'low');
219
+
220
+ if (!confirmed) {
221
+ formatter.info('Logout cancelled');
222
+ return;
223
+ }
224
+ }
225
+
226
+ try {
227
+ const envPath = join(process.cwd(), '.env');
228
+
229
+ if (!existsSync(envPath)) {
230
+ formatter.info('No .env file found');
231
+ return;
232
+ }
233
+
234
+ const envContent = readFileSync(envPath, 'utf-8');
235
+ const envLines = envContent.split('\n');
236
+ const envVars: Record<string, string> = {};
237
+
238
+ envLines.forEach(line => {
239
+ const match = line.match(/^([^=]+)=(.*)$/);
240
+ if (match) {
241
+ envVars[match[1].trim()] = match[2].trim();
242
+ }
243
+ });
244
+
245
+ // Remove auth-related variables
246
+ delete envVars['CAKEMAIL_EMAIL'];
247
+ delete envVars['CAKEMAIL_PASSWORD'];
248
+ delete envVars['CAKEMAIL_ACCESS_TOKEN'];
249
+ delete envVars['CAKEMAIL_CURRENT_ACCOUNT_ID'];
250
+
251
+ // Rebuild .env content
252
+ const newEnvContent = Object.entries(envVars)
253
+ .map(([key, value]) => `${key}=${value}`)
254
+ .join('\n') + '\n';
255
+
256
+ writeFileSync(envPath, newEnvContent, 'utf-8');
257
+
258
+ formatter.success('Logged out successfully');
259
+ formatter.info('Credentials removed from .env file');
260
+ } catch (error: any) {
261
+ formatter.error(error.message);
262
+ process.exit(1);
263
+ }
264
+ });
265
+
266
+ return account;
267
+ }
@@ -0,0 +1,78 @@
1
+ // ABOUTME: CLI commands for Account.
2
+ // ABOUTME: Generated by api-kit from the OpenAPI spec.
3
+
4
+ import { Command } from 'commander';
5
+ import { createSpinner } from '../utils/spinner.js';
6
+
7
+ export function createAccountsCommand(
8
+ client: any,
9
+ formatter: any
10
+ ): Command {
11
+ const cmd = new Command('accounts')
12
+ .description('Manage the authenticated user's own account. An account represents an organization on the platform with its own contacts, campaigns, and settings. Use these endpoints to view or update account details, address, and domains. To manage sub-accounts under a partner account, see the Sub-Account endpoints.');
13
+
14
+
15
+ cmd
16
+ .command('get-self')
17
+ .description('Show my account details')
18
+ .action(async () => {
19
+ const spinner = createSpinner('Fetching account...').start();
20
+ try {
21
+ const data = await client.getSelfAccount({
22
+ });
23
+
24
+ spinner.stop();
25
+ formatter.output(data);
26
+ } catch (error: any) {
27
+ spinner.stop();
28
+ formatter.error(error.message);
29
+ process.exit(1);
30
+ }
31
+ });
32
+
33
+
34
+ cmd
35
+ .command('patch-self')
36
+ .description('Update my account')
37
+ .requiredOption('--data <value>', 'Request body as JSON string')
38
+ .action(async (options) => {
39
+ const spinner = createSpinner('Updating account...').start();
40
+ try {
41
+ const body = JSON.parse(options.data);
42
+ const data = await client.patchSelfAccount({
43
+ ...body,
44
+ });
45
+
46
+ spinner.stop();
47
+ formatter.success('Account updated successfully');
48
+ formatter.output(data);
49
+ } catch (error: any) {
50
+ spinner.stop();
51
+ formatter.error(error.message);
52
+ process.exit(1);
53
+ }
54
+ });
55
+
56
+
57
+ cmd
58
+ .command('convert-self-account-to-organization')
59
+ .description('Convert my account to an Organization')
60
+ .action(async () => {
61
+ const spinner = createSpinner('Creating account...').start();
62
+ try {
63
+ const data = await client.convertSelfAccountToOrganization({
64
+ });
65
+
66
+ spinner.stop();
67
+ formatter.success('Account created successfully');
68
+ formatter.output(data);
69
+ } catch (error: any) {
70
+ spinner.stop();
71
+ formatter.error(error.message);
72
+ process.exit(1);
73
+ }
74
+ });
75
+
76
+
77
+ return cmd;
78
+ }