doomain 0.1.10 → 0.1.12

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.
@@ -2,6 +2,7 @@ import { Command } from '@oclif/core';
2
2
  export default class DomainsList extends Command {
3
3
  static description: string;
4
4
  static flags: {
5
+ account: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
5
6
  domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
7
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
8
  provider: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,8 +1,9 @@
1
1
  import { Command } from '@oclif/core';
2
2
  import { loadConfig } from '../../lib/config.js';
3
- import { domainFlag, jsonFlag, providerFlag } from '../../lib/flags.js';
3
+ import { accountFlag, domainFlag, jsonFlag, providerFlag } from '../../lib/flags.js';
4
4
  import { createOutput, outputError } from '../../lib/output.js';
5
- import { createProvider } from '../../lib/providers/registry.js';
5
+ import { DEFAULT_PROVIDER_ACCOUNT, isDefaultProviderAccount, listConfiguredProviderAccounts, normalizeProviderAccount, } from '../../lib/providers/core/config.js';
6
+ import { createProvider, getProviderDefinition } from '../../lib/providers/registry.js';
6
7
  import { normalizeDomain } from '../../lib/validate.js';
7
8
  async function resolveZones(provider, domain) {
8
9
  if (!domain)
@@ -16,6 +17,7 @@ async function resolveZones(provider, domain) {
16
17
  export default class DomainsList extends Command {
17
18
  static description = 'List DNS zones and records for a provider.';
18
19
  static flags = {
20
+ account: accountFlag,
19
21
  domain: domainFlag,
20
22
  json: jsonFlag,
21
23
  provider: providerFlag,
@@ -25,17 +27,37 @@ export default class DomainsList extends Command {
25
27
  const out = createOutput({ json: flags.json });
26
28
  try {
27
29
  const config = await loadConfig();
28
- const provider = await createProvider(flags.provider ?? process.env.DOOMAIN_PROVIDER ?? config.defaults?.provider ?? 'spaceship');
29
- const zones = await resolveZones(provider, flags.domain);
30
+ const providerId = flags.provider ?? process.env.DOOMAIN_PROVIDER ?? config.defaults?.provider ?? 'spaceship';
31
+ const definition = getProviderDefinition(providerId);
32
+ const account = flags.account ? normalizeProviderAccount(flags.account) : undefined;
33
+ const accounts = account
34
+ ? [{ account, isDefaultAccount: isDefaultProviderAccount(account), providerId: definition.id }]
35
+ : listConfiguredProviderAccounts(config, definition);
36
+ const selectedAccounts = accounts.length > 0 ? accounts : [{ account: DEFAULT_PROVIDER_ACCOUNT, isDefaultAccount: true, providerId: definition.id }];
30
37
  const results = [];
31
- for (const zone of zones) {
32
- const records = await provider.listRecords(zone);
33
- results.push({ zone, records });
34
- out.info(`${zone.name} (${records.length} records)`);
35
- for (const record of records)
36
- out.info(` ${record.type} ${record.name} -> ${record.value}`);
38
+ for (const selectedAccount of selectedAccounts) {
39
+ const provider = await createProvider(definition.id, { account: selectedAccount.account });
40
+ const zones = await resolveZones(provider, flags.domain);
41
+ for (const zone of zones) {
42
+ const records = await provider.listRecords(zone);
43
+ results.push({
44
+ account: selectedAccount.account,
45
+ isDefaultAccount: selectedAccount.isDefaultAccount,
46
+ provider: provider.id,
47
+ records,
48
+ zone,
49
+ });
50
+ const accountLabel = selectedAccount.isDefaultAccount ? provider.id : `${provider.id}/${selectedAccount.account}`;
51
+ out.info(`${zone.name} (${records.length} records) via ${accountLabel}`);
52
+ for (const record of records)
53
+ out.info(` ${record.type} ${record.name} -> ${record.value}`);
54
+ }
37
55
  }
38
- out.result({ provider: provider.id, zones: results });
56
+ out.result({
57
+ ...(selectedAccounts.length === 1 ? { account: selectedAccounts[0].account, isDefaultAccount: selectedAccounts[0].isDefaultAccount } : {}),
58
+ provider: definition.id,
59
+ zones: results,
60
+ });
39
61
  }
40
62
  catch (error) {
41
63
  outputError(out.json, error, 'DOMAIN_LINK_FAILED');
@@ -6,6 +6,7 @@ export default class Link extends Command {
6
6
  domain: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
7
  };
8
8
  static flags: {
9
+ account: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
10
  apex: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
11
  domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
12
  'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -1,6 +1,6 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import { DoomainError } from '../lib/errors.js';
3
- import { apexFlag, domainFlag, jsonFlag, projectFlag, providerFlag, subdomainFlag } from '../lib/flags.js';
3
+ import { accountFlag, apexFlag, domainFlag, jsonFlag, projectFlag, providerFlag, subdomainFlag } from '../lib/flags.js';
4
4
  import { linkDomain } from '../lib/link-domain.js';
5
5
  import { createOutput, outputError } from '../lib/output.js';
6
6
  export default class Link extends Command {
@@ -11,11 +11,13 @@ export default class Link extends Command {
11
11
  '<%= config.bin %> <%= command.id %> --domain app.example.com --project my-app --json',
12
12
  '<%= config.bin %> <%= command.id %> --domain example.com --subdomain app --project my-app',
13
13
  '<%= config.bin %> <%= command.id %> --provider spaceship --domain example.com --apex --project my-app --json',
14
+ '<%= config.bin %> <%= command.id %> app.example.com --provider spaceship --account work --project my-app --json',
14
15
  ];
15
16
  static args = {
16
17
  domain: Args.string({ description: 'Target domain to link, for example app.example.com.', required: false }),
17
18
  };
18
19
  static flags = {
20
+ account: accountFlag,
19
21
  apex: apexFlag,
20
22
  domain: domainFlag,
21
23
  'dry-run': Flags.boolean({ description: 'Preview changes without writing to Vercel or DNS.' }),
@@ -5,6 +5,7 @@ export default class ProvidersConnect extends Command {
5
5
  };
6
6
  static description: string;
7
7
  static flags: {
8
+ account: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
9
  'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
10
  'api-secret': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
11
  credential: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,10 +1,10 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import * as p from '@clack/prompts';
3
3
  import { getConfigPath, loadConfig, maskSecret, updateConfig } from '../../lib/config.js';
4
- import { jsonFlag } from '../../lib/flags.js';
4
+ import { accountFlag, jsonFlag } from '../../lib/flags.js';
5
5
  import { createOutput, outputError } from '../../lib/output.js';
6
+ import { DEFAULT_PROVIDER_ACCOUNT, isDefaultProviderAccount, listConfiguredProviderAccounts, normalizeProviderAccount, providerAccountHasCredentials, withProviderAccountCredentials, } from '../../lib/providers/core/config.js';
6
7
  import { getProviderDefinition, listProviderDefinitions } from '../../lib/providers/registry.js';
7
- import { isProviderConfigured } from '../../lib/providers/status.js';
8
8
  function requireString(value, message) {
9
9
  if (typeof value === 'string' && value.trim())
10
10
  return value.trim();
@@ -58,12 +58,40 @@ async function promptCredential(credential, detectedPublicIp) {
58
58
  }
59
59
  return typeof value === 'string' && value.trim() ? value.trim() : null;
60
60
  }
61
+ function validateProviderAccount(value) {
62
+ try {
63
+ normalizeProviderAccount(value);
64
+ return undefined;
65
+ }
66
+ catch (error) {
67
+ return error instanceof Error ? error.message : String(error);
68
+ }
69
+ }
70
+ async function promptProviderAccount() {
71
+ const value = await p.text({ message: 'Profile name', placeholder: DEFAULT_PROVIDER_ACCOUNT, validate: validateProviderAccount });
72
+ if (p.isCancel(value)) {
73
+ p.cancel('Cancelled');
74
+ return null;
75
+ }
76
+ return normalizeProviderAccount(value);
77
+ }
78
+ async function confirmProviderAccountOverwrite(definition, account) {
79
+ const value = await p.confirm({
80
+ initialValue: false,
81
+ message: `Profile "${account}" already exists for ${definition.displayName}. Overwrite it?`,
82
+ });
83
+ if (p.isCancel(value)) {
84
+ p.cancel('Cancelled');
85
+ return false;
86
+ }
87
+ return value;
88
+ }
61
89
  async function promptProvider() {
62
90
  const config = await loadConfig();
63
91
  const selected = await p.select({
64
92
  message: 'Choose DNS provider',
65
93
  options: listProviderDefinitions().map((definition) => ({
66
- hint: isProviderConfigured(definition, config) ? 'Connected' : 'Not connected',
94
+ hint: listConfiguredProviderAccounts(config, definition).length > 0 ? 'Connected' : 'Not connected',
67
95
  label: definition.displayName,
68
96
  value: definition.id,
69
97
  })),
@@ -91,6 +119,7 @@ export default class ProvidersConnect extends Command {
91
119
  };
92
120
  static description = 'Save DNS provider credentials locally.';
93
121
  static flags = {
122
+ account: accountFlag,
94
123
  'api-key': Flags.string({ description: 'Compatibility alias for Spaceship apiKey.' }),
95
124
  'api-secret': Flags.string({ description: 'Compatibility alias for Spaceship apiSecret.' }),
96
125
  credential: Flags.string({ char: 'c', description: 'Provider credential as key=value.', multiple: true }),
@@ -107,6 +136,16 @@ export default class ProvidersConnect extends Command {
107
136
  const definition = args.provider ? getProviderDefinition(args.provider) : await promptProvider();
108
137
  if (!definition)
109
138
  return;
139
+ const account = flags.account ? normalizeProviderAccount(flags.account) : out.json ? DEFAULT_PROVIDER_ACCOUNT : await promptProviderAccount();
140
+ if (!account)
141
+ return;
142
+ const isDefaultAccount = isDefaultProviderAccount(account);
143
+ const currentConfig = await loadConfig();
144
+ if (!out.json && providerAccountHasCredentials(currentConfig, definition.id, account)) {
145
+ const overwrite = await confirmProviderAccountOverwrite(definition, account);
146
+ if (!overwrite)
147
+ return;
148
+ }
110
149
  const passedCredentials = parseCredentialFlags(flags.credential);
111
150
  const credentials = {};
112
151
  const detectedPublicIp = !out.json && usesClientIp(definition) ? await fetchPublicIp() : undefined;
@@ -145,7 +184,6 @@ export default class ProvidersConnect extends Command {
145
184
  spinner = undefined;
146
185
  }
147
186
  let setDefault = true;
148
- const currentConfig = await loadConfig();
149
187
  if (!out.json && currentConfig.defaults?.provider && currentConfig.defaults.provider !== definition.id) {
150
188
  const value = await p.confirm({
151
189
  initialValue: true,
@@ -162,13 +200,16 @@ export default class ProvidersConnect extends Command {
162
200
  defaults: setDefault ? { ...config.defaults, provider: definition.id } : config.defaults,
163
201
  providers: {
164
202
  ...config.providers,
165
- [definition.id]: { credentials },
203
+ [definition.id]: withProviderAccountCredentials(config.providers?.[definition.id], account, credentials),
166
204
  },
167
205
  }));
168
206
  out.result({
207
+ account,
169
208
  configPath: getConfigPath(),
170
209
  credentials: Object.fromEntries(Object.entries(credentials).map(([key, value]) => [key, maskSecret(value)])),
210
+ defaultAccount: isDefaultAccount,
171
211
  domainCount,
212
+ isDefaultAccount,
172
213
  provider: definition.id,
173
214
  verified: !flags['no-verify'],
174
215
  });
@@ -7,6 +7,7 @@ export default class ProvidersDisconnect extends Command {
7
7
  static description: string;
8
8
  static examples: string[];
9
9
  static flags: {
10
+ account: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
11
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
12
  };
12
13
  run(): Promise<void>;
@@ -1,7 +1,8 @@
1
1
  import { Args, Command } from '@oclif/core';
2
2
  import { getConfigPath, updateConfig } from '../../lib/config.js';
3
- import { jsonFlag } from '../../lib/flags.js';
3
+ import { accountFlag, jsonFlag } from '../../lib/flags.js';
4
4
  import { createOutput, outputError } from '../../lib/output.js';
5
+ import { isDefaultProviderAccount, normalizeProviderAccount } from '../../lib/providers/core/config.js';
5
6
  import { getProviderDefinition } from '../../lib/providers/registry.js';
6
7
  function envOverrides(definition) {
7
8
  return definition.credentials.flatMap((credential) => (process.env[credential.env] ? [credential.env] : []));
@@ -17,6 +18,7 @@ export default class ProvidersDisconnect extends Command {
17
18
  '<%= config.bin %> <%= command.id %> cloudflare --json',
18
19
  ];
19
20
  static flags = {
21
+ account: accountFlag,
20
22
  json: jsonFlag,
21
23
  };
22
24
  async run() {
@@ -24,13 +26,41 @@ export default class ProvidersDisconnect extends Command {
24
26
  const out = createOutput({ json: flags.json });
25
27
  try {
26
28
  const definition = getProviderDefinition(args.provider);
29
+ const account = flags.account ? normalizeProviderAccount(flags.account) : undefined;
27
30
  let removed = false;
28
31
  await updateConfig((config) => {
29
32
  const providers = { ...config.providers };
30
- removed = providers[definition.id] !== undefined;
31
- delete providers[definition.id];
33
+ const provider = providers[definition.id];
34
+ if (!account) {
35
+ removed = provider !== undefined;
36
+ delete providers[definition.id];
37
+ }
38
+ else if (provider) {
39
+ const nextProvider = { ...provider };
40
+ if (isDefaultProviderAccount(account)) {
41
+ removed = nextProvider.credentials !== undefined || (definition.id === 'spaceship' && ('apiKey' in nextProvider || 'apiSecret' in nextProvider));
42
+ delete nextProvider.credentials;
43
+ if (definition.id === 'spaceship') {
44
+ delete nextProvider.apiKey;
45
+ delete nextProvider.apiSecret;
46
+ delete nextProvider.domains;
47
+ }
48
+ }
49
+ else {
50
+ const accounts = { ...nextProvider.accounts };
51
+ removed = accounts[account] !== undefined;
52
+ delete accounts[account];
53
+ nextProvider.accounts = Object.keys(accounts).length > 0 ? accounts : undefined;
54
+ }
55
+ if (nextProvider.credentials || nextProvider.settings || (nextProvider.accounts && Object.keys(nextProvider.accounts).length > 0)) {
56
+ providers[definition.id] = nextProvider;
57
+ }
58
+ else {
59
+ delete providers[definition.id];
60
+ }
61
+ }
32
62
  const defaults = { ...config.defaults };
33
- if (defaults.provider === definition.id)
63
+ if (defaults.provider === definition.id && providers[definition.id] === undefined)
34
64
  delete defaults.provider;
35
65
  return {
36
66
  ...config,
@@ -39,7 +69,7 @@ export default class ProvidersDisconnect extends Command {
39
69
  };
40
70
  });
41
71
  const overrides = envOverrides(definition);
42
- out.result({ configPath: getConfigPath(), environmentOverrides: overrides, provider: definition.id, removed });
72
+ out.result({ account, configPath: getConfigPath(), environmentOverrides: overrides, provider: definition.id, removed });
43
73
  if (overrides.length > 0)
44
74
  out.warn(`${definition.displayName} environment credentials are still set: ${overrides.join(', ')}.`);
45
75
  out.success(removed ? `${definition.displayName} credentials removed from ${getConfigPath()}.` : `${definition.displayName} was not connected.`);
@@ -24,8 +24,10 @@ export default class ProvidersStatus extends Command {
24
24
  spinner?.start('Checking DNS providers');
25
25
  const providers = await listProviderStatuses({ verify: !flags['no-verify'] });
26
26
  spinner?.stop('Checked DNS providers');
27
- for (const provider of providers)
28
- out.info(`${provider.displayName} (${provider.id}) - ${formatStatus(provider)}${provider.default ? ' [default]' : ''}`);
27
+ for (const provider of providers) {
28
+ const account = provider.isDefaultAccount ? provider.id : `${provider.id}/${provider.account}`;
29
+ out.info(`${provider.displayName} (${account}) - ${formatStatus(provider)}${provider.default ? ' [default]' : ''}`);
30
+ }
29
31
  out.result({ providers });
30
32
  }
31
33
  }
@@ -5,6 +5,7 @@ export default class ProvidersVerify extends Command {
5
5
  };
6
6
  static description: string;
7
7
  static flags: {
8
+ account: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
9
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
10
  };
10
11
  run(): Promise<void>;
@@ -1,6 +1,7 @@
1
1
  import { Args, Command } from '@oclif/core';
2
- import { jsonFlag } from '../../lib/flags.js';
2
+ import { accountFlag, jsonFlag } from '../../lib/flags.js';
3
3
  import { createOutput, outputError } from '../../lib/output.js';
4
+ import { isDefaultProviderAccount, normalizeProviderAccount } from '../../lib/providers/core/config.js';
4
5
  import { createProvider } from '../../lib/providers/registry.js';
5
6
  export default class ProvidersVerify extends Command {
6
7
  static args = {
@@ -8,15 +9,17 @@ export default class ProvidersVerify extends Command {
8
9
  };
9
10
  static description = 'Verify saved DNS provider credentials.';
10
11
  static flags = {
12
+ account: accountFlag,
11
13
  json: jsonFlag,
12
14
  };
13
15
  async run() {
14
16
  const { args, flags } = await this.parse(ProvidersVerify);
15
17
  const out = createOutput({ json: flags.json });
16
18
  try {
17
- const provider = await createProvider(args.provider);
19
+ const account = normalizeProviderAccount(flags.account);
20
+ const provider = await createProvider(args.provider, { account });
18
21
  const health = await provider.verifyCredentials();
19
- out.result({ health, provider: provider.id });
22
+ out.result({ account, health, isDefaultAccount: isDefaultProviderAccount(account), provider: provider.id });
20
23
  out.success(`${provider.name} credentials verified.`);
21
24
  }
22
25
  catch (error) {
@@ -6,8 +6,8 @@ import { jsonFlag } from '../lib/flags.js';
6
6
  import { createLinkPlan, linkDomain } from '../lib/link-domain.js';
7
7
  import { detectLocalVercelProject } from '../lib/local-vercel.js';
8
8
  import { createOutput, outputError } from '../lib/output.js';
9
+ import { DEFAULT_PROVIDER_ACCOUNT, isDefaultProviderAccount, listConfiguredProviderAccounts, normalizeProviderAccount, providerAccountHasCredentials, withProviderAccountCredentials, } from '../lib/providers/core/config.js';
9
10
  import { createProvider, getProviderDefinition, listProviderDefinitions } from '../lib/providers/registry.js';
10
- import { isProviderConfigured } from '../lib/providers/status.js';
11
11
  import { listGlobalVercelTokens } from '../lib/vercel-auth.js';
12
12
  import { createVercelClient } from '../lib/vercel.js';
13
13
  const PERSONAL_ACCOUNT = '__personal__';
@@ -26,6 +26,28 @@ async function promptRequired(message, opts = {}) {
26
26
  const resolved = cancelIfNeeded(value);
27
27
  return typeof resolved === 'string' ? resolved.trim() : null;
28
28
  }
29
+ function validateProviderAccount(value) {
30
+ try {
31
+ normalizeProviderAccount(value);
32
+ return undefined;
33
+ }
34
+ catch (error) {
35
+ return error instanceof Error ? error.message : String(error);
36
+ }
37
+ }
38
+ async function promptProviderAccount() {
39
+ const value = await p.text({ message: 'Profile name', placeholder: DEFAULT_PROVIDER_ACCOUNT, validate: validateProviderAccount });
40
+ const resolved = cancelIfNeeded(value);
41
+ return typeof resolved === 'string' ? normalizeProviderAccount(resolved) : null;
42
+ }
43
+ async function confirmProviderAccountOverwrite(definition, account) {
44
+ const value = await p.confirm({
45
+ initialValue: false,
46
+ message: `Profile "${account}" already exists for ${definition.displayName}. Overwrite it?`,
47
+ });
48
+ const resolved = cancelIfNeeded(value);
49
+ return resolved === true;
50
+ }
29
51
  async function fetchPublicIp() {
30
52
  const controller = new AbortController();
31
53
  const timeout = setTimeout(() => controller.abort(), 2000);
@@ -72,7 +94,7 @@ async function promptProviderDefinition(definitions, config) {
72
94
  const selected = await p.select({
73
95
  message: 'Choose DNS provider',
74
96
  options: definitions.map((definition) => ({
75
- hint: isProviderConfigured(definition, config) ? 'Connected' : 'Not connected',
97
+ hint: listConfiguredProviderAccounts(config, definition).length > 0 ? 'Connected' : 'Not connected',
76
98
  label: definition.displayName,
77
99
  value: definition.id,
78
100
  })),
@@ -87,19 +109,24 @@ function showProviderSetup(definition) {
87
109
  return;
88
110
  p.note(definition.setup.notes.join('\n'), `${definition.displayName} setup`);
89
111
  }
90
- async function listProviderDomainOptions(definition) {
91
- const provider = await createProvider(definition.id);
112
+ async function listProviderDomainOptions(definition, account) {
113
+ const provider = await createProvider(definition.id, { account: account.account });
92
114
  const zones = await provider.listZones();
93
- return zones.map((zone) => toProviderDomainOption(definition, zone));
115
+ return zones.map((zone) => toProviderDomainOption(definition, account, zone));
94
116
  }
95
- function toProviderDomainOption(definition, zone) {
117
+ function toProviderDomainOption(definition, account, zone) {
96
118
  return {
119
+ account: account.account,
97
120
  domain: zone.name,
98
- id: `${definition.id}:${zone.name}`,
121
+ id: `${definition.id}:${account.account}:${zone.name}`,
122
+ isDefaultAccount: account.isDefaultAccount,
99
123
  providerId: definition.id,
100
124
  providerName: definition.displayName,
101
125
  };
102
126
  }
127
+ function providerAccountLabel(option) {
128
+ return option.isDefaultAccount ? option.providerName : `${option.providerName}/${option.account}`;
129
+ }
103
130
  function projectLabel(project) {
104
131
  return project.name ? `${project.name} (${project.id})` : project.id;
105
132
  }
@@ -237,15 +264,24 @@ export default class Wizard extends Command {
237
264
  const project = resolved;
238
265
  const projectDisplay = projectLabel(projects.find((item) => item.id === project) ?? { id: project });
239
266
  p.log.success(`Vercel ready: ${projectDisplay}`);
240
- const configuredProviderDefinitions = providerDefinitions.filter((definition) => isProviderConfigured(definition, config));
267
+ const configuredProviderAccounts = providerDefinitions.flatMap((definition) => listConfiguredProviderAccounts(config, definition).map((account) => ({ account, definition })));
241
268
  const providerFailures = [];
242
269
  const domainOptions = [];
243
- if (configuredProviderDefinitions.length === 0) {
270
+ if (configuredProviderAccounts.length === 0) {
244
271
  p.log.info('No DNS provider is configured yet. Connect one to continue.');
245
272
  const selectedDefinition = await promptProviderDefinition(providerDefinitions, config);
246
273
  if (!selectedDefinition)
247
274
  return;
248
275
  showProviderSetup(selectedDefinition);
276
+ const account = await promptProviderAccount();
277
+ if (!account)
278
+ return;
279
+ if (providerAccountHasCredentials(config, selectedDefinition.id, account)) {
280
+ const overwrite = await confirmProviderAccountOverwrite(selectedDefinition, account);
281
+ if (!overwrite)
282
+ return;
283
+ }
284
+ const providerAccount = { account, isDefaultAccount: isDefaultProviderAccount(account), providerId: selectedDefinition.id };
249
285
  const credentials = await promptProviderCredentials(selectedDefinition);
250
286
  if (!credentials)
251
287
  return;
@@ -256,24 +292,28 @@ export default class Wizard extends Command {
256
292
  const zones = await provider.listZones();
257
293
  domainSpinner.stop(`Connected ${selectedDefinition.displayName} and loaded ${zones.length} domain${zones.length === 1 ? '' : 's'}`);
258
294
  activeSpinner = undefined;
259
- domainOptions.push(...zones.map((zone) => toProviderDomainOption(selectedDefinition, zone)));
295
+ domainOptions.push(...zones.map((zone) => toProviderDomainOption(selectedDefinition, providerAccount, zone)));
260
296
  await updateConfig((current) => ({
261
297
  ...current,
262
298
  defaults: { ...current.defaults, provider: selectedDefinition.id },
263
- providers: { ...current.providers, [selectedDefinition.id]: { credentials } },
299
+ providers: {
300
+ ...current.providers,
301
+ [selectedDefinition.id]: withProviderAccountCredentials(current.providers?.[selectedDefinition.id], account, credentials),
302
+ },
264
303
  vercel: { token: vercelToken, teamId: vercelTeamId },
265
304
  }));
266
305
  }
267
306
  else {
268
307
  const domainSpinner = p.spinner();
269
308
  activeSpinner = domainSpinner;
270
- domainSpinner.start(`Loading domains from ${configuredProviderDefinitions.length} provider${configuredProviderDefinitions.length === 1 ? '' : 's'}`);
271
- for (const definition of configuredProviderDefinitions) {
309
+ domainSpinner.start(`Loading domains from ${configuredProviderAccounts.length} provider account${configuredProviderAccounts.length === 1 ? '' : 's'}`);
310
+ for (const { account, definition } of configuredProviderAccounts) {
272
311
  try {
273
- domainOptions.push(...(await listProviderDomainOptions(definition)));
312
+ domainOptions.push(...(await listProviderDomainOptions(definition, account)));
274
313
  }
275
314
  catch (error) {
276
- providerFailures.push(`${definition.displayName}: ${error instanceof Error ? error.message : String(error)}`);
315
+ const label = account.isDefaultAccount ? definition.displayName : `${definition.displayName}/${account.account}`;
316
+ providerFailures.push(`${label}: ${error instanceof Error ? error.message : String(error)}`);
277
317
  }
278
318
  }
279
319
  domainSpinner.stop(`Loaded ${domainOptions.length} domain${domainOptions.length === 1 ? '' : 's'}`);
@@ -299,14 +339,14 @@ export default class Wizard extends Command {
299
339
  placeholder: 'Type to filter domains...',
300
340
  maxItems: 10,
301
341
  initialValue,
302
- options: domainOptions.map((option) => ({ label: option.domain, value: option.id, hint: option.providerName })),
342
+ options: domainOptions.map((option) => ({ label: option.domain, value: option.id, hint: providerAccountLabel(option) })),
303
343
  });
304
344
  const selectedId = cancelIfNeeded(selectedDomainId);
305
345
  if (selectedId === null)
306
346
  return;
307
347
  selectedDomain = domainOptions.find((option) => option.id === selectedId) ?? domainOptions[0];
308
348
  }
309
- p.log.info(`Using domain ${selectedDomain.domain} from ${selectedDomain.providerName}.`);
349
+ p.log.info(`Using domain ${selectedDomain.domain} from ${providerAccountLabel(selectedDomain)}.`);
310
350
  const domain = selectedDomain.domain;
311
351
  await updateConfig((current) => ({
312
352
  ...current,
@@ -330,8 +370,8 @@ export default class Wizard extends Command {
330
370
  return;
331
371
  }
332
372
  const fullDomain = apex ? domain : `${subdomain}.${domain}`;
333
- const preview = await createLinkPlan({ provider: selectedDomain.providerId, domain, subdomain, apex, project });
334
- p.note([`Vercel: add ${preview.domain} to ${projectDisplay}`, ...preview.records.map((record) => recordPreview(record, selectedDomain.providerName))].join('\n'), 'Preview');
373
+ const preview = await createLinkPlan({ account: selectedDomain.account, provider: selectedDomain.providerId, domain, subdomain, apex, project });
374
+ p.note([`Vercel: add ${preview.domain} to ${projectDisplay}`, ...preview.records.map((record) => recordPreview(record, providerAccountLabel(selectedDomain)))].join('\n'), 'Preview');
335
375
  const confirmed = await p.confirm({
336
376
  message: `Link ${fullDomain} via ${selectedDomain.providerName} to Vercel project ${projectDisplay}?`,
337
377
  initialValue: true,
@@ -343,6 +383,7 @@ export default class Wizard extends Command {
343
383
  activeSpinner = spinner;
344
384
  spinner.start('Adding domain to Vercel');
345
385
  const result = await linkDomain({
386
+ account: selectedDomain.account,
346
387
  provider: selectedDomain.providerId,
347
388
  domain,
348
389
  subdomain,
@@ -1,5 +1,5 @@
1
1
  import { type ProviderStatus } from './providers/status.js';
2
- export type ProviderConnectionStatus = Pick<ProviderStatus, 'configured' | 'default' | 'displayName' | 'docsUrl' | 'id'>;
2
+ export type ProviderConnectionStatus = Pick<ProviderStatus, 'account' | 'configured' | 'default' | 'displayName' | 'docsUrl' | 'id' | 'isDefaultAccount'>;
3
3
  export interface CommandSchema {
4
4
  name: string;
5
5
  description: string;
@@ -9,6 +9,7 @@ export const commandSchemas = [
9
9
  'doomain link --domain app.example.com --project my-app --json',
10
10
  'doomain link --domain example.com --subdomain app --project my-app --json',
11
11
  'doomain link --provider spaceship --domain example.com --apex --project my-app --dry-run --json',
12
+ 'doomain link app.example.com --provider spaceship --account work --project my-app --json',
12
13
  ],
13
14
  agentHint: 'For agent use, try `doomain link <domain> --json` first. Do not inspect project files, run provider status, or use --dry-run unless the user explicitly asks for a preview. The command infers project/provider and returns structured recovery errors when inference fails.',
14
15
  agentInstructions: [
@@ -25,6 +26,7 @@ export const commandSchemas = [
25
26
  flags: [
26
27
  { name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
27
28
  { name: 'provider', type: 'string', description: 'DNS provider id. Inferred from the target domain when omitted.' },
29
+ { name: 'account', type: 'string', description: 'DNS provider profile/account alias. Defaults to the provider default account.' },
28
30
  { name: 'domain', type: 'string', description: 'Target domain or base zone, for example app.example.com or example.com.' },
29
31
  { name: 'subdomain', type: 'string', description: 'Subdomain to add.' },
30
32
  { name: 'apex', type: 'boolean', description: 'Use the root/apex domain.' },
@@ -41,12 +43,14 @@ export const commandSchemas = [
41
43
  examples: [
42
44
  'doomain providers connect',
43
45
  'doomain providers connect spaceship --credential apiKey=key --credential apiSecret=secret --json',
46
+ 'doomain providers connect spaceship --account work --credential apiKey=key --credential apiSecret=secret --json',
44
47
  'doomain providers connect namecheap --credential apiUser=user --credential apiKey=key --credential clientIp=127.0.0.1 --json',
45
48
  'doomain providers connect cloudflare --credential apiToken=token --credential accountId=account_id --json',
46
49
  'doomain providers connect hostinger --credential apiToken=token --json',
47
50
  ],
48
51
  flags: [
49
52
  { name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
53
+ { name: 'account', type: 'string', description: 'DNS provider profile/account alias. Defaults to the provider default account.' },
50
54
  { name: 'credential', type: 'string', description: 'Provider credential as key=value. Can be repeated.' },
51
55
  { name: 'api-key', type: 'string', description: 'Spaceship API key.' },
52
56
  { name: 'api-secret', type: 'string', description: 'Spaceship API secret.' },
@@ -56,9 +60,10 @@ export const commandSchemas = [
56
60
  {
57
61
  name: 'providers add',
58
62
  description: 'Alias for providers connect.',
59
- examples: ['doomain providers add', 'doomain providers add namecheap'],
63
+ examples: ['doomain providers add', 'doomain providers add namecheap', 'doomain providers add spaceship --account work'],
60
64
  flags: [
61
65
  { name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
66
+ { name: 'account', type: 'string', description: 'DNS provider profile/account alias. Defaults to the provider default account.' },
62
67
  { name: 'credential', type: 'string', description: 'Provider credential as key=value. Can be repeated.' },
63
68
  { name: 'no-verify', type: 'boolean', description: 'Save credentials without verifying them first.' },
64
69
  ],
@@ -75,17 +80,29 @@ export const commandSchemas = [
75
80
  {
76
81
  name: 'providers disconnect',
77
82
  description: 'Remove saved DNS provider credentials locally.',
78
- examples: ['doomain providers disconnect namecheap --json', 'doomain providers disconnect cloudflare --json', 'doomain providers disconnect hostinger --json'],
83
+ examples: [
84
+ 'doomain providers disconnect namecheap --json',
85
+ 'doomain providers disconnect cloudflare --json',
86
+ 'doomain providers disconnect spaceship --account work --json',
87
+ 'doomain providers disconnect hostinger --json',
88
+ ],
79
89
  flags: [
80
90
  { name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
91
+ { name: 'account', type: 'string', description: 'DNS provider profile/account alias. Omit to remove all accounts for the provider.' },
81
92
  ],
82
93
  },
83
94
  {
84
95
  name: 'providers verify',
85
96
  description: 'Verify saved DNS provider credentials.',
86
- examples: ['doomain providers verify spaceship --json', 'doomain providers verify namecheap --json', 'doomain providers verify hostinger --json'],
97
+ examples: [
98
+ 'doomain providers verify spaceship --json',
99
+ 'doomain providers verify spaceship --account work --json',
100
+ 'doomain providers verify namecheap --json',
101
+ 'doomain providers verify hostinger --json',
102
+ ],
87
103
  flags: [
88
104
  { name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
105
+ { name: 'account', type: 'string', description: 'DNS provider profile/account alias. Defaults to the provider default account.' },
89
106
  ],
90
107
  },
91
108
  {
@@ -114,11 +131,13 @@ export function getCommandSchema(name) {
114
131
  }
115
132
  async function configuredProviders() {
116
133
  return (await listProviderStatuses({ verify: false })).map((provider) => ({
134
+ account: provider.account,
117
135
  configured: provider.configured,
118
136
  default: provider.default,
119
137
  displayName: provider.displayName,
120
138
  docsUrl: provider.docsUrl,
121
139
  id: provider.id,
140
+ isDefaultAccount: provider.isDefaultAccount,
122
141
  }));
123
142
  }
124
143
  function withProviderConnections(schema, providers) {