ardent-cli-beta 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,8 +12,10 @@ non-interactively. `ardent status` and `ardent logout` inspect and remove that
12
12
  local session. The CLI always talks to Ardent's production control plane. Oclif
13
13
  provides help, version, autocomplete, and native JSON command behavior. Resource
14
14
  context is retained from login. Project listing, creation, deletion, and local
15
- context switching are implemented; other resource commands are not implemented
16
- yet.
15
+ context switching are implemented. Organization member listing, invitation,
16
+ removal, and pending-invitation cancellation are implemented. Connector listing
17
+ and registration are implemented; registration follows connector status, while
18
+ credential delivery and replication readiness remain owed.
17
19
 
18
20
  Set `ARDENT_API_URL` to point the CLI at a development control plane. Without
19
21
  it, the CLI uses `https://production.tryardent.com`.
@@ -28,6 +30,12 @@ npm run build
28
30
  ./bin/run.js project create "My project"
29
31
  ./bin/run.js project switch "My project"
30
32
  ./bin/run.js project delete "My project"
33
+ ./bin/run.js member list
34
+ ./bin/run.js member invite teammate@example.com
35
+ ./bin/run.js member invite administrator@example.com --role admin
36
+ ./bin/run.js member delete teammate@example.com
37
+ ./bin/run.js connector list
38
+ ./bin/run.js connector create postgresql 'postgresql://user:password@database.example.com/app'
31
39
  ARDENT_API_URL=http://localhost:8080 ./bin/run.js login --token <token>
32
40
  ```
33
41
 
@@ -0,0 +1,19 @@
1
+ import { ArdentCommand } from '../../command.js';
2
+ interface ConnectorCreation {
3
+ connector_id: string;
4
+ status: 'registered' | 'registering';
5
+ }
6
+ export default class ConnectorCreate extends ArdentCommand {
7
+ static args: {
8
+ type: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
9
+ url: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
10
+ };
11
+ static description: string;
12
+ static helpGroup: string;
13
+ static flags: {
14
+ environment: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ };
17
+ run(): Promise<ConnectorCreation>;
18
+ }
19
+ export {};
@@ -0,0 +1,56 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { Args, Flags, ux } from '@oclif/core';
3
+ import { loadSession } from '../../auth/credentials.js';
4
+ import { ArdentCommand } from '../../command.js';
5
+ import { createConnector, waitForConnectorRegistration } from '../../connectors/client.js';
6
+ import { terminalText } from '../../terminal.js';
7
+ export default class ConnectorCreate extends ArdentCommand {
8
+ static args = {
9
+ type: Args.string({ description: 'Connector type', required: true }),
10
+ url: Args.string({ description: 'PostgreSQL connection URL', required: true }),
11
+ };
12
+ static description = 'Register a PostgreSQL connector in the selected project';
13
+ static helpGroup = 'Connectors';
14
+ static flags = {
15
+ environment: Flags.string({ description: 'Environment ID (defaults to Ardent Cloud)' }),
16
+ name: Flags.string({ char: 'n', description: 'Connector name' }),
17
+ };
18
+ async run() {
19
+ const { args, flags } = await this.parse(ConnectorCreate);
20
+ if (!['postgres', 'postgresql'].includes(args.type.toLowerCase())) {
21
+ this.error(`Unsupported connector type ${JSON.stringify(args.type)}. Supported: postgresql.`);
22
+ }
23
+ const session = await loadSession(this.config.configDir);
24
+ if (!session?.organization)
25
+ this.error('You must be logged in to run this command. Run ardent login.');
26
+ if (!session.selectedProject)
27
+ this.error('No project selected. Run ardent project list, then ardent project switch <name>.');
28
+ const providedName = flags.name?.trim();
29
+ if (flags.name !== undefined && !providedName)
30
+ this.error('Connector name cannot be empty.');
31
+ const name = providedName ?? `${session.selectedProject.name}-${randomUUID().slice(0, 8)}`;
32
+ const environmentID = flags.environment?.trim();
33
+ if (flags.environment !== undefined && !environmentID)
34
+ this.error('Environment ID cannot be empty.');
35
+ const interactive = !this.jsonEnabled() && ux.action.type === 'spinner';
36
+ if (interactive) {
37
+ ux.action.start(`${terminalText(name)} in ${terminalText(session.selectedProject.name)}`, 'registering');
38
+ }
39
+ else if (!this.jsonEnabled()) {
40
+ this.log(`Registering connector ${terminalText(name)} for project ${terminalText(session.selectedProject.name)}...`);
41
+ }
42
+ const registration = await createConnector(session.token, session.organization.id, session.selectedProject.id, environmentID, name, args.url);
43
+ if (!interactive) {
44
+ if (!this.jsonEnabled()) {
45
+ this.log(`Connector registration started (${terminalText(registration.connector_id)}).`);
46
+ }
47
+ return { connector_id: registration.connector_id, status: 'registering' };
48
+ }
49
+ await waitForConnectorRegistration(session.token, registration.connector_id, (status) => {
50
+ ux.action.status = status;
51
+ });
52
+ ux.action.stop(ux.colorize('green', '✓ registered'));
53
+ this.log('Credential delivery and replication readiness are still pending.');
54
+ return { connector_id: registration.connector_id, status: 'registered' };
55
+ }
56
+ }
@@ -0,0 +1,9 @@
1
+ import { ArdentCommand } from '../../command.js';
2
+ import { type Connector } from '../../connectors/client.js';
3
+ export default class ConnectorList extends ArdentCommand {
4
+ static description: string;
5
+ static helpGroup: string;
6
+ run(): Promise<{
7
+ connectors: Connector[];
8
+ }>;
9
+ }
@@ -0,0 +1,25 @@
1
+ import { loadSession } from '../../auth/credentials.js';
2
+ import { ArdentCommand } from '../../command.js';
3
+ import { listConnectors } from '../../connectors/client.js';
4
+ import { terminalText } from '../../terminal.js';
5
+ export default class ConnectorList extends ArdentCommand {
6
+ static description = 'List connectors in the selected project';
7
+ static helpGroup = 'Connectors';
8
+ async run() {
9
+ const session = await loadSession(this.config.configDir);
10
+ if (!session?.organization)
11
+ this.error('You must be logged in to run this command. Run ardent login.');
12
+ if (!session.selectedProject)
13
+ this.error('No project selected. Run ardent project list, then ardent project switch <name>.');
14
+ const connectors = await listConnectors(session.token, session.organization.id, session.selectedProject.id);
15
+ if (!this.jsonEnabled()) {
16
+ if (connectors.length === 0)
17
+ this.log('No connectors found.');
18
+ const nameWidth = Math.max(0, ...connectors.map(({ name }) => terminalText(name).length));
19
+ for (const connector of connectors) {
20
+ this.log(`${terminalText(connector.name).padEnd(nameWidth)} ${terminalText(connector.status)}`);
21
+ }
22
+ }
23
+ return { connectors };
24
+ }
25
+ }
@@ -0,0 +1,12 @@
1
+ import { ArdentCommand } from '../../command.js';
2
+ export default class MemberDelete extends ArdentCommand {
3
+ static args: {
4
+ email: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static helpGroup: string;
8
+ run(): Promise<{
9
+ email: string;
10
+ status: 'active' | 'pending';
11
+ }>;
12
+ }
@@ -0,0 +1,48 @@
1
+ import { Args } from '@oclif/core';
2
+ import { loadSession } from '../../auth/credentials.js';
3
+ import { ArdentCommand } from '../../command.js';
4
+ import { deleteInvitation, deleteMembership, listInvitations, listMemberships } from '../../members/client.js';
5
+ import { terminalText } from '../../terminal.js';
6
+ export default class MemberDelete extends ArdentCommand {
7
+ static args = {
8
+ email: Args.string({ description: 'Member or pending invitation email', required: true }),
9
+ };
10
+ static description = 'Delete a member or pending invitation by email';
11
+ static helpGroup = 'Members';
12
+ async run() {
13
+ const { args } = await this.parse(MemberDelete);
14
+ const email = args.email.trim().toLowerCase();
15
+ if (!email)
16
+ this.error('Email cannot be empty.');
17
+ const session = await loadSession(this.config.configDir);
18
+ if (!session?.organization)
19
+ this.error('You must be logged in to run this command. Run ardent login.');
20
+ const [memberships, invitations] = await Promise.all([
21
+ listMemberships(session.token, session.organization.id),
22
+ listInvitations(session.token, session.organization.id),
23
+ ]);
24
+ const matchingMemberships = memberships.filter((candidate) => candidate.email.toLowerCase() === email);
25
+ const matchingInvitations = invitations.filter((candidate) => candidate.email.toLowerCase() === email);
26
+ if (matchingMemberships.length + matchingInvitations.length > 1) {
27
+ this.error(`Multiple active members or pending invitations match ${JSON.stringify(email)}.`);
28
+ }
29
+ const [membership] = matchingMemberships;
30
+ const [invitation] = matchingInvitations;
31
+ if (membership) {
32
+ if (!this.jsonEnabled())
33
+ this.log(`Deleting member ${terminalText(membership.email)}...`);
34
+ await deleteMembership(session.token, session.organization.id, membership.user_id);
35
+ if (!this.jsonEnabled())
36
+ this.log(`✓ Deleted member ${terminalText(membership.email)}`);
37
+ return { email: membership.email, status: 'active' };
38
+ }
39
+ if (!invitation)
40
+ this.error(`Member ${JSON.stringify(email)} not found.`);
41
+ if (!this.jsonEnabled())
42
+ this.log(`Canceling invitation for ${terminalText(invitation.email)}...`);
43
+ await deleteInvitation(session.token, session.organization.id, invitation.id);
44
+ if (!this.jsonEnabled())
45
+ this.log(`✓ Canceled invitation for ${terminalText(invitation.email)}`);
46
+ return { email: invitation.email, status: 'pending' };
47
+ }
48
+ }
@@ -0,0 +1,13 @@
1
+ import { ArdentCommand } from '../../command.js';
2
+ import { type Invitation } from '../../members/client.js';
3
+ export default class MemberInvite extends ArdentCommand {
4
+ static args: {
5
+ email: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static description: string;
8
+ static helpGroup: string;
9
+ static flags: {
10
+ role: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ };
12
+ run(): Promise<Invitation>;
13
+ }
@@ -0,0 +1,35 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { loadSession } from '../../auth/credentials.js';
3
+ import { ArdentCommand } from '../../command.js';
4
+ import { inviteMember } from '../../members/client.js';
5
+ import { terminalText } from '../../terminal.js';
6
+ const roles = {
7
+ admin: 'Admin',
8
+ member: 'Member',
9
+ owner: 'Owner',
10
+ viewer: 'Viewer',
11
+ };
12
+ export default class MemberInvite extends ArdentCommand {
13
+ static args = {
14
+ email: Args.string({ description: 'Email address to invite', required: true }),
15
+ };
16
+ static description = 'Invite a member to the organization';
17
+ static helpGroup = 'Members';
18
+ static flags = {
19
+ role: Flags.string({ default: 'member', description: 'Role to grant', options: Object.keys(roles) }),
20
+ };
21
+ async run() {
22
+ const { args, flags } = await this.parse(MemberInvite);
23
+ const email = args.email.trim().toLowerCase();
24
+ if (!email)
25
+ this.error('Email cannot be empty.');
26
+ const session = await loadSession(this.config.configDir);
27
+ if (!session?.organization)
28
+ this.error('You must be logged in to run this command. Run ardent login.');
29
+ const role = roles[flags.role];
30
+ const invitation = await inviteMember(session.token, session.organization.id, email, role);
31
+ if (!this.jsonEnabled())
32
+ this.log(`✓ Invited ${terminalText(invitation.email)} as ${terminalText(invitation.role_display_name)}`);
33
+ return invitation;
34
+ }
35
+ }
@@ -0,0 +1,10 @@
1
+ import { ArdentCommand } from '../../command.js';
2
+ import { type Invitation, type Membership } from '../../members/client.js';
3
+ export default class MemberList extends ArdentCommand {
4
+ static description: string;
5
+ static helpGroup: string;
6
+ run(): Promise<{
7
+ invitations: Invitation[] | null;
8
+ memberships: Membership[];
9
+ }>;
10
+ }
@@ -0,0 +1,41 @@
1
+ import { loadSession } from '../../auth/credentials.js';
2
+ import { ArdentCommand } from '../../command.js';
3
+ import { listMemberships, listReadableInvitations } from '../../members/client.js';
4
+ import { terminalText } from '../../terminal.js';
5
+ export default class MemberList extends ArdentCommand {
6
+ static description = 'List organization members and pending invitations';
7
+ static helpGroup = 'Members';
8
+ async run() {
9
+ const session = await loadSession(this.config.configDir);
10
+ if (!session?.organization)
11
+ this.error('You must be logged in to run this command. Run ardent login.');
12
+ const [memberships, invitations] = await Promise.all([
13
+ listMemberships(session.token, session.organization.id),
14
+ listReadableInvitations(session.token, session.organization.id),
15
+ ]);
16
+ const readableInvitations = invitations ?? [];
17
+ if (!this.jsonEnabled()) {
18
+ const rows = [
19
+ ...memberships.map((membership) => ({
20
+ email: terminalText(membership.email),
21
+ role: terminalText(membership.role_display_name),
22
+ status: 'Active',
23
+ })),
24
+ ...readableInvitations.map((invitation) => ({
25
+ email: terminalText(invitation.email),
26
+ role: terminalText(invitation.role_display_name),
27
+ status: 'Pending',
28
+ })),
29
+ ];
30
+ if (rows.length === 0)
31
+ this.log('No members found.');
32
+ const emailWidth = Math.max(0, ...rows.map(({ email }) => email.length));
33
+ const roleWidth = Math.max(0, ...rows.map(({ role }) => role.length));
34
+ for (const row of rows)
35
+ this.log(`${row.email.padEnd(emailWidth)} ${row.role.padEnd(roleWidth)} ${row.status}`);
36
+ if (invitations === undefined)
37
+ this.log('Pending invitations are not visible with this login.');
38
+ }
39
+ return { invitations: invitations ?? null, memberships };
40
+ }
41
+ }
@@ -0,0 +1,25 @@
1
+ export interface Connector {
2
+ created_at: string;
3
+ environment_id: string;
4
+ id: string;
5
+ name: string;
6
+ project_id: string;
7
+ source: {
8
+ database: string;
9
+ host: string;
10
+ port: number;
11
+ };
12
+ status: string;
13
+ updated_at: string;
14
+ }
15
+ export interface ConnectorRegistration {
16
+ connector_id: string;
17
+ }
18
+ export interface ConnectorRegistrationStatus {
19
+ connector_id: string;
20
+ failure?: string;
21
+ status: 'failed' | 'registered' | 'registering';
22
+ }
23
+ export declare function listConnectors(token: string, organizationID: string, projectID: string): Promise<Connector[]>;
24
+ export declare function createConnector(token: string, organizationID: string, projectID: string, environmentID: string | undefined, name: string, url: string): Promise<ConnectorRegistration>;
25
+ export declare function waitForConnectorRegistration(token: string, connectorID: string, onStatus?: (status: ConnectorRegistrationStatus['status']) => void): Promise<ConnectorRegistrationStatus>;
@@ -0,0 +1,76 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { request } from '../api/client.js';
3
+ export async function listConnectors(token, organizationID, projectID) {
4
+ const query = new URLSearchParams({ organization_id: organizationID, project_id: projectID });
5
+ const connectors = await request(token, `/connectors?${query}`);
6
+ if (!Array.isArray(connectors) || connectors.some((connector) => !isConnector(connector, projectID))) {
7
+ throw new Error('Ardent API returned an invalid connectors response.');
8
+ }
9
+ return connectors;
10
+ }
11
+ export async function createConnector(token, organizationID, projectID, environmentID, name, url) {
12
+ const requestID = randomUUID();
13
+ const registration = await request(token, '/connectors', {
14
+ body: JSON.stringify({
15
+ ...(environmentID ? { environment_id: environmentID } : {}),
16
+ name,
17
+ organization_id: organizationID,
18
+ project_id: projectID,
19
+ request_id: requestID,
20
+ url,
21
+ }),
22
+ method: 'POST',
23
+ });
24
+ if (!isConnectorRegistration(registration))
25
+ throw new Error('Ardent API returned an invalid connector registration response.');
26
+ return registration;
27
+ }
28
+ export async function waitForConnectorRegistration(token, connectorID, onStatus) {
29
+ for (;;) {
30
+ const status = await request(token, `/connectors/${encodeURIComponent(connectorID)}`);
31
+ if (!isConnectorRegistrationStatus(status, connectorID)) {
32
+ throw new Error('Ardent API returned an invalid connector registration status.');
33
+ }
34
+ onStatus?.(status.status);
35
+ if (status.status === 'registered')
36
+ return status;
37
+ if (status.status === 'failed')
38
+ throw new Error(status.failure || 'Connector registration failed.');
39
+ await new Promise((resolve) => setTimeout(resolve, 2000));
40
+ }
41
+ }
42
+ function isConnector(value, projectID) {
43
+ if (typeof value !== 'object' || value === null)
44
+ return false;
45
+ const connector = value;
46
+ const source = connector.source;
47
+ return (typeof connector.id === 'string' &&
48
+ connector.id.length > 0 &&
49
+ connector.project_id === projectID &&
50
+ typeof connector.environment_id === 'string' &&
51
+ connector.environment_id.length > 0 &&
52
+ typeof connector.name === 'string' &&
53
+ typeof connector.status === 'string' &&
54
+ connector.status.length > 0 &&
55
+ typeof source === 'object' &&
56
+ source !== null &&
57
+ typeof source.host === 'string' &&
58
+ Number.isInteger(source.port) &&
59
+ typeof source.database === 'string' &&
60
+ typeof connector.created_at === 'string' &&
61
+ typeof connector.updated_at === 'string');
62
+ }
63
+ function isConnectorRegistration(value) {
64
+ if (typeof value !== 'object' || value === null)
65
+ return false;
66
+ const registration = value;
67
+ return typeof registration.connector_id === 'string' && registration.connector_id.length > 0;
68
+ }
69
+ function isConnectorRegistrationStatus(value, connectorID) {
70
+ if (typeof value !== 'object' || value === null)
71
+ return false;
72
+ const status = value;
73
+ return (status.connector_id === connectorID &&
74
+ (status.status === 'registering' || status.status === 'registered' || status.status === 'failed') &&
75
+ (status.failure === undefined || typeof status.failure === 'string'));
76
+ }
@@ -0,0 +1,29 @@
1
+ export type OrganizationRole = 'Admin' | 'Member' | 'Owner' | 'Viewer';
2
+ export interface Membership {
3
+ created_at: string;
4
+ email: string;
5
+ full_name: string | null;
6
+ organization_id: string;
7
+ role_display_name: string;
8
+ scopes: string[];
9
+ updated_at: string;
10
+ user_id: string;
11
+ }
12
+ export interface Invitation {
13
+ created_at: string;
14
+ created_by: string | null;
15
+ email: string;
16
+ expires_at: string;
17
+ id: string;
18
+ inviter_name: string | null;
19
+ organization_id: string;
20
+ organization_name: string;
21
+ role_display_name: string;
22
+ scopes: string[];
23
+ }
24
+ export declare function listMemberships(token: string, organizationID: string): Promise<Membership[]>;
25
+ export declare function listInvitations(token: string, organizationID: string): Promise<Invitation[]>;
26
+ export declare function listReadableInvitations(token: string, organizationID: string): Promise<Invitation[] | undefined>;
27
+ export declare function inviteMember(token: string, organizationID: string, email: string, role: OrganizationRole): Promise<Invitation>;
28
+ export declare function deleteMembership(token: string, organizationID: string, userID: string): Promise<void>;
29
+ export declare function deleteInvitation(token: string, organizationID: string, invitationID: string): Promise<void>;
@@ -0,0 +1,114 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { APIError, request } from '../api/client.js';
3
+ const sharedReadScopes = [
4
+ 'organizations.read',
5
+ 'organization_memberships.read',
6
+ 'projects.read',
7
+ 'environments.read',
8
+ 'connectors.read',
9
+ ];
10
+ const adminScopes = [
11
+ 'organizations.read',
12
+ 'organization_memberships.read',
13
+ 'organization_memberships.update',
14
+ 'organization_invitations.read',
15
+ 'organization_invitations.create',
16
+ 'organization_invitations.delete',
17
+ 'projects.read',
18
+ 'projects.create',
19
+ 'projects.update',
20
+ 'projects.delete',
21
+ 'environments.read',
22
+ 'environments.create',
23
+ 'connectors.read',
24
+ 'connectors.create',
25
+ 'environments.update',
26
+ 'branches.connect',
27
+ 'billing.read',
28
+ 'api_keys.read',
29
+ 'api_keys.create',
30
+ 'api_keys.revoke',
31
+ ];
32
+ const standardRoleScopes = {
33
+ Owner: adminScopes.flatMap((scope) => (scope === 'billing.read' ? [scope, 'billing.manage'] : [scope])),
34
+ Admin: adminScopes,
35
+ Member: [...sharedReadScopes, 'projects.create', 'connectors.create', 'branches.connect'],
36
+ Viewer: sharedReadScopes,
37
+ };
38
+ export async function listMemberships(token, organizationID) {
39
+ const memberships = await request(token, `/organizations/${encodeURIComponent(organizationID)}/memberships`);
40
+ if (!Array.isArray(memberships) || memberships.some((membership) => !isMembership(membership, organizationID))) {
41
+ throw new Error('Ardent API returned an invalid memberships response.');
42
+ }
43
+ return memberships;
44
+ }
45
+ export async function listInvitations(token, organizationID) {
46
+ const invitations = await request(token, `/organizations/${encodeURIComponent(organizationID)}/invitations`);
47
+ if (!Array.isArray(invitations) || invitations.some((invitation) => !isInvitation(invitation, organizationID))) {
48
+ throw new Error('Ardent API returned an invalid invitations response.');
49
+ }
50
+ return invitations;
51
+ }
52
+ export async function listReadableInvitations(token, organizationID) {
53
+ try {
54
+ return await listInvitations(token, organizationID);
55
+ }
56
+ catch (error) {
57
+ if (error instanceof APIError && error.status === 403 && error.code === 'permission_denied')
58
+ return undefined;
59
+ throw error;
60
+ }
61
+ }
62
+ export async function inviteMember(token, organizationID, email, role) {
63
+ const invitation = await request(token, `/organizations/${encodeURIComponent(organizationID)}/invitations`, {
64
+ body: JSON.stringify({ email, role_display_name: role, scopes: standardRoleScopes[role] }),
65
+ headers: { 'Idempotency-Key': randomUUID() },
66
+ method: 'POST',
67
+ });
68
+ if (!isInvitation(invitation, organizationID))
69
+ throw new Error('Ardent API returned an invalid invitation response.');
70
+ return invitation;
71
+ }
72
+ export async function deleteMembership(token, organizationID, userID) {
73
+ const response = await request(token, `/organizations/${encodeURIComponent(organizationID)}/memberships/${encodeURIComponent(userID)}`, { method: 'DELETE' });
74
+ if (response !== undefined)
75
+ throw new Error('Ardent API did not confirm member deletion. Run ardent member list before retrying.');
76
+ }
77
+ export async function deleteInvitation(token, organizationID, invitationID) {
78
+ const response = await request(token, `/organizations/${encodeURIComponent(organizationID)}/invitations/${encodeURIComponent(invitationID)}`, { method: 'DELETE' });
79
+ if (response !== undefined)
80
+ throw new Error('Ardent API did not confirm invitation cancellation. Run ardent member list before retrying.');
81
+ }
82
+ function isMembership(value, organizationID) {
83
+ if (typeof value !== 'object' || value === null)
84
+ return false;
85
+ const membership = value;
86
+ return (membership.organization_id === organizationID &&
87
+ typeof membership.user_id === 'string' &&
88
+ membership.user_id.length > 0 &&
89
+ typeof membership.email === 'string' &&
90
+ (membership.full_name === null || typeof membership.full_name === 'string') &&
91
+ typeof membership.role_display_name === 'string' &&
92
+ stringArray(membership.scopes) &&
93
+ typeof membership.created_at === 'string' &&
94
+ typeof membership.updated_at === 'string');
95
+ }
96
+ function isInvitation(value, organizationID) {
97
+ if (typeof value !== 'object' || value === null)
98
+ return false;
99
+ const invitation = value;
100
+ return (invitation.organization_id === organizationID &&
101
+ typeof invitation.id === 'string' &&
102
+ invitation.id.length > 0 &&
103
+ typeof invitation.organization_name === 'string' &&
104
+ typeof invitation.email === 'string' &&
105
+ typeof invitation.role_display_name === 'string' &&
106
+ stringArray(invitation.scopes) &&
107
+ typeof invitation.expires_at === 'string' &&
108
+ (invitation.created_by === null || typeof invitation.created_by === 'string') &&
109
+ (invitation.inviter_name === null || typeof invitation.inviter_name === 'string') &&
110
+ typeof invitation.created_at === 'string');
111
+ }
112
+ function stringArray(value) {
113
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
114
+ }
@@ -95,6 +95,208 @@
95
95
  "status.js"
96
96
  ]
97
97
  },
98
+ "connector:create": {
99
+ "aliases": [],
100
+ "args": {
101
+ "type": {
102
+ "description": "Connector type",
103
+ "name": "type",
104
+ "required": true
105
+ },
106
+ "url": {
107
+ "description": "PostgreSQL connection URL",
108
+ "name": "url",
109
+ "required": true
110
+ }
111
+ },
112
+ "description": "Register a PostgreSQL connector in the selected project",
113
+ "flags": {
114
+ "json": {
115
+ "description": "Format output as json.",
116
+ "helpGroup": "GLOBAL",
117
+ "name": "json",
118
+ "allowNo": false,
119
+ "type": "boolean"
120
+ },
121
+ "environment": {
122
+ "description": "Environment ID (defaults to Ardent Cloud)",
123
+ "name": "environment",
124
+ "hasDynamicHelp": false,
125
+ "multiple": false,
126
+ "type": "option"
127
+ },
128
+ "name": {
129
+ "char": "n",
130
+ "description": "Connector name",
131
+ "name": "name",
132
+ "hasDynamicHelp": false,
133
+ "multiple": false,
134
+ "type": "option"
135
+ }
136
+ },
137
+ "hasDynamicHelp": false,
138
+ "hiddenAliases": [],
139
+ "id": "connector:create",
140
+ "pluginAlias": "ardent-cli-beta",
141
+ "pluginName": "ardent-cli-beta",
142
+ "pluginType": "core",
143
+ "strict": true,
144
+ "enableJsonFlag": true,
145
+ "helpGroup": "Connectors",
146
+ "isESM": true,
147
+ "relativePath": [
148
+ "dist",
149
+ "commands",
150
+ "connector",
151
+ "create.js"
152
+ ]
153
+ },
154
+ "connector:list": {
155
+ "aliases": [],
156
+ "args": {},
157
+ "description": "List connectors in the selected project",
158
+ "flags": {
159
+ "json": {
160
+ "description": "Format output as json.",
161
+ "helpGroup": "GLOBAL",
162
+ "name": "json",
163
+ "allowNo": false,
164
+ "type": "boolean"
165
+ }
166
+ },
167
+ "hasDynamicHelp": false,
168
+ "hiddenAliases": [],
169
+ "id": "connector:list",
170
+ "pluginAlias": "ardent-cli-beta",
171
+ "pluginName": "ardent-cli-beta",
172
+ "pluginType": "core",
173
+ "strict": true,
174
+ "enableJsonFlag": true,
175
+ "helpGroup": "Connectors",
176
+ "isESM": true,
177
+ "relativePath": [
178
+ "dist",
179
+ "commands",
180
+ "connector",
181
+ "list.js"
182
+ ]
183
+ },
184
+ "member:delete": {
185
+ "aliases": [],
186
+ "args": {
187
+ "email": {
188
+ "description": "Member or pending invitation email",
189
+ "name": "email",
190
+ "required": true
191
+ }
192
+ },
193
+ "description": "Delete a member or pending invitation by email",
194
+ "flags": {
195
+ "json": {
196
+ "description": "Format output as json.",
197
+ "helpGroup": "GLOBAL",
198
+ "name": "json",
199
+ "allowNo": false,
200
+ "type": "boolean"
201
+ }
202
+ },
203
+ "hasDynamicHelp": false,
204
+ "hiddenAliases": [],
205
+ "id": "member:delete",
206
+ "pluginAlias": "ardent-cli-beta",
207
+ "pluginName": "ardent-cli-beta",
208
+ "pluginType": "core",
209
+ "strict": true,
210
+ "enableJsonFlag": true,
211
+ "helpGroup": "Members",
212
+ "isESM": true,
213
+ "relativePath": [
214
+ "dist",
215
+ "commands",
216
+ "member",
217
+ "delete.js"
218
+ ]
219
+ },
220
+ "member:invite": {
221
+ "aliases": [],
222
+ "args": {
223
+ "email": {
224
+ "description": "Email address to invite",
225
+ "name": "email",
226
+ "required": true
227
+ }
228
+ },
229
+ "description": "Invite a member to the organization",
230
+ "flags": {
231
+ "json": {
232
+ "description": "Format output as json.",
233
+ "helpGroup": "GLOBAL",
234
+ "name": "json",
235
+ "allowNo": false,
236
+ "type": "boolean"
237
+ },
238
+ "role": {
239
+ "description": "Role to grant",
240
+ "name": "role",
241
+ "default": "member",
242
+ "hasDynamicHelp": false,
243
+ "multiple": false,
244
+ "options": [
245
+ "admin",
246
+ "member",
247
+ "owner",
248
+ "viewer"
249
+ ],
250
+ "type": "option"
251
+ }
252
+ },
253
+ "hasDynamicHelp": false,
254
+ "hiddenAliases": [],
255
+ "id": "member:invite",
256
+ "pluginAlias": "ardent-cli-beta",
257
+ "pluginName": "ardent-cli-beta",
258
+ "pluginType": "core",
259
+ "strict": true,
260
+ "enableJsonFlag": true,
261
+ "helpGroup": "Members",
262
+ "isESM": true,
263
+ "relativePath": [
264
+ "dist",
265
+ "commands",
266
+ "member",
267
+ "invite.js"
268
+ ]
269
+ },
270
+ "member:list": {
271
+ "aliases": [],
272
+ "args": {},
273
+ "description": "List organization members and pending invitations",
274
+ "flags": {
275
+ "json": {
276
+ "description": "Format output as json.",
277
+ "helpGroup": "GLOBAL",
278
+ "name": "json",
279
+ "allowNo": false,
280
+ "type": "boolean"
281
+ }
282
+ },
283
+ "hasDynamicHelp": false,
284
+ "hiddenAliases": [],
285
+ "id": "member:list",
286
+ "pluginAlias": "ardent-cli-beta",
287
+ "pluginName": "ardent-cli-beta",
288
+ "pluginType": "core",
289
+ "strict": true,
290
+ "enableJsonFlag": true,
291
+ "helpGroup": "Members",
292
+ "isESM": true,
293
+ "relativePath": [
294
+ "dist",
295
+ "commands",
296
+ "member",
297
+ "list.js"
298
+ ]
299
+ },
98
300
  "project:create": {
99
301
  "aliases": [],
100
302
  "args": {
@@ -234,5 +436,5 @@
234
436
  ]
235
437
  }
236
438
  },
237
- "version": "0.0.9"
439
+ "version": "0.0.11"
238
440
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli-beta",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "The Ardent command-line interface",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",