diff --git a/src/cli.js b/src/cli.js index 8d4215d..b115ea0 100755 --- a/src/cli.js +++ b/src/cli.js @@ -15,11 +15,13 @@ Commands: list-apps [--display-name ] list-app-permissions --app-id list-app-grants --app-id + table [--pretty] Options: --display-name Filter apps by exact display name --app-id Application (client) ID --query Filter output JSON using JMESPath + --pretty Use normalized column widths for Markdown table output --output Output format: json|table|prettytable (default: json) -h, --help Show this help message`; } @@ -30,6 +32,31 @@ function outputFiltered(object, query) { : object; } +async function readJsonFromStdin() { + const input = await new Promise((resolve, reject) => { + let data = ""; + process.stdin.setEncoding("utf8"); + process.stdin.on("data", (chunk) => { + data += chunk; + }); + process.stdin.on("end", () => { + resolve(data); + }); + process.stdin.on("error", (err) => { + reject(err); + }); + }); + if (!input.trim()) { + throw new Error("No JSON input provided on stdin"); + } + + try { + return JSON.parse(input); + } catch (err) { + throw new Error(`Invalid JSON input on stdin: ${err.message}`); + } +} + async function main() { const argv = process.argv.slice(2); const command = argv[0]; @@ -45,6 +72,7 @@ async function main() { "display-name": { type: "string" }, "app-id": { type: "string" }, query: { type: "string" }, + pretty: { type: "boolean" }, output: { type: "string" }, }, strict: true, @@ -60,37 +88,57 @@ async function main() { throw new Error("--output must be one of: json, table, prettytable"); } - const config = await loadPublicConfig(); - const { client } = await getGraphClient({ - tenantId: config.tenantId, - clientId: config.clientId, - }); - let result; switch (command) { + case "table": + result = await readJsonFromStdin(); + break; case "list-apps": - result = await listApps(client, { - displayName: values["display-name"], - }); + { + const config = await loadPublicConfig(); + const { client } = await getGraphClient({ + tenantId: config.tenantId, + clientId: config.clientId, + }); + result = await listApps(client, { + displayName: values["display-name"], + }); + } break; case "list-app-permissions": if (!values["app-id"]) { throw new Error("--app-id is required for list-app-permissions"); } - result = await listAppPermissions(client, values["app-id"]); + { + const config = await loadPublicConfig(); + const { client } = await getGraphClient({ + tenantId: config.tenantId, + clientId: config.clientId, + }); + result = await listAppPermissions(client, values["app-id"]); + } break; case "list-app-grants": if (!values["app-id"]) { throw new Error("--app-id is required for list-app-grants"); } - result = await listAppGrants(client, values["app-id"]); + { + const config = await loadPublicConfig(); + const { client } = await getGraphClient({ + tenantId: config.tenantId, + clientId: config.clientId, + }); + result = await listAppGrants(client, values["app-id"]); + } break; default: throw new Error(`Unknown command: ${command}`); } const filtered = outputFiltered(result, values.query); - if (outputFormat === "prettytable") { + if (command === "table") { + console.log(toMarkdownTable(filtered, Boolean(values.pretty))); + } else if (outputFormat === "prettytable") { console.log(toMarkdownTable(filtered, true)); } else if (outputFormat === "table") { console.log(toMarkdownTable(filtered)); diff --git a/src/graph/auth.js b/src/graph/auth.js index d773bb0..e964124 100644 --- a/src/graph/auth.js +++ b/src/graph/auth.js @@ -8,7 +8,7 @@ import { Client } from "@microsoft/microsoft-graph-client"; * @param { Object } options - Options for authentication * @param { string } options.tenantId - The Azure AD tenant ID * @param { string } options.clientId - The Azure AD client ID - * @returns { Object } An object containing the Graph API token and client + * @returns { Promise<{ graphApiToken: Object, client: Object }> } An object containing the Graph API token and client */ export async function getGraphClient({ tenantId, clientId }) { const graphApiToken = await loginInteractive({