feat(idpcli): Add idp CLI (IdpCli) with commands, file-based credential storage, typed request APIs; bump deps and update config
This commit is contained in:
@@ -0,0 +1,477 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export interface IIdpCliConfig {
|
||||
idpBaseUrl: string;
|
||||
configDir?: string;
|
||||
}
|
||||
|
||||
export interface IStoredCredentials {
|
||||
refreshToken?: string;
|
||||
jwt?: string;
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* IdpCli - A Node.js CLI client for idp.global
|
||||
* Uses file-based storage instead of browser webstore
|
||||
*/
|
||||
export class IdpCli {
|
||||
public config: IIdpCliConfig;
|
||||
public configDir: string;
|
||||
public credentialsPath: string;
|
||||
|
||||
public typedsocket: plugins.typedsocket.TypedSocket;
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
private typedsocketDeferred = plugins.smartpromise.defer<plugins.typedsocket.TypedSocket>();
|
||||
|
||||
constructor(configArg: IIdpCliConfig) {
|
||||
this.config = configArg;
|
||||
this.configDir = configArg.configDir || plugins.path.join(plugins.os.homedir(), '.idp-global');
|
||||
this.credentialsPath = plugins.path.join(this.configDir, 'credentials.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure config directory exists
|
||||
*/
|
||||
private ensureConfigDir(): void {
|
||||
if (!plugins.fs.existsSync(this.configDir)) {
|
||||
plugins.fs.mkdirSync(this.configDir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store credentials to file
|
||||
*/
|
||||
public storeCredentials(credentials: IStoredCredentials): void {
|
||||
this.ensureConfigDir();
|
||||
plugins.fs.writeFileSync(this.credentialsPath, JSON.stringify(credentials, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load stored credentials
|
||||
*/
|
||||
public loadCredentials(): IStoredCredentials | null {
|
||||
try {
|
||||
if (!plugins.fs.existsSync(this.credentialsPath)) {
|
||||
return null;
|
||||
}
|
||||
const content = plugins.fs.readFileSync(this.credentialsPath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete stored credentials (logout)
|
||||
*/
|
||||
public deleteCredentials(): void {
|
||||
try {
|
||||
if (plugins.fs.existsSync(this.credentialsPath)) {
|
||||
plugins.fs.unlinkSync(this.credentialsPath);
|
||||
}
|
||||
} catch {
|
||||
// ignore if file doesn't exist
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to IDP server via WebSocket
|
||||
*/
|
||||
public async connect(): Promise<void> {
|
||||
if (this.typedsocketDeferred.status === 'fulfilled') {
|
||||
return;
|
||||
}
|
||||
|
||||
let baseUrl = this.config.idpBaseUrl;
|
||||
if (baseUrl.endsWith('/')) {
|
||||
baseUrl = baseUrl.slice(0, -1);
|
||||
}
|
||||
if (!baseUrl.endsWith('/typedrequest')) {
|
||||
baseUrl = `${baseUrl}/typedrequest`;
|
||||
}
|
||||
|
||||
console.log(`Connecting to ${baseUrl}...`);
|
||||
this.typedsocket = await plugins.typedsocket.TypedSocket.createClient(
|
||||
this.typedrouter,
|
||||
baseUrl
|
||||
);
|
||||
this.typedsocketDeferred.resolve(this.typedsocket);
|
||||
console.log('Connected!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from IDP server
|
||||
*/
|
||||
public async disconnect(): Promise<void> {
|
||||
if (this.typedsocket) {
|
||||
await this.typedsocket.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Authentication Commands
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Login with email and password
|
||||
*/
|
||||
public async loginWithPassword(email: string, password: string): Promise<boolean> {
|
||||
await this.connect();
|
||||
|
||||
const loginRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_LoginWithEmailOrUsernameAndPassword>(
|
||||
'loginWithEmailOrUsernameAndPassword'
|
||||
);
|
||||
|
||||
const response = await loginRequest.fire({
|
||||
username: email,
|
||||
password: password,
|
||||
});
|
||||
|
||||
if (response.refreshToken) {
|
||||
this.storeCredentials({
|
||||
refreshToken: response.refreshToken,
|
||||
});
|
||||
console.log('Login successful!');
|
||||
return true;
|
||||
} else if (response.twoFaNeeded) {
|
||||
console.log('Two-factor authentication required.');
|
||||
return false;
|
||||
} else {
|
||||
console.log('Login failed.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Login with API token
|
||||
*/
|
||||
public async loginWithApiToken(apiToken: string): Promise<boolean> {
|
||||
await this.connect();
|
||||
|
||||
const loginRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_LoginWithApiToken>(
|
||||
'loginWithApiToken'
|
||||
);
|
||||
|
||||
const response = await loginRequest.fire({
|
||||
apiToken,
|
||||
});
|
||||
|
||||
if (response.jwt) {
|
||||
this.storeCredentials({
|
||||
jwt: response.jwt,
|
||||
});
|
||||
console.log('Login successful!');
|
||||
return true;
|
||||
} else {
|
||||
console.log('Login failed.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh JWT from stored refresh token
|
||||
*/
|
||||
public async refreshJwt(): Promise<string | null> {
|
||||
const credentials = this.loadCredentials();
|
||||
if (!credentials?.refreshToken) {
|
||||
console.error('No refresh token stored. Please login first.');
|
||||
return null;
|
||||
}
|
||||
|
||||
await this.connect();
|
||||
|
||||
const refreshRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_RefreshJwt>(
|
||||
'refreshJwt'
|
||||
);
|
||||
|
||||
const response = await refreshRequest.fire({
|
||||
refreshToken: credentials.refreshToken,
|
||||
});
|
||||
|
||||
if (response.jwt) {
|
||||
this.storeCredentials({
|
||||
...credentials,
|
||||
jwt: response.jwt,
|
||||
});
|
||||
return response.jwt;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout - clear stored credentials
|
||||
*/
|
||||
public async logout(): Promise<void> {
|
||||
const credentials = this.loadCredentials();
|
||||
|
||||
if (credentials?.refreshToken) {
|
||||
try {
|
||||
await this.connect();
|
||||
const logoutRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.ILogoutRequest>(
|
||||
'logout'
|
||||
);
|
||||
await logoutRequest.fire({
|
||||
refreshToken: credentials.refreshToken,
|
||||
});
|
||||
} catch (e) {
|
||||
// Ignore errors during server-side logout
|
||||
}
|
||||
}
|
||||
|
||||
this.deleteCredentials();
|
||||
console.log('Logged out successfully.');
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// User Commands
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Get current user info
|
||||
*/
|
||||
public async whoami(): Promise<plugins.idpInterfaces.data.IUser | null> {
|
||||
const jwt = await this.ensureAuthenticated();
|
||||
if (!jwt) return null;
|
||||
|
||||
await this.connect();
|
||||
|
||||
const whoIsRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_WhoIs>(
|
||||
'whoIs'
|
||||
);
|
||||
|
||||
const response = await whoIsRequest.fire({ jwt });
|
||||
return response.user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user sessions
|
||||
*/
|
||||
public async getSessions(): Promise<plugins.idpInterfaces.request.IReq_GetUserSessions['response']['sessions'] | null> {
|
||||
const jwt = await this.ensureAuthenticated();
|
||||
if (!jwt) return null;
|
||||
|
||||
await this.connect();
|
||||
|
||||
const sessionsRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetUserSessions>(
|
||||
'getUserSessions'
|
||||
);
|
||||
|
||||
const response = await sessionsRequest.fire({ jwt });
|
||||
return response.sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a session
|
||||
*/
|
||||
public async revokeSession(sessionId: string): Promise<boolean> {
|
||||
const jwt = await this.ensureAuthenticated();
|
||||
if (!jwt) return false;
|
||||
|
||||
await this.connect();
|
||||
|
||||
const revokeRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_RevokeSession>(
|
||||
'revokeSession'
|
||||
);
|
||||
|
||||
const response = await revokeRequest.fire({ jwt, sessionId });
|
||||
return response.success;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Organization Commands
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Get organizations for current user
|
||||
*/
|
||||
public async getOrganizations(): Promise<{
|
||||
roles: plugins.idpInterfaces.data.IRole[];
|
||||
organizations: plugins.idpInterfaces.data.IOrganization[];
|
||||
} | null> {
|
||||
const jwt = await this.ensureAuthenticated();
|
||||
if (!jwt) return null;
|
||||
|
||||
const user = await this.whoami();
|
||||
if (!user) return null;
|
||||
|
||||
await this.connect();
|
||||
|
||||
const orgsRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetRolesAndOrganizationsForUserId>(
|
||||
'getRolesAndOrganizationsForUserId'
|
||||
);
|
||||
|
||||
const response = await orgsRequest.fire({
|
||||
jwt,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new organization
|
||||
*/
|
||||
public async createOrganization(
|
||||
name: string,
|
||||
slug: string,
|
||||
mode: 'checkAvailability' | 'manifest' = 'manifest'
|
||||
): Promise<plugins.idpInterfaces.request.IReq_CreateOrganization['response'] | null> {
|
||||
const jwt = await this.ensureAuthenticated();
|
||||
if (!jwt) return null;
|
||||
|
||||
const user = await this.whoami();
|
||||
if (!user) return null;
|
||||
|
||||
await this.connect();
|
||||
|
||||
const createRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_CreateOrganization>(
|
||||
'createOrganization'
|
||||
);
|
||||
|
||||
const response = await createRequest.fire({
|
||||
jwt,
|
||||
userId: user.id,
|
||||
organizationName: name,
|
||||
organizationSlug: slug,
|
||||
action: mode,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get organization members
|
||||
*/
|
||||
public async getOrgMembers(
|
||||
organizationId: string
|
||||
): Promise<plugins.idpInterfaces.request.IReq_GetOrgMembers['response']['members'] | null> {
|
||||
const jwt = await this.ensureAuthenticated();
|
||||
if (!jwt) return null;
|
||||
|
||||
await this.connect();
|
||||
|
||||
const membersRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetOrgMembers>(
|
||||
'getOrgMembers'
|
||||
);
|
||||
|
||||
const response = await membersRequest.fire({
|
||||
jwt,
|
||||
organizationId,
|
||||
});
|
||||
|
||||
return response.members;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invite a user to organization
|
||||
*/
|
||||
public async inviteMember(
|
||||
organizationId: string,
|
||||
email: string,
|
||||
roles: string[] = ['member']
|
||||
): Promise<plugins.idpInterfaces.request.IReq_CreateInvitation['response'] | null> {
|
||||
const jwt = await this.ensureAuthenticated();
|
||||
if (!jwt) return null;
|
||||
|
||||
await this.connect();
|
||||
|
||||
const inviteRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_CreateInvitation>(
|
||||
'createInvitation'
|
||||
);
|
||||
|
||||
const response = await inviteRequest.fire({
|
||||
jwt,
|
||||
organizationId,
|
||||
email,
|
||||
roles,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Admin Commands
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Check if current user is global admin
|
||||
*/
|
||||
public async checkGlobalAdmin(): Promise<boolean> {
|
||||
const jwt = await this.ensureAuthenticated();
|
||||
if (!jwt) return false;
|
||||
|
||||
await this.connect();
|
||||
|
||||
const adminRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_CheckGlobalAdmin>(
|
||||
'checkGlobalAdmin'
|
||||
);
|
||||
|
||||
const response = await adminRequest.fire({ jwt });
|
||||
return response.isGlobalAdmin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get global app statistics (admin only)
|
||||
*/
|
||||
public async getGlobalAppStats(): Promise<plugins.idpInterfaces.request.IReq_GetGlobalAppStats['response']['apps'] | null> {
|
||||
const jwt = await this.ensureAuthenticated();
|
||||
if (!jwt) return null;
|
||||
|
||||
await this.connect();
|
||||
|
||||
const statsRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetGlobalAppStats>(
|
||||
'getGlobalAppStats'
|
||||
);
|
||||
|
||||
const response = await statsRequest.fire({ jwt });
|
||||
return response.apps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspend a user (admin only)
|
||||
*/
|
||||
public async suspendUser(userId: string): Promise<boolean> {
|
||||
const jwt = await this.ensureAuthenticated();
|
||||
if (!jwt) return false;
|
||||
|
||||
await this.connect();
|
||||
|
||||
const suspendRequest = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_SuspendUser>(
|
||||
'suspendUser'
|
||||
);
|
||||
|
||||
await suspendRequest.fire({ jwt, userId });
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Helpers
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Ensure user is authenticated, refresh JWT if needed
|
||||
*/
|
||||
private async ensureAuthenticated(): Promise<string | null> {
|
||||
let credentials = this.loadCredentials();
|
||||
|
||||
if (!credentials) {
|
||||
console.error('Not logged in. Please run: idp login');
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we have a JWT, return it
|
||||
if (credentials.jwt) {
|
||||
return credentials.jwt;
|
||||
}
|
||||
|
||||
// Otherwise, try to get a new JWT from refresh token
|
||||
if (credentials.refreshToken) {
|
||||
const jwt = await this.refreshJwt();
|
||||
return jwt;
|
||||
}
|
||||
|
||||
console.error('No valid credentials. Please run: idp login');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { IdpCli } from './classes.idpcli.js';
|
||||
|
||||
export { IdpCli } from './classes.idpcli.js';
|
||||
|
||||
const DEFAULT_IDP_URL = 'https://idp.global';
|
||||
|
||||
/**
|
||||
* Run the CLI
|
||||
*/
|
||||
export const runCli = async () => {
|
||||
const smartcliInstance = new plugins.smartcli.Smartcli();
|
||||
smartcliInstance.addVersion('1.0.0');
|
||||
|
||||
const getIdpClient = () => {
|
||||
const idpUrl = process.env.IDP_URL || DEFAULT_IDP_URL;
|
||||
return new IdpCli({ idpBaseUrl: idpUrl });
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Help
|
||||
// ============================================
|
||||
smartcliInstance.addHelp({
|
||||
helpText: `
|
||||
idp - CLI for idp.global identity provider
|
||||
|
||||
USAGE:
|
||||
idp <command> [options]
|
||||
|
||||
COMMANDS:
|
||||
login Login with email and password
|
||||
login-token Login with API token
|
||||
logout Logout and clear credentials
|
||||
whoami Show current user information
|
||||
|
||||
orgs List organizations
|
||||
orgs-create Create a new organization
|
||||
|
||||
members List organization members
|
||||
invite Invite a user to organization
|
||||
|
||||
sessions List active sessions
|
||||
revoke Revoke a session
|
||||
|
||||
admin-check Check if current user is global admin
|
||||
admin-apps List global apps (admin only)
|
||||
admin-suspend Suspend a user (admin only)
|
||||
|
||||
help Show this help message
|
||||
|
||||
ENVIRONMENT:
|
||||
IDP_URL Override IDP server URL (default: https://idp.global)
|
||||
|
||||
EXAMPLES:
|
||||
idp login
|
||||
idp whoami
|
||||
idp orgs
|
||||
idp members --org <org-id>
|
||||
idp invite --org <org-id> --email user@example.com
|
||||
`,
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Login Commands
|
||||
// ============================================
|
||||
smartcliInstance.addCommand('login').subscribe(async (argv) => {
|
||||
const client = getIdpClient();
|
||||
const interact = new plugins.smartinteract.SmartInteract();
|
||||
|
||||
const emailAnswer = await interact.askQuestion({
|
||||
type: 'input',
|
||||
name: 'email',
|
||||
message: 'Email:',
|
||||
default: '',
|
||||
});
|
||||
|
||||
const passwordAnswer = await interact.askQuestion({
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
message: 'Password:',
|
||||
default: '',
|
||||
});
|
||||
|
||||
await client.loginWithPassword(emailAnswer.value as string, passwordAnswer.value as string);
|
||||
await client.disconnect();
|
||||
});
|
||||
|
||||
smartcliInstance.addCommand('login-token').subscribe(async (argv) => {
|
||||
const client = getIdpClient();
|
||||
const interact = new plugins.smartinteract.SmartInteract();
|
||||
|
||||
const tokenAnswer = await interact.askQuestion({
|
||||
type: 'password',
|
||||
name: 'token',
|
||||
message: 'API Token:',
|
||||
default: '',
|
||||
});
|
||||
|
||||
await client.loginWithApiToken(tokenAnswer.value as string);
|
||||
await client.disconnect();
|
||||
});
|
||||
|
||||
smartcliInstance.addCommand('logout').subscribe(async (argv) => {
|
||||
const client = getIdpClient();
|
||||
await client.logout();
|
||||
await client.disconnect();
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// User Commands
|
||||
// ============================================
|
||||
smartcliInstance.addCommand('whoami').subscribe(async (argv) => {
|
||||
const client = getIdpClient();
|
||||
const user = await client.whoami();
|
||||
|
||||
if (user) {
|
||||
console.log('\nUser Information:');
|
||||
console.log(` ID: ${user.id}`);
|
||||
console.log(` Name: ${user.data?.name || 'N/A'}`);
|
||||
console.log(` Username: ${user.data?.username || 'N/A'}`);
|
||||
console.log(` Email: ${user.data?.email || 'N/A'}`);
|
||||
console.log(` Status: ${user.data?.status || 'N/A'}`);
|
||||
console.log(` Global Admin: ${user.data?.isGlobalAdmin ? 'Yes' : 'No'}`);
|
||||
}
|
||||
|
||||
await client.disconnect();
|
||||
});
|
||||
|
||||
smartcliInstance.addCommand('sessions').subscribe(async (argv) => {
|
||||
const client = getIdpClient();
|
||||
const sessions = await client.getSessions();
|
||||
|
||||
if (sessions) {
|
||||
console.log('\nActive Sessions:');
|
||||
for (const session of sessions) {
|
||||
console.log(` - ${session.id}`);
|
||||
console.log(` Device: ${session.deviceName || 'Unknown'}`);
|
||||
console.log(` Browser: ${session.browser || 'Unknown'}`);
|
||||
console.log(` OS: ${session.os || 'Unknown'}`);
|
||||
console.log(` Last Active: ${new Date(session.lastActive).toLocaleString()}`);
|
||||
console.log(` Current: ${session.isCurrent ? 'Yes' : 'No'}`);
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
await client.disconnect();
|
||||
});
|
||||
|
||||
smartcliInstance.addCommand('revoke').subscribe(async (argv) => {
|
||||
const client = getIdpClient();
|
||||
const sessionId = argv.session || argv.s || argv._[1];
|
||||
|
||||
if (!sessionId) {
|
||||
console.error('Please provide a session ID: idp revoke --session <session-id>');
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await client.revokeSession(sessionId);
|
||||
if (success) {
|
||||
console.log('Session revoked successfully.');
|
||||
} else {
|
||||
console.error('Failed to revoke session.');
|
||||
}
|
||||
|
||||
await client.disconnect();
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Organization Commands
|
||||
// ============================================
|
||||
smartcliInstance.addCommand('orgs').subscribe(async (argv) => {
|
||||
const client = getIdpClient();
|
||||
const result = await client.getOrganizations();
|
||||
|
||||
if (result) {
|
||||
console.log('\nOrganizations:');
|
||||
for (const org of result.organizations) {
|
||||
const role = result.roles.find((r) => r.data?.organizationId === org.id);
|
||||
console.log(` - ${org.data?.name} (${org.id})`);
|
||||
console.log(` Slug: ${org.data?.slug}`);
|
||||
console.log(` Roles: ${role?.data?.roles?.join(', ') || 'Unknown'}`);
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
await client.disconnect();
|
||||
});
|
||||
|
||||
smartcliInstance.addCommand('orgs-create').subscribe(async (argv) => {
|
||||
const client = getIdpClient();
|
||||
const interact = new plugins.smartinteract.SmartInteract();
|
||||
|
||||
const nameAnswer = await interact.askQuestion({
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: 'Organization Name:',
|
||||
default: '',
|
||||
});
|
||||
|
||||
const slugAnswer = await interact.askQuestion({
|
||||
type: 'input',
|
||||
name: 'slug',
|
||||
message: 'Organization Slug:',
|
||||
default: '',
|
||||
});
|
||||
|
||||
// First check availability
|
||||
const checkResult = await client.createOrganization(
|
||||
nameAnswer.value as string,
|
||||
slugAnswer.value as string,
|
||||
'checkAvailability'
|
||||
);
|
||||
|
||||
if (!checkResult?.nameAvailable) {
|
||||
console.error('Organization name or slug is not available.');
|
||||
await client.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
// Then create
|
||||
const result = await client.createOrganization(
|
||||
nameAnswer.value as string,
|
||||
slugAnswer.value as string,
|
||||
'manifest'
|
||||
);
|
||||
|
||||
if (result?.resultingOrganization) {
|
||||
console.log('\nOrganization created successfully!');
|
||||
console.log(` ID: ${result.resultingOrganization.id}`);
|
||||
console.log(` Name: ${result.resultingOrganization.data?.name}`);
|
||||
}
|
||||
|
||||
await client.disconnect();
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Member Commands
|
||||
// ============================================
|
||||
smartcliInstance.addCommand('members').subscribe(async (argv) => {
|
||||
const client = getIdpClient();
|
||||
const orgId = argv.org || argv.o || argv._[1];
|
||||
|
||||
if (!orgId) {
|
||||
console.error('Please provide an organization ID: idp members --org <org-id>');
|
||||
return;
|
||||
}
|
||||
|
||||
const members = await client.getOrgMembers(orgId);
|
||||
|
||||
if (members) {
|
||||
console.log('\nOrganization Members:');
|
||||
for (const member of members) {
|
||||
console.log(` - ${member.user.data?.name || 'Unknown'}`);
|
||||
console.log(` Email: ${member.user.data?.email || 'N/A'}`);
|
||||
console.log(` Roles: ${member.role.data?.roles?.join(', ') || 'Unknown'}`);
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
await client.disconnect();
|
||||
});
|
||||
|
||||
smartcliInstance.addCommand('invite').subscribe(async (argv) => {
|
||||
const client = getIdpClient();
|
||||
const orgId = argv.org || argv.o;
|
||||
const email = argv.email || argv.e || argv._[1];
|
||||
|
||||
if (!orgId || !email) {
|
||||
console.error('Please provide organization ID and email:');
|
||||
console.error(' idp invite --org <org-id> --email user@example.com');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await client.inviteMember(orgId, email);
|
||||
|
||||
if (result?.success) {
|
||||
console.log(`Invitation sent to ${email}`);
|
||||
} else {
|
||||
console.error(`Failed to send invitation: ${result?.message || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
await client.disconnect();
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Admin Commands
|
||||
// ============================================
|
||||
smartcliInstance.addCommand('admin-check').subscribe(async (argv) => {
|
||||
const client = getIdpClient();
|
||||
const isAdmin = await client.checkGlobalAdmin();
|
||||
|
||||
if (isAdmin) {
|
||||
console.log('You are a global admin.');
|
||||
} else {
|
||||
console.log('You are not a global admin.');
|
||||
}
|
||||
|
||||
await client.disconnect();
|
||||
});
|
||||
|
||||
smartcliInstance.addCommand('admin-apps').subscribe(async (argv) => {
|
||||
const client = getIdpClient();
|
||||
const apps = await client.getGlobalAppStats();
|
||||
|
||||
if (apps) {
|
||||
console.log('\nGlobal Apps:');
|
||||
for (const appInfo of apps) {
|
||||
console.log(` - ${appInfo.app.data?.name}`);
|
||||
console.log(` ID: ${appInfo.app.id}`);
|
||||
console.log(` Connections: ${appInfo.connectionCount}`);
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
await client.disconnect();
|
||||
});
|
||||
|
||||
smartcliInstance.addCommand('admin-suspend').subscribe(async (argv) => {
|
||||
const client = getIdpClient();
|
||||
const userId = argv.user || argv.u || argv._[1];
|
||||
|
||||
if (!userId) {
|
||||
console.error('Please provide a user ID: idp admin-suspend --user <user-id>');
|
||||
return;
|
||||
}
|
||||
|
||||
const interact = new plugins.smartinteract.SmartInteract();
|
||||
const confirmAnswer = await interact.askQuestion({
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: `Are you sure you want to suspend user ${userId}?`,
|
||||
default: false,
|
||||
});
|
||||
|
||||
if (confirmAnswer.value) {
|
||||
const success = await client.suspendUser(userId);
|
||||
if (success) {
|
||||
console.log('User suspended successfully.');
|
||||
} else {
|
||||
console.error('Failed to suspend user.');
|
||||
}
|
||||
} else {
|
||||
console.log('Operation cancelled.');
|
||||
}
|
||||
|
||||
await client.disconnect();
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Default/Standard command
|
||||
// ============================================
|
||||
smartcliInstance.standardCommand().subscribe(async (argv) => {
|
||||
// If no command specified, show help
|
||||
smartcliInstance.triggerCommand('help', argv);
|
||||
});
|
||||
|
||||
// Start parsing
|
||||
smartcliInstance.startParse();
|
||||
};
|
||||
|
||||
// Auto-run if this is the main module
|
||||
runCli().catch(console.error);
|
||||
@@ -0,0 +1,25 @@
|
||||
// node built-ins
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
|
||||
export { fs, path, os };
|
||||
|
||||
// @push.rocks scope
|
||||
import * as smartcli from '@push.rocks/smartcli';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartrx from '@push.rocks/smartrx';
|
||||
import * as smartinteract from '@push.rocks/smartinteract';
|
||||
|
||||
export { smartcli, smartpromise, smartrx, smartinteract };
|
||||
|
||||
// @api.global scope
|
||||
import * as typedrequest from '@api.global/typedrequest';
|
||||
import * as typedsocket from '@api.global/typedsocket';
|
||||
|
||||
export { typedrequest, typedsocket };
|
||||
|
||||
// local
|
||||
import * as idpInterfaces from '../dist_ts_interfaces/index.js';
|
||||
|
||||
export { idpInterfaces };
|
||||
Reference in New Issue
Block a user