@bussolabs/closeyourit-cli 0.0.4 → 0.0.6
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 +1 -0
- package/dist/commands/logs/list.d.ts +13 -0
- package/dist/commands/logs/list.js +51 -0
- package/dist/lib/output.js +22 -6
- package/oclif.manifest.json +902 -831
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -61,6 +61,7 @@ organization are stored locally.
|
|
|
61
61
|
| `tickets create --project <id\|key> --title <t> [--kind] [--step-*] [--description] [--status-id] [--priority-id]` | Create a ticket. |
|
|
62
62
|
| `metrics list --project <id\|key> [--kind] [--page]` | List metric groups. |
|
|
63
63
|
| `metrics show <id> --project <id\|key>` | Show one metric group. |
|
|
64
|
+
| `logs list [--project <id\|key>] [--level] [--trace-id] [--environment] [--page]` | List structured log entries. Cross-app: omit `--project` for the full visible stream. |
|
|
64
65
|
|
|
65
66
|
Every command accepts `--json` for machine-readable output and `--help` for usage details.
|
|
66
67
|
`--project` accepts either a project UUID or its key (matched case-insensitively).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class LogsList extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
page: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
level: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
'trace-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
environment: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<unknown>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_1 = require("../../base");
|
|
5
|
+
const output_1 = require("../../lib/output");
|
|
6
|
+
/** Collapse whitespace (log messages may be multi-line) and truncate so the table stays single-line. */
|
|
7
|
+
function oneLine(value, max = 80) {
|
|
8
|
+
const flat = value.replace(/\s+/g, ' ').trim();
|
|
9
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
10
|
+
}
|
|
11
|
+
class LogsList extends base_1.BaseCommand {
|
|
12
|
+
static description = 'List structured log entries (cross-app stream)';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> logs list',
|
|
15
|
+
'<%= config.bin %> logs list --project acme-api --level error',
|
|
16
|
+
'<%= config.bin %> logs list --trace-id 7f3c… --json',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
// Optional here (the endpoint is cross-app): without it, the stream spans every visible project.
|
|
20
|
+
project: core_1.Flags.string({ char: 'p', description: 'Filter by project id (UUID) or key' }),
|
|
21
|
+
level: core_1.Flags.string({ description: 'Filter by level (debug, info, warning, error, fatal)' }),
|
|
22
|
+
'trace-id': core_1.Flags.string({ description: 'Filter by trace id (correlate one request)' }),
|
|
23
|
+
environment: core_1.Flags.string({ description: 'Filter by environment (e.g. production)' }),
|
|
24
|
+
...base_1.pageFlag,
|
|
25
|
+
};
|
|
26
|
+
async run() {
|
|
27
|
+
const { flags } = await this.parse(LogsList);
|
|
28
|
+
const query = new URLSearchParams({ page: String(flags.page) });
|
|
29
|
+
if (flags.project)
|
|
30
|
+
query.set('project_id', await this.resolveProjectId(flags.project));
|
|
31
|
+
if (flags.level)
|
|
32
|
+
query.set('level', flags.level);
|
|
33
|
+
if (flags['trace-id'])
|
|
34
|
+
query.set('trace_id', flags['trace-id']);
|
|
35
|
+
if (flags.environment)
|
|
36
|
+
query.set('environment', flags.environment);
|
|
37
|
+
const res = await this.api.get(`/cli/v1/log_entries?${query.toString()}`);
|
|
38
|
+
const entries = res.data ?? [];
|
|
39
|
+
if (!this.jsonEnabled()) {
|
|
40
|
+
this.log((0, output_1.renderTable)(['OCCURRED_AT', 'LEVEL', 'ENV', 'LOGGER', 'MESSAGE'], entries.map((entry) => [
|
|
41
|
+
String(entry.occurred_at ?? ''),
|
|
42
|
+
String(entry.level ?? ''),
|
|
43
|
+
String(entry.environment ?? ''),
|
|
44
|
+
String(entry.logger ?? ''),
|
|
45
|
+
oneLine(String(entry.message ?? '')),
|
|
46
|
+
])));
|
|
47
|
+
}
|
|
48
|
+
return res;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.default = LogsList;
|
package/dist/lib/output.js
CHANGED
|
@@ -2,20 +2,36 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.renderTable = renderTable;
|
|
4
4
|
exports.renderRecord = renderRecord;
|
|
5
|
+
/**
|
|
6
|
+
* Neutralize terminal control sequences (CWE-150) in server-supplied strings.
|
|
7
|
+
* Log/error/ticket text is attacker-influenceable; printing raw ESC (0x1B) & co.
|
|
8
|
+
* to a TTY allows cursor/color/title injection. Replace every C0/C1 control char
|
|
9
|
+
* (0x00–0x1F, 0x7F–0x9F) with a space — ANSI is defused, width is preserved.
|
|
10
|
+
*/
|
|
11
|
+
function sanitize(value) {
|
|
12
|
+
let out = '';
|
|
13
|
+
for (const ch of String(value ?? '')) {
|
|
14
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
15
|
+
out += code <= 0x1f || (code >= 0x7f && code <= 0x9f) ? ' ' : ch;
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
5
19
|
/** Minimal dependency-free table renderer for human output. */
|
|
6
20
|
function renderTable(headers, rows) {
|
|
7
|
-
|
|
8
|
-
|
|
21
|
+
const safeHeaders = headers.map(sanitize);
|
|
22
|
+
const safeRows = rows.map((row) => row.map(sanitize));
|
|
23
|
+
if (safeRows.length === 0) {
|
|
24
|
+
return `${safeHeaders.join(' ')}\n(no results)`;
|
|
9
25
|
}
|
|
10
|
-
const widths =
|
|
11
|
-
const line = (cols) => cols.map((cell, col) =>
|
|
26
|
+
const widths = safeHeaders.map((header, col) => Math.max(header.length, ...safeRows.map((row) => (row[col] ?? '').length)));
|
|
27
|
+
const line = (cols) => cols.map((cell, col) => (cell ?? '').padEnd(widths[col])).join(' ').trimEnd();
|
|
12
28
|
const separator = widths.map((width) => '-'.repeat(width));
|
|
13
|
-
return [line(
|
|
29
|
+
return [line(safeHeaders), line(separator), ...safeRows.map(line)].join('\n');
|
|
14
30
|
}
|
|
15
31
|
/** Pretty-print the scalar fields of a record as `key: value` lines. */
|
|
16
32
|
function renderRecord(obj) {
|
|
17
33
|
const lines = Object.entries(obj)
|
|
18
34
|
.filter(([, value]) => value === null || ['string', 'number', 'boolean'].includes(typeof value))
|
|
19
|
-
.map(([key, value]) => `${key}: ${value === null ? '-' : value}`);
|
|
35
|
+
.map(([key, value]) => `${sanitize(key)}: ${value === null ? '-' : sanitize(value)}`);
|
|
20
36
|
return lines.length > 0 ? lines.join('\n') : JSON.stringify(obj, null, 2);
|
|
21
37
|
}
|