@faable/faable 1.30.0 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,6 +12,20 @@ const firstPage = async (res) => {
12
12
  const items = (await res).results;
13
13
  return items;
14
14
  };
15
+ // Walk the cursor to exhaustion. `list()` needs this instead of `firstPage`:
16
+ // a user who sees many apps (admins see all of them) gets a multi-page
17
+ // listing, and matching by repository against a truncated first page made
18
+ // every repo-resolved command answer "No app linked to this repository".
19
+ const allPages = async (fetch_page) => {
20
+ const items = [];
21
+ let next;
22
+ do {
23
+ const page = await fetch_page(next);
24
+ items.push(...page.results);
25
+ next = page.next ?? undefined;
26
+ } while (next);
27
+ return items;
28
+ };
15
29
  const data = async (res) => {
16
30
  const items = (await res).data;
17
31
  return items;
@@ -84,7 +98,9 @@ class FaableApi {
84
98
  return new FaableApi(config);
85
99
  }
86
100
  async list() {
87
- return firstPage(data(this.client.get(`/app`)));
101
+ return allPages((next) => data(this.client.get(`/app`, {
102
+ params: { pageSize: 200, ...(next ? { next } : {}) },
103
+ })));
88
104
  }
89
105
  async getBySlug(slug) {
90
106
  return data(this.client.get(`/app/slug/${slug}`));
@@ -0,0 +1,67 @@
1
+ import { FaableAuthApi } from '@faable/auth-sdk';
2
+ import { CredentialsStore } from '../lib/CredentialsStore.js';
3
+ import { log } from '../log.js';
4
+ import { loadLiveCredentials } from './session.js';
5
+
6
+ // Default tenant host. `faable auth` is customer-facing: a customer targets
7
+ // their own tenant with --auth-url https://<account>.auth.faable.link (or
8
+ // FAABLE_AUTH_URL); the default points at the Faable tenant.
9
+ const DEFAULT_AUTH_URL = 'https://faable.auth.faable.link';
10
+ // Build a management-API client for the Auth server reusing the CLI session
11
+ // (FAABLE_TOKEN → `faable login` credentials, auto-refreshed). The bearer is
12
+ // sent as-is and the SERVER decides whether it may manage the target tenant
13
+ // (today MGMT_AUTH_ENFORCE is authz dry-run; when enforced, tokens without the
14
+ // management audience/scopes for the tenant will get 403 — see
15
+ // auth/docs/management-api-m2m-token.md for the M2M path).
16
+ const requireAuthAdmin = async (opts = {}) => {
17
+ let token = process.env.FAABLE_TOKEN;
18
+ if (!token) {
19
+ const store = new CredentialsStore();
20
+ const config = await loadLiveCredentials(store);
21
+ if (config?.apikey && !config.token) {
22
+ // Deploy API keys are not Auth management credentials.
23
+ log.error("❌ You are logged in with an API key, but `faable auth` needs a browser session. Run 'faable login' (without --apikey) first.");
24
+ process.exit(1);
25
+ }
26
+ token = config?.token;
27
+ }
28
+ if (!token) {
29
+ log.error("❌ Not logged in. Run 'faable login' first.");
30
+ process.exit(1);
31
+ }
32
+ const domain = opts.authUrl || process.env.FAABLE_AUTH_URL || DEFAULT_AUTH_URL;
33
+ const account = opts.account || process.env.FAABLE_AUTH_ACCOUNT;
34
+ return new FaableAuthApi({
35
+ domain,
36
+ ...(account ? { headers: { account_id: account } } : {}),
37
+ // Static bearer: passing no `auth` keeps sdk-base from attaching any token
38
+ // strategy, so this header is used verbatim on every request.
39
+ fetcher: { headers: { Authorization: `Bearer ${token}` } }
40
+ });
41
+ };
42
+ // Translate raw management-API failures into actionable CLI errors. Everything
43
+ // else is rethrown untouched (FaableApiError messages already carry
44
+ // status+url) and lands in the global yargs .fail() handler.
45
+ const hintAuthError = (e) => {
46
+ const status = e?.response?.status;
47
+ if (status === 401) {
48
+ throw new Error("Unauthorized (401) by the Auth management API. Your session may have expired — run 'faable login' and retry.");
49
+ }
50
+ if (status === 403) {
51
+ throw new Error('Forbidden (403): your session is not allowed to manage this tenant. Check --auth-url/--account, or use credentials with management access for it.');
52
+ }
53
+ throw e;
54
+ };
55
+ // Wrap a yargs handler so management-API errors surface with the hints above.
56
+ const withAuthHints = (handler) => {
57
+ return async (args) => {
58
+ try {
59
+ await handler(args);
60
+ }
61
+ catch (e) {
62
+ hintAuthError(e);
63
+ }
64
+ };
65
+ };
66
+
67
+ export { hintAuthError, requireAuthAdmin, withAuthHints };
@@ -0,0 +1,62 @@
1
+ import fs from 'fs-extra';
2
+ import { withAuthHints, requireAuthAdmin } from '../../../api/auth_admin.js';
3
+ import { log } from '../../../log.js';
4
+ import { json_option, tenant_options } from '../options.js';
5
+ import { print_json } from '../render.js';
6
+
7
+ const actions_create = {
8
+ command: 'create',
9
+ describe: 'Create an action',
10
+ builder: yargs => json_option(tenant_options(yargs))
11
+ .option('name', {
12
+ alias: 'n',
13
+ type: 'string',
14
+ demandOption: true,
15
+ description: 'Action name (max 200 chars)'
16
+ })
17
+ .option('trigger', {
18
+ alias: 't',
19
+ type: 'string',
20
+ choices: ['post-login', 'continue'],
21
+ demandOption: true,
22
+ description: 'Trigger point'
23
+ })
24
+ .option('code-file', {
25
+ alias: 'f',
26
+ type: 'string',
27
+ description: 'Path to a JS file with the action code'
28
+ })
29
+ .option('disabled', {
30
+ type: 'boolean',
31
+ default: false,
32
+ description: 'Create the action disabled'
33
+ })
34
+ .option('order', {
35
+ type: 'number',
36
+ description: 'Execution order (lower runs first, default 0)'
37
+ })
38
+ .example('$0 auth actions create -n add-claims -t post-login -f ./claims.js', 'Create a post-login action from a file')
39
+ .showHelpOnFail(false),
40
+ handler: withAuthHints(async (args) => {
41
+ let code;
42
+ if (args.codeFile) {
43
+ if (!(await fs.pathExists(args.codeFile))) {
44
+ throw new Error(`Code file not found: ${args.codeFile}`);
45
+ }
46
+ code = await fs.readFile(args.codeFile, 'utf8');
47
+ }
48
+ const api = await requireAuthAdmin(args);
49
+ const action = await api.actionCreate({
50
+ name: args.name,
51
+ trigger: args.trigger,
52
+ ...(code !== undefined ? { code } : {}),
53
+ ...(args.disabled ? { enabled: false } : {}),
54
+ ...(args.order !== undefined ? { order: args.order } : {})
55
+ });
56
+ if (args.json)
57
+ return print_json(action);
58
+ log.info(`✅ Created action ${action.id} (${action.name}, trigger ${action.trigger})`);
59
+ })
60
+ };
61
+
62
+ export { actions_create };
@@ -0,0 +1,40 @@
1
+ import { withAuthHints, requireAuthAdmin } from '../../../api/auth_admin.js';
2
+ import { log } from '../../../log.js';
3
+ import { json_option, tenant_options } from '../options.js';
4
+ import { print_json, yes_no } from '../render.js';
5
+
6
+ const actions_get = {
7
+ command: 'get <action_id>',
8
+ describe: 'Show an action (use --code to print its source)',
9
+ builder: yargs => json_option(tenant_options(yargs))
10
+ .positional('action_id', {
11
+ type: 'string',
12
+ demandOption: true,
13
+ description: 'Action identifier'
14
+ })
15
+ .option('code', {
16
+ type: 'boolean',
17
+ default: false,
18
+ description: 'Print the action source code'
19
+ })
20
+ .showHelpOnFail(false),
21
+ handler: withAuthHints(async (args) => {
22
+ const api = await requireAuthAdmin(args);
23
+ const action = await api.actionGet(args.action_id);
24
+ if (args.json)
25
+ return print_json(action);
26
+ log.info(`⚙️ ${action.id}`);
27
+ log.info(` Name: ${action.name ?? '-'}`);
28
+ log.info(` Trigger: ${action.trigger ?? '-'}`);
29
+ log.info(` Enabled: ${yes_no(action.enabled)}`);
30
+ log.info(` Order: ${action.order ?? 0}`);
31
+ log.info(` Created: ${action.createdAt ?? '-'}`);
32
+ if (args.code) {
33
+ log.info(' Code:');
34
+ // Raw to stdout so it can be piped to a file untouched.
35
+ process.stdout.write((action.code ?? '') + '\n');
36
+ }
37
+ })
38
+ };
39
+
40
+ export { actions_get };
@@ -0,0 +1,19 @@
1
+ import { actions_create } from './create.js';
2
+ import { actions_get } from './get.js';
3
+ import { actions_list } from './list.js';
4
+ import { actions_rm } from './rm.js';
5
+
6
+ const actions = {
7
+ command: 'actions',
8
+ describe: 'Manage auth actions (login-flow hooks)',
9
+ builder: yargs => yargs
10
+ .command(actions_list)
11
+ .command(actions_get)
12
+ .command(actions_create)
13
+ .command(actions_rm)
14
+ .demandCommand(1)
15
+ .showHelpOnFail(false),
16
+ handler: () => { }
17
+ };
18
+
19
+ export { actions };
@@ -0,0 +1,43 @@
1
+ import { withAuthHints, requireAuthAdmin } from '../../../api/auth_admin.js';
2
+ import { log } from '../../../log.js';
3
+ import { json_option, list_options, tenant_options } from '../options.js';
4
+ import { fetch_items } from '../paging.js';
5
+ import { print_json, yes_no, when, table_lines } from '../render.js';
6
+
7
+ const actions_list = {
8
+ command: 'list',
9
+ describe: 'List actions',
10
+ builder: yargs => json_option(list_options(tenant_options(yargs)))
11
+ .option('query', {
12
+ type: 'string',
13
+ description: 'FaableQL filter, e.g. "trigger:post-login"'
14
+ })
15
+ .showHelpOnFail(false),
16
+ handler: withAuthHints(async (args) => {
17
+ const api = await requireAuthAdmin(args);
18
+ const { items, more } = await fetch_items(api.actionList({ query: args.query, pageSize: args.limit }), args.all);
19
+ if (args.json)
20
+ return print_json(items);
21
+ if (items.length === 0) {
22
+ log.info('📭 No actions.');
23
+ return;
24
+ }
25
+ log.info(`⚙️ ${items.length} action(s):`);
26
+ const rows = items.map(a => [
27
+ a.id ?? '-',
28
+ a.name ?? '-',
29
+ a.trigger ?? '-',
30
+ yes_no(a.enabled),
31
+ String(a.order ?? 0),
32
+ when(a.createdAt)
33
+ ]);
34
+ for (const line of table_lines(['ID', 'NAME', 'TRIGGER', 'ENABLED', 'ORDER', 'CREATED'], rows)) {
35
+ log.info(` ${line}`);
36
+ }
37
+ if (more) {
38
+ log.info('… more results available: raise --limit or use --all.');
39
+ }
40
+ })
41
+ };
42
+
43
+ export { actions_list };
@@ -0,0 +1,45 @@
1
+ import prompts from 'prompts';
2
+ import { withAuthHints, requireAuthAdmin } from '../../../api/auth_admin.js';
3
+ import { log } from '../../../log.js';
4
+ import { tenant_options } from '../options.js';
5
+
6
+ const actions_rm = {
7
+ command: 'rm <action_id>',
8
+ describe: 'Delete an action',
9
+ builder: yargs => tenant_options(yargs)
10
+ .positional('action_id', {
11
+ type: 'string',
12
+ demandOption: true,
13
+ description: 'Action identifier'
14
+ })
15
+ .option('yes', {
16
+ alias: 'y',
17
+ type: 'boolean',
18
+ default: false,
19
+ description: 'Skip the confirmation prompt'
20
+ })
21
+ .showHelpOnFail(false),
22
+ handler: withAuthHints(async (args) => {
23
+ const api = await requireAuthAdmin(args);
24
+ // Fetch first: shows WHAT will be deleted and 404s before the prompt.
25
+ const action = await api.actionGet(args.action_id);
26
+ if (!args.yes) {
27
+ const { confirm } = await prompts({
28
+ type: 'toggle',
29
+ name: 'confirm',
30
+ message: `Delete action "${action.name}" (${action.id}, trigger ${action.trigger})?`,
31
+ initial: false,
32
+ active: 'yes',
33
+ inactive: 'no'
34
+ });
35
+ if (!confirm) {
36
+ log.info('Cancelled.');
37
+ return;
38
+ }
39
+ }
40
+ await api.actionDelete(args.action_id);
41
+ log.info(`🗑️ Deleted action ${args.action_id}.`);
42
+ })
43
+ };
44
+
45
+ export { actions_rm };
@@ -0,0 +1,56 @@
1
+ import { withAuthHints, requireAuthAdmin } from '../../../api/auth_admin.js';
2
+ import { log } from '../../../log.js';
3
+ import { json_option, tenant_options } from '../options.js';
4
+ import { print_json } from '../render.js';
5
+
6
+ const clients_create = {
7
+ command: 'create',
8
+ describe: 'Create an OAuth client',
9
+ builder: yargs => json_option(tenant_options(yargs))
10
+ .option('name', {
11
+ alias: 'n',
12
+ type: 'string',
13
+ demandOption: true,
14
+ description: 'Client name'
15
+ })
16
+ .option('description', {
17
+ alias: 'd',
18
+ type: 'string',
19
+ description: 'Client description'
20
+ })
21
+ .option('callback', {
22
+ type: 'string',
23
+ array: true,
24
+ description: 'Allowed callback (redirect) URL — repeatable'
25
+ })
26
+ .option('logout-url', {
27
+ type: 'string',
28
+ array: true,
29
+ description: 'Allowed post-logout URL — repeatable'
30
+ })
31
+ .option('web-origin', {
32
+ type: 'string',
33
+ array: true,
34
+ description: 'Allowed web origin (CORS) — repeatable'
35
+ })
36
+ .example('$0 auth clients create -n my-app --callback https://app.example.com/callback', 'Create a client with one redirect URL')
37
+ .showHelpOnFail(false),
38
+ handler: withAuthHints(async (args) => {
39
+ const api = await requireAuthAdmin(args);
40
+ const client = await api.clientCreate({
41
+ name: args.name,
42
+ ...(args.description ? { description: args.description } : {}),
43
+ ...(args.callback?.length ? { callbacks: args.callback } : {}),
44
+ ...(args.logoutUrl?.length ? { logout_urls: args.logoutUrl } : {}),
45
+ ...(args.webOrigin?.length ? { web_origins: args.webOrigin } : {})
46
+ });
47
+ if (args.json)
48
+ return print_json(client);
49
+ log.info(`✅ Created client "${client.name}" (${client.id})`);
50
+ log.info(` Client ID: ${client.client_id}`);
51
+ log.info(` Client secret: ${client.client_secret}`);
52
+ log.info(' ⚠️ Store the secret now — treat it like a password.');
53
+ })
54
+ };
55
+
56
+ export { clients_create };
@@ -0,0 +1,46 @@
1
+ import { withAuthHints, requireAuthAdmin } from '../../../api/auth_admin.js';
2
+ import { log } from '../../../log.js';
3
+ import { json_option, tenant_options } from '../options.js';
4
+ import { print_json } from '../render.js';
5
+ import { resolve_client } from './resolve.js';
6
+
7
+ const clients_get = {
8
+ command: 'get <client_id>',
9
+ describe: 'Show an OAuth client',
10
+ builder: yargs => json_option(tenant_options(yargs))
11
+ .positional('client_id', {
12
+ type: 'string',
13
+ demandOption: true,
14
+ description: 'Client resource id or OAuth client_id'
15
+ })
16
+ .option('secret', {
17
+ type: 'boolean',
18
+ default: false,
19
+ description: 'Also print the client secret'
20
+ })
21
+ .showHelpOnFail(false),
22
+ handler: withAuthHints(async (args) => {
23
+ const api = await requireAuthAdmin(args);
24
+ const client = await resolve_client(api, args.client_id);
25
+ if (args.json)
26
+ return print_json(client);
27
+ log.info(`🔑 ${client.name ?? client.id}`);
28
+ log.info(` ID: ${client.id}`);
29
+ log.info(` Client ID: ${client.client_id ?? '-'}`);
30
+ if (args.secret) {
31
+ log.info(` Secret: ${client.client_secret ?? '-'}`);
32
+ }
33
+ if (client.description)
34
+ log.info(` Description: ${client.description}`);
35
+ log.info(` Callbacks: ${client.callbacks?.length ? client.callbacks.join(', ') : '-'}`);
36
+ if (client.logout_urls?.length) {
37
+ log.info(` Logout URLs: ${client.logout_urls.join(', ')}`);
38
+ }
39
+ if (client.web_origins?.length) {
40
+ log.info(` Web origins: ${client.web_origins.join(', ')}`);
41
+ }
42
+ log.info(` Created: ${client.createdAt ?? '-'}`);
43
+ })
44
+ };
45
+
46
+ export { clients_get };
@@ -0,0 +1,19 @@
1
+ import { clients_create } from './create.js';
2
+ import { clients_get } from './get.js';
3
+ import { clients_list } from './list.js';
4
+ import { clients_rm } from './rm.js';
5
+
6
+ const clients = {
7
+ command: 'clients',
8
+ describe: 'Manage OAuth clients',
9
+ builder: yargs => yargs
10
+ .command(clients_list)
11
+ .command(clients_get)
12
+ .command(clients_create)
13
+ .command(clients_rm)
14
+ .demandCommand(1)
15
+ .showHelpOnFail(false),
16
+ handler: () => { }
17
+ };
18
+
19
+ export { clients };
@@ -0,0 +1,41 @@
1
+ import { withAuthHints, requireAuthAdmin } from '../../../api/auth_admin.js';
2
+ import { log } from '../../../log.js';
3
+ import { json_option, list_options, tenant_options } from '../options.js';
4
+ import { fetch_items } from '../paging.js';
5
+ import { print_json, truncate, when, table_lines } from '../render.js';
6
+
7
+ const clients_list = {
8
+ command: 'list',
9
+ describe: 'List OAuth clients',
10
+ builder: yargs => json_option(list_options(tenant_options(yargs)))
11
+ .option('q', {
12
+ type: 'string',
13
+ description: 'Full-text search over name/description/client_id'
14
+ })
15
+ .showHelpOnFail(false),
16
+ handler: withAuthHints(async (args) => {
17
+ const api = await requireAuthAdmin(args);
18
+ const { items, more } = await fetch_items(api.clientList({ q: args.q, pageSize: args.limit }), args.all);
19
+ if (args.json)
20
+ return print_json(items);
21
+ if (items.length === 0) {
22
+ log.info('📭 No clients.');
23
+ return;
24
+ }
25
+ log.info(`🔑 ${items.length} client(s):`);
26
+ const rows = items.map(c => [
27
+ c.client_id ?? '-',
28
+ truncate(c.name, 28),
29
+ String(c.callbacks?.length ?? 0),
30
+ when(c.createdAt)
31
+ ]);
32
+ for (const line of table_lines(['CLIENT_ID', 'NAME', 'CALLBACKS', 'CREATED'], rows)) {
33
+ log.info(` ${line}`);
34
+ }
35
+ if (more) {
36
+ log.info('… more results available: raise --limit or use --all.');
37
+ }
38
+ })
39
+ };
40
+
41
+ export { clients_list };
@@ -0,0 +1,21 @@
1
+ // Accept either the resource id (the /client/:id param) or the OAuth
2
+ // `client_id`. The direct lookup rejects a non-resource id with 400 (schema
3
+ // format) or 404 — on either, fall back to a search (the management list is
4
+ // searchable by client_id) and use a single exact match.
5
+ const resolve_client = async (api, id) => {
6
+ try {
7
+ return await api.clientGet(id);
8
+ }
9
+ catch (e) {
10
+ const status = e?.response?.status;
11
+ if (status !== 404 && status !== 400)
12
+ throw e;
13
+ const candidates = await api.clientList({ q: id, pageSize: 10 }).pass();
14
+ const exact = candidates.results.filter(c => c.client_id === id);
15
+ if (exact.length === 1)
16
+ return exact[0];
17
+ throw new Error(`Client not found: ${id}`, { cause: e });
18
+ }
19
+ };
20
+
21
+ export { resolve_client };
@@ -0,0 +1,45 @@
1
+ import prompts from 'prompts';
2
+ import { withAuthHints, requireAuthAdmin } from '../../../api/auth_admin.js';
3
+ import { log } from '../../../log.js';
4
+ import { tenant_options } from '../options.js';
5
+ import { resolve_client } from './resolve.js';
6
+
7
+ const clients_rm = {
8
+ command: 'rm <client_id>',
9
+ describe: 'Delete an OAuth client',
10
+ builder: yargs => tenant_options(yargs)
11
+ .positional('client_id', {
12
+ type: 'string',
13
+ demandOption: true,
14
+ description: 'Client resource id or OAuth client_id'
15
+ })
16
+ .option('yes', {
17
+ alias: 'y',
18
+ type: 'boolean',
19
+ default: false,
20
+ description: 'Skip the confirmation prompt'
21
+ })
22
+ .showHelpOnFail(false),
23
+ handler: withAuthHints(async (args) => {
24
+ const api = await requireAuthAdmin(args);
25
+ const client = await resolve_client(api, args.client_id);
26
+ if (!args.yes) {
27
+ const { confirm } = await prompts({
28
+ type: 'toggle',
29
+ name: 'confirm',
30
+ message: `Delete client "${client.name}" (${client.client_id})? Apps using it will stop authenticating.`,
31
+ initial: false,
32
+ active: 'yes',
33
+ inactive: 'no'
34
+ });
35
+ if (!confirm) {
36
+ log.info('Cancelled.');
37
+ return;
38
+ }
39
+ }
40
+ await api.clientDelete(client.id);
41
+ log.info(`🗑️ Deleted client ${client.client_id} (${client.id}).`);
42
+ })
43
+ };
44
+
45
+ export { clients_rm };
@@ -0,0 +1,23 @@
1
+ import { actions } from './actions/index.js';
2
+ import { clients } from './clients/index.js';
3
+ import { logs } from './logs/index.js';
4
+ import { users } from './users/index.js';
5
+
6
+ // `faable auth` — management commands for a Faable Auth tenant. Auth: reuses
7
+ // the `faable login` session (or FAABLE_TOKEN); tenant: --auth-url /
8
+ // FAABLE_AUTH_URL (defaults to the Faable tenant host). See
9
+ // src/api/auth_admin.ts.
10
+ const auth = {
11
+ command: 'auth',
12
+ describe: 'Manage Faable Auth (users, actions, clients, audit logs)',
13
+ builder: yargs => yargs
14
+ .command(users)
15
+ .command(actions)
16
+ .command(clients)
17
+ .command(logs)
18
+ .demandCommand(1)
19
+ .showHelpOnFail(false),
20
+ handler: () => { }
21
+ };
22
+
23
+ export { auth };
@@ -0,0 +1,48 @@
1
+ import { withAuthHints, requireAuthAdmin } from '../../../api/auth_admin.js';
2
+ import { log } from '../../../log.js';
3
+ import { json_option, tenant_options } from '../options.js';
4
+ import { print_json, log_status_badge } from '../render.js';
5
+
6
+ const logs_get = {
7
+ command: 'get <log_id>',
8
+ describe: 'Show an audit log entry',
9
+ builder: yargs => json_option(tenant_options(yargs))
10
+ .positional('log_id', {
11
+ type: 'string',
12
+ demandOption: true,
13
+ description: 'Log entry identifier'
14
+ })
15
+ .showHelpOnFail(false),
16
+ handler: withAuthHints(async (args) => {
17
+ const api = await requireAuthAdmin(args);
18
+ const entry = await api.logGet(args.log_id);
19
+ if (args.json)
20
+ return print_json(entry);
21
+ log.info(`📜 ${entry.id}`);
22
+ log.info(` Date: ${entry.createdAt ?? '-'}`);
23
+ log.info(` Type: ${entry.type ?? '-'}`);
24
+ log.info(` Status: ${log_status_badge(entry.status)}`);
25
+ if (entry.message)
26
+ log.info(` Message: ${entry.message}`);
27
+ const refs = [
28
+ ['User', entry.user],
29
+ ['Client', entry.client],
30
+ ['Connection', entry.connection],
31
+ ['Team', entry.team],
32
+ ['Identity', entry.identity]
33
+ ];
34
+ for (const [label, ref] of refs) {
35
+ if (!ref)
36
+ continue;
37
+ const id = typeof ref === 'string' ? ref : ref.id;
38
+ if (id)
39
+ log.info(` ${label}:${' '.repeat(Math.max(1, 8 - label.length))}${id}`);
40
+ }
41
+ if (entry.data !== undefined && entry.data !== null) {
42
+ log.info(' Data:');
43
+ process.stdout.write(JSON.stringify(entry.data, null, 2) + '\n');
44
+ }
45
+ })
46
+ };
47
+
48
+ export { logs_get };
@@ -0,0 +1,15 @@
1
+ import { logs_get } from './get.js';
2
+ import { logs_list } from './list.js';
3
+
4
+ const logs = {
5
+ command: 'logs',
6
+ describe: 'Browse the audit log (read-only)',
7
+ builder: yargs => yargs
8
+ .command(logs_list)
9
+ .command(logs_get)
10
+ .demandCommand(1)
11
+ .showHelpOnFail(false),
12
+ handler: () => { }
13
+ };
14
+
15
+ export { logs };