@bussolabs/closeyourit-cli 0.0.21 โ†’ 0.0.22

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.
@@ -24,7 +24,7 @@ class AgentsList extends base_1.BaseCommand {
24
24
  String(agent.kind ?? ''),
25
25
  String(agent.schedule ?? ''),
26
26
  String(agent.enabled ?? ''),
27
- String(agent.last_run_status ?? '-'),
27
+ agent.last_run_status ? (0, output_1.statusCell)(agent.last_run_status) : '-',
28
28
  String(agent.id ?? ''),
29
29
  ])));
30
30
  }
@@ -19,7 +19,7 @@ class AgentsRunsList extends base_1.BaseCommand {
19
19
  const runs = res.data ?? [];
20
20
  if (!this.jsonEnabled()) {
21
21
  this.log((0, output_1.renderTable)(['STATUS', 'PROJECT', 'DURATION', 'STARTED', 'ID'], runs.map((run) => [
22
- String(run.status ?? ''),
22
+ (0, output_1.statusCell)(run.status),
23
23
  String(run.project_id ?? '-'),
24
24
  run.duration_ms == null ? '-' : `${run.duration_ms} ms`,
25
25
  String(run.started_at ?? '-'),
@@ -26,7 +26,7 @@ class CronMonitorsList extends base_1.BaseCommand {
26
26
  if (!this.jsonEnabled()) {
27
27
  this.log((0, output_1.renderTable)(['NAME', 'STATUS', 'INTERVAL (min)', 'LAST CHECK-IN', 'ID'], monitors.map((monitor) => [
28
28
  String(monitor.name ?? ''),
29
- String(monitor.status ?? ''),
29
+ (0, output_1.statusCell)(monitor.status),
30
30
  String(monitor.expected_interval_minutes ?? '-'),
31
31
  String(monitor.last_check_in_at ?? '-'),
32
32
  String(monitor.id ?? ''),
@@ -26,7 +26,7 @@ class ErrorsList extends base_1.BaseCommand {
26
26
  this.log((0, output_1.renderTable)(['ID', 'TITLE', 'STATUS', 'EVENTS'], groups.map((group) => [
27
27
  String(group.id ?? ''),
28
28
  String(group.title ?? ''),
29
- String(group.status ?? ''),
29
+ (0, output_1.statusCell)(group.status),
30
30
  String(group.events_count ?? ''),
31
31
  ])));
32
32
  }
@@ -29,7 +29,7 @@ class IdeasList extends base_1.BaseCommand {
29
29
  this.log((0, output_1.renderTable)(['ID', 'TITLE', 'STATUS', 'VOTES', 'COMMENTS'], ideas.map((idea) => [
30
30
  String(idea.id ?? ''),
31
31
  String(idea.title ?? ''),
32
- String(idea.status ?? ''),
32
+ (0, output_1.statusCell)(idea.status),
33
33
  String(idea.votes_count ?? 0),
34
34
  String(idea.comments_count ?? 0),
35
35
  ])));
@@ -18,14 +18,14 @@ class IdeasShow extends base_1.BaseCommand {
18
18
  const { problem, solution, stakeholders, ...fields } = res.data ?? {};
19
19
  this.log((0, output_1.renderRecord)(fields));
20
20
  if (Array.isArray(stakeholders) && stakeholders.length > 0) {
21
- this.log(`\nStakeholders: ${stakeholders.join(', ')}`);
21
+ this.log(`\n๐Ÿ‘ฅ Stakeholders: ${stakeholders.join(', ')}`);
22
22
  }
23
23
  if (problem) {
24
- this.log('\nProblem:');
24
+ this.log('\n๐ŸŽฏ Problem:');
25
25
  this.log((0, output_1.sanitizeMultiline)(String(problem)));
26
26
  }
27
27
  if (solution) {
28
- this.log('\nSolution:');
28
+ this.log('\n๐Ÿ’ก Solution:');
29
29
  this.log((0, output_1.sanitizeMultiline)(String(solution)));
30
30
  }
31
31
  }
@@ -33,7 +33,7 @@ class KbAsk extends base_1.BaseCommand {
33
33
  this.log((0, output_1.sanitizeMultiline)(data.answer));
34
34
  const pages = Array.isArray(data.pages) ? data.pages : [];
35
35
  if (pages.length > 0) {
36
- this.log('\nSources:');
36
+ this.log('\n๐Ÿ“š Sources:');
37
37
  for (const page of pages) {
38
38
  this.log(` [${(0, output_1.sanitize)(page.kind)}] ${(0, output_1.sanitize)(page.title)} ยท ${(0, output_1.sanitize)(page.id)}`);
39
39
  }
@@ -18,7 +18,7 @@ class MonitorsList extends base_1.BaseCommand {
18
18
  this.log((0, output_1.renderTable)(['NAME', 'URL', 'STATUS', 'ACTIVE', 'ID'], monitors.map((monitor) => [
19
19
  String(monitor.name ?? ''),
20
20
  String(monitor.url ?? ''),
21
- String(monitor.current_status ?? ''),
21
+ (0, output_1.statusCell)(monitor.current_status),
22
22
  String(monitor.active ?? ''),
23
23
  String(monitor.id ?? ''),
24
24
  ])));
@@ -27,7 +27,7 @@ class ServersList extends base_1.BaseCommand {
27
27
  if (!this.jsonEnabled()) {
28
28
  this.log((0, output_1.renderTable)(['NAME', 'STATUS', 'CPU%', 'MEM%', 'DISK%', 'LAST SEEN', 'ID'], hosts.map((host) => [
29
29
  String(host.name ?? ''),
30
- String(host.status ?? ''),
30
+ (0, output_1.statusCell)(host.status),
31
31
  String(host.cpu_pct ?? '-'),
32
32
  String(host.mem_pct ?? '-'),
33
33
  String(host.disk_pct ?? '-'),
@@ -15,7 +15,7 @@ class TicketsList extends base_1.BaseCommand {
15
15
  this.log((0, output_1.renderTable)(['CODE', 'TITLE', 'STATUS'], tickets.map((ticket) => [
16
16
  String(ticket.code ?? ''),
17
17
  String(ticket.title ?? ''),
18
- String(ticket.status ?? ''),
18
+ (0, output_1.statusCell)(ticket.status, typeof ticket.status_color === 'string' ? ticket.status_color : undefined),
19
19
  ])));
20
20
  }
21
21
  return res;
@@ -46,7 +46,7 @@ class TicketsShow extends base_1.BaseCommand {
46
46
  return all;
47
47
  }
48
48
  renderAttachments(attachments) {
49
- this.log(`\nAttachments (${attachments.length})`);
49
+ this.log(`\n๐Ÿ“Ž Attachments (${attachments.length})`);
50
50
  if (attachments.length === 0)
51
51
  return;
52
52
  this.log((0, output_1.renderTable)(['ID', 'FILENAME', 'CONTENT_TYPE', 'BYTE_SIZE', 'CREATED_AT'], attachments.map((attachment) => [
@@ -58,7 +58,7 @@ class TicketsShow extends base_1.BaseCommand {
58
58
  ])));
59
59
  }
60
60
  renderComments(comments) {
61
- this.log(`\nComments (${comments.length})`);
61
+ this.log(`\n๐Ÿ’ฌ Comments (${comments.length})`);
62
62
  for (const comment of comments) {
63
63
  this.log(`\nโ”€โ”€ ${(0, output_1.sanitize)(comment.author ?? '?')} ยท ${(0, output_1.sanitize)(comment.created_at ?? '')} ยท ${(0, output_1.sanitize)(comment.id ?? '')}`);
64
64
  this.log((0, output_1.sanitizeMultiline)(comment.body ?? ''));
@@ -90,13 +90,13 @@ class TicketsShow extends base_1.BaseCommand {
90
90
  }));
91
91
  // Body: description + technical analysis (only if present).
92
92
  const scalarBlocks = [
93
- ['Description', ticket.description],
94
- ['Technical analysis', ticket.technical_analysis],
93
+ ['๐Ÿ“', 'Description', ticket.description],
94
+ ['๐Ÿ”ง', 'Technical analysis', ticket.technical_analysis],
95
95
  ];
96
- for (const [label, value] of scalarBlocks) {
96
+ for (const [icon, label, value] of scalarBlocks) {
97
97
  if (value === null || value === undefined || String(value).trim() === '')
98
98
  continue;
99
- this.log(`\n${label}\n${'-'.repeat(label.length)}`);
99
+ this.log((0, output_1.section)(label, icon));
100
100
  this.log((0, output_1.sanitizeMultiline)(value));
101
101
  }
102
102
  this.renderScenarios(Array.isArray(ticket.scenarios) ? ticket.scenarios : []);
@@ -112,7 +112,7 @@ class TicketsShow extends base_1.BaseCommand {
112
112
  ];
113
113
  scenarios.forEach((scenario, index) => {
114
114
  const title = scenario.title ? `Scenario ${index + 1}: ${(0, output_1.sanitize)(scenario.title)}` : `Scenario ${index + 1}`;
115
- this.log(`\n${title}\n${'-'.repeat(title.length)}`);
115
+ this.log((0, output_1.section)(title, '๐Ÿงช'));
116
116
  for (const [label, key] of steps) {
117
117
  const value = scenario[key];
118
118
  if (value === null || value === undefined || String(value).trim() === '')
@@ -125,7 +125,7 @@ class TicketsShow extends base_1.BaseCommand {
125
125
  renderConditions(conditions) {
126
126
  if (conditions.length === 0)
127
127
  return;
128
- this.log('\nDefinition of Done\n------------------');
128
+ this.log((0, output_1.section)('Definition of Done', 'โœ…'));
129
129
  for (const condition of conditions)
130
130
  this.log(`- ${(0, output_1.sanitizeMultiline)(condition.text ?? '')}`);
131
131
  }
@@ -16,7 +16,7 @@ class TokensList extends base_1.BaseCommand {
16
16
  String(token.id ?? ''),
17
17
  String(token.name ?? ''),
18
18
  String(token.token_prefix ?? ''),
19
- String(token.status ?? ''),
19
+ (0, output_1.statusCell)(token.status),
20
20
  ])));
21
21
  }
22
22
  return res;
@@ -11,7 +11,23 @@ export declare function sanitize(value: unknown): string;
11
11
  * Every other C0/C1 control char is still defused per line.
12
12
  */
13
13
  export declare function sanitizeMultiline(value: unknown): string;
14
+ /** Terminal display width of a string (wide code points count as 2, zero-width joiners/VS as 0). */
15
+ export declare function displayWidth(value: string): number;
14
16
  /** Minimal dependency-free table renderer for human output. */
15
17
  export declare function renderTable(headers: string[], rows: string[][]): string;
16
- /** Pretty-print the scalar fields of a record as `key: value` lines. */
18
+ /**
19
+ * Status dots make problems scannable at a glance. Four fixed-width circles (no variation
20
+ * selectors, so table columns stay aligned): down=๐Ÿ”ด, warn=๐ŸŸก, ok=๐ŸŸข, neutral=โšช.
21
+ */
22
+ export type StatusTone = 'down' | 'warn' | 'ok' | 'neutral';
23
+ /**
24
+ * Status dot for a value. With `color` (a Tailwind family name) it uses the colour map โ€” the stable
25
+ * token for org-renamed status/priority; otherwise the monitoring string-enum map. Unknown โ†’ neutral.
26
+ */
27
+ export declare function statusDot(value: unknown, color?: string): string;
28
+ /** A table cell with its status dot prefixed (the emoji survives `sanitize`). */
29
+ export declare function statusCell(value: unknown, color?: string): string;
30
+ /** A section heading: optional emoji + title, underlined with dashes sized to the title. */
31
+ export declare function section(title: string, icon?: string): string;
32
+ /** Pretty-print the scalar fields of a record as `key: value` lines (status keys get a dot). */
17
33
  export declare function renderRecord(obj: Record<string, unknown>): string;
@@ -2,7 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sanitize = sanitize;
4
4
  exports.sanitizeMultiline = sanitizeMultiline;
5
+ exports.displayWidth = displayWidth;
5
6
  exports.renderTable = renderTable;
7
+ exports.statusDot = statusDot;
8
+ exports.statusCell = statusCell;
9
+ exports.section = section;
6
10
  exports.renderRecord = renderRecord;
7
11
  /**
8
12
  * Neutralize terminal control sequences (CWE-150) in server-supplied strings.
@@ -29,6 +33,32 @@ function sanitizeMultiline(value) {
29
33
  .map((line) => sanitize(line))
30
34
  .join('\n');
31
35
  }
36
+ // Code-point ranges that render as 2 terminal columns (CJK + emoji + symbols). Emoji like ๐Ÿ”ด have a
37
+ // JS `.length` of 2 while โšช (U+26AA) has `.length` 1 โ€” yet both render 2-wide, so padding a column by
38
+ // `.length` misaligns rows that mix them. Measuring by code point keeps the status column square.
39
+ const WIDE_RANGES = [
40
+ [0x1100, 0x115f], // Hangul Jamo
41
+ [0x2600, 0x27bf], // Misc symbols + Dingbats (โšช โšซ โœ… โ€ฆ)
42
+ [0x2b00, 0x2bff], // Misc symbols and arrows
43
+ [0x2e80, 0xa4cf], // CJK & friends
44
+ [0xac00, 0xd7a3], // Hangul syllables
45
+ [0xf900, 0xfaff], // CJK compatibility ideographs
46
+ [0xfe30, 0xfe4f], // CJK compatibility forms
47
+ [0xff00, 0xff60], // Fullwidth forms
48
+ [0xffe0, 0xffe6], // Fullwidth signs
49
+ [0x1f000, 0x1faff], // emoji & pictographs (๐Ÿ”ด ๐ŸŸก ๐ŸŸข โ€ฆ)
50
+ ];
51
+ /** Terminal display width of a string (wide code points count as 2, zero-width joiners/VS as 0). */
52
+ function displayWidth(value) {
53
+ let width = 0;
54
+ for (const ch of value) {
55
+ const cp = ch.codePointAt(0) ?? 0;
56
+ if (cp === 0x200d || (cp >= 0xfe00 && cp <= 0xfe0f) || (cp >= 0x0300 && cp <= 0x036f))
57
+ continue;
58
+ width += WIDE_RANGES.some(([lo, hi]) => cp >= lo && cp <= hi) ? 2 : 1;
59
+ }
60
+ return width;
61
+ }
32
62
  /** Minimal dependency-free table renderer for human output. */
33
63
  function renderTable(headers, rows) {
34
64
  const safeHeaders = headers.map(sanitize);
@@ -36,15 +66,120 @@ function renderTable(headers, rows) {
36
66
  if (safeRows.length === 0) {
37
67
  return `${safeHeaders.join(' ')}\n(no results)`;
38
68
  }
39
- const widths = safeHeaders.map((header, col) => Math.max(header.length, ...safeRows.map((row) => (row[col] ?? '').length)));
40
- const line = (cols) => cols.map((cell, col) => (cell ?? '').padEnd(widths[col])).join(' ').trimEnd();
69
+ const widths = safeHeaders.map((header, col) => Math.max(displayWidth(header), ...safeRows.map((row) => displayWidth(row[col] ?? ''))));
70
+ const pad = (cell, col) => cell + ' '.repeat(Math.max(0, widths[col] - displayWidth(cell)));
71
+ const line = (cols) => cols.map((cell, col) => pad(cell ?? '', col)).join(' ').trimEnd();
41
72
  const separator = widths.map((width) => '-'.repeat(width));
42
73
  return [line(safeHeaders), line(separator), ...safeRows.map(line)].join('\n');
43
74
  }
44
- /** Pretty-print the scalar fields of a record as `key: value` lines. */
75
+ const TONE_DOT = {
76
+ down: '๐Ÿ”ด',
77
+ neutral: 'โšช',
78
+ ok: '๐ŸŸข',
79
+ warn: '๐ŸŸก',
80
+ };
81
+ /**
82
+ * Stable enum strings of the monitoring domains โ†’ tone (keys are lowercase). These come straight
83
+ * from the model enums and never get org-renamed, so the CLI maps them without any backend help.
84
+ * Caveats baked in: agent-run failure is `fail` (not `failed`); a paused monitor/host is neutral.
85
+ */
86
+ const STATUS_TONE = {
87
+ // ideas
88
+ archived: 'neutral',
89
+ // errors โ€” status
90
+ ignored: 'neutral',
91
+ resolved: 'ok',
92
+ unresolved: 'down',
93
+ // errors โ€” level
94
+ debug: 'neutral',
95
+ error: 'down',
96
+ fatal: 'down',
97
+ info: 'ok',
98
+ warning: 'warn',
99
+ // uptime monitor + server host
100
+ down: 'down',
101
+ paused: 'neutral',
102
+ pending: 'warn',
103
+ unknown: 'neutral',
104
+ up: 'ok',
105
+ // cron monitor
106
+ late: 'warn',
107
+ missed: 'down',
108
+ ok: 'ok',
109
+ // ideas
110
+ converted: 'ok',
111
+ open: 'warn',
112
+ // incident phase
113
+ detected: 'down',
114
+ fixing: 'warn',
115
+ investigating: 'warn',
116
+ monitoring: 'warn',
117
+ // agent run
118
+ fail: 'down',
119
+ running: 'warn',
120
+ success: 'ok',
121
+ // token
122
+ active: 'ok',
123
+ expired: 'neutral',
124
+ revoked: 'neutral',
125
+ };
126
+ /**
127
+ * Badge colour (Tailwind family name) โ†’ tone. Ticket status/priority labels are org-customisable,
128
+ * so the serializer ships the badge colour as a stable token and the CLI translates it here โ€”
129
+ * identical to what the web badge shows. Unknown colour โ†’ falls back to the value string map.
130
+ */
131
+ const COLOR_TONE = {
132
+ amber: 'warn',
133
+ emerald: 'ok',
134
+ gray: 'neutral',
135
+ green: 'ok',
136
+ indigo: 'warn',
137
+ orange: 'down',
138
+ red: 'down',
139
+ sky: 'ok',
140
+ teal: 'ok',
141
+ violet: 'warn',
142
+ };
143
+ /**
144
+ * Status dot for a value. With `color` (a Tailwind family name) it uses the colour map โ€” the stable
145
+ * token for org-renamed status/priority; otherwise the monitoring string-enum map. Unknown โ†’ neutral.
146
+ */
147
+ function statusDot(value, color) {
148
+ const tone = (color ? COLOR_TONE[color.trim().toLowerCase()] : undefined) ??
149
+ STATUS_TONE[String(value ?? '').trim().toLowerCase()] ??
150
+ 'neutral';
151
+ return TONE_DOT[tone];
152
+ }
153
+ /** A table cell with its status dot prefixed (the emoji survives `sanitize`). */
154
+ function statusCell(value, color) {
155
+ return `${statusDot(value, color)} ${value ?? ''}`;
156
+ }
157
+ /** A section heading: optional emoji + title, underlined with dashes sized to the title. */
158
+ function section(title, icon) {
159
+ return `\n${icon ? `${icon} ` : ''}${title}\n${'-'.repeat(title.length)}`;
160
+ }
161
+ // Record keys whose value is a status/level/phase โ†’ get a dot. Keys with a `<key>_color` sibling
162
+ // (ticket status/priority) dot by colour; the enum-string keys below dot by the string map.
163
+ const ENUM_STATUS_KEYS = new Set(['status', 'current_status', 'last_run_status', 'level', 'phase']);
164
+ // Metadata keys used only to pick a dot colour โ€” never printed as their own line.
165
+ const HIDDEN_KEYS = new Set(['status_color', 'priority_color']);
166
+ /** The dot prefix (with trailing space) for a record line, or '' when the key isn't status-like. */
167
+ function recordDot(key, value, obj) {
168
+ const color = obj[`${key}_color`];
169
+ if (typeof color === 'string' && color)
170
+ return `${statusDot(value, color)} `;
171
+ if (ENUM_STATUS_KEYS.has(key))
172
+ return `${statusDot(value)} `;
173
+ return '';
174
+ }
175
+ /** Pretty-print the scalar fields of a record as `key: value` lines (status keys get a dot). */
45
176
  function renderRecord(obj) {
46
177
  const lines = Object.entries(obj)
47
- .filter(([, value]) => value === null || ['string', 'number', 'boolean'].includes(typeof value))
48
- .map(([key, value]) => `${sanitize(key)}: ${value === null ? '-' : sanitize(value)}`);
178
+ .filter(([key, value]) => !HIDDEN_KEYS.has(key) && (value === null || ['string', 'number', 'boolean'].includes(typeof value)))
179
+ .map(([key, value]) => {
180
+ if (value === null)
181
+ return `${sanitize(key)}: -`;
182
+ return `${sanitize(key)}: ${recordDot(key, value, obj)}${sanitize(value)}`;
183
+ });
49
184
  return lines.length > 0 ? lines.join('\n') : JSON.stringify(obj, null, 2);
50
185
  }