@bussolabs/closeyourit-cli 0.22.0 → 0.24.1

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.
Files changed (126) hide show
  1. package/README.md +44 -3
  2. package/dist/base.d.ts +23 -0
  3. package/dist/base.js +64 -4
  4. package/dist/commands/assistant/ask.d.ts +24 -0
  5. package/dist/commands/assistant/ask.js +112 -0
  6. package/dist/commands/assistant/conversations/create.d.ts +9 -0
  7. package/dist/commands/assistant/conversations/create.js +25 -0
  8. package/dist/commands/assistant/conversations/delete.d.ts +12 -0
  9. package/dist/commands/assistant/conversations/delete.js +25 -0
  10. package/dist/commands/assistant/conversations/list.d.ts +10 -0
  11. package/dist/commands/assistant/conversations/list.js +30 -0
  12. package/dist/commands/assistant/conversations/show.d.ts +9 -0
  13. package/dist/commands/assistant/conversations/show.js +25 -0
  14. package/dist/commands/coverage/show.d.ts +10 -0
  15. package/dist/commands/coverage/show.js +39 -0
  16. package/dist/commands/errors/assign.d.ts +14 -0
  17. package/dist/commands/errors/assign.js +52 -0
  18. package/dist/commands/errors/bulk-triage.d.ts +11 -0
  19. package/dist/commands/errors/bulk-triage.js +41 -0
  20. package/dist/commands/errors/delete.d.ts +13 -0
  21. package/dist/commands/errors/delete.js +28 -0
  22. package/dist/commands/errors/grouping-rules/create.d.ts +15 -0
  23. package/dist/commands/errors/grouping-rules/create.js +44 -0
  24. package/dist/commands/errors/grouping-rules/delete.d.ts +13 -0
  25. package/dist/commands/errors/grouping-rules/delete.js +27 -0
  26. package/dist/commands/errors/grouping-rules/list.d.ts +10 -0
  27. package/dist/commands/errors/grouping-rules/list.js +28 -0
  28. package/dist/commands/errors/grouping-rules/update.d.ts +18 -0
  29. package/dist/commands/errors/grouping-rules/update.js +43 -0
  30. package/dist/commands/errors/merge.d.ts +14 -0
  31. package/dist/commands/errors/merge.js +39 -0
  32. package/dist/commands/errors/split.d.ts +13 -0
  33. package/dist/commands/errors/split.js +34 -0
  34. package/dist/commands/invitations/create.js +9 -2
  35. package/dist/commands/invitations/resend.js +8 -2
  36. package/dist/commands/kb/attach.d.ts +12 -0
  37. package/dist/commands/kb/attach.js +49 -0
  38. package/dist/commands/kb/attachment-download.d.ts +14 -0
  39. package/dist/commands/kb/attachment-download.js +38 -0
  40. package/dist/commands/kb/attachments.d.ts +9 -0
  41. package/dist/commands/kb/attachments.js +36 -0
  42. package/dist/commands/kb/create.d.ts +4 -1
  43. package/dist/commands/kb/create.js +41 -5
  44. package/dist/commands/kb/detach.d.ts +13 -0
  45. package/dist/commands/kb/detach.js +26 -0
  46. package/dist/commands/kb/show.js +3 -0
  47. package/dist/commands/kb/update.d.ts +4 -0
  48. package/dist/commands/kb/update.js +69 -21
  49. package/dist/commands/metrics/bulk-triage.d.ts +11 -0
  50. package/dist/commands/metrics/bulk-triage.js +44 -0
  51. package/dist/commands/service-accounts/show.d.ts +9 -0
  52. package/dist/commands/service-accounts/show.js +26 -0
  53. package/dist/commands/service-accounts/tokens/list.d.ts +12 -0
  54. package/dist/commands/service-accounts/tokens/list.js +40 -0
  55. package/dist/commands/service-accounts/update.d.ts +31 -0
  56. package/dist/commands/service-accounts/update.js +105 -0
  57. package/dist/commands/shared/delete.d.ts +12 -0
  58. package/dist/commands/shared/delete.js +30 -0
  59. package/dist/commands/shared/list.d.ts +6 -0
  60. package/dist/commands/shared/list.js +45 -0
  61. package/dist/commands/shared/set.d.ts +14 -0
  62. package/dist/commands/shared/set.js +41 -0
  63. package/dist/commands/tickets/ask.d.ts +13 -0
  64. package/dist/commands/tickets/ask.js +81 -0
  65. package/dist/commands/tickets/clarifications.d.ts +21 -0
  66. package/dist/commands/tickets/clarifications.js +36 -0
  67. package/dist/commands/tickets/dependencies/add.d.ts +15 -0
  68. package/dist/commands/tickets/dependencies/add.js +62 -0
  69. package/dist/commands/tickets/dependencies/list.d.ts +12 -0
  70. package/dist/commands/tickets/dependencies/list.js +37 -0
  71. package/dist/commands/tickets/dependencies/remove.d.ts +14 -0
  72. package/dist/commands/tickets/dependencies/remove.js +59 -0
  73. package/dist/commands/tickets/work-context.d.ts +15 -0
  74. package/dist/commands/tickets/work-context.js +130 -0
  75. package/dist/commands/usage/list.d.ts +12 -0
  76. package/dist/commands/usage/list.js +43 -0
  77. package/dist/commands/usage/reporters.d.ts +10 -0
  78. package/dist/commands/usage/reporters.js +37 -0
  79. package/dist/commands/vault/requests/approve.d.ts +9 -0
  80. package/dist/commands/vault/requests/approve.js +40 -0
  81. package/dist/commands/vault/requests/list.d.ts +6 -0
  82. package/dist/commands/vault/requests/list.js +29 -0
  83. package/dist/commands/vault/requests/reject.d.ts +12 -0
  84. package/dist/commands/vault/requests/reject.js +52 -0
  85. package/dist/commands/workload/create.d.ts +15 -0
  86. package/dist/commands/workload/create.js +45 -0
  87. package/dist/commands/workload/delete.d.ts +12 -0
  88. package/dist/commands/workload/delete.js +26 -0
  89. package/dist/commands/workload/list.d.ts +9 -0
  90. package/dist/commands/workload/list.js +29 -0
  91. package/dist/commands/workload/participant/add.d.ts +10 -0
  92. package/dist/commands/workload/participant/add.js +27 -0
  93. package/dist/commands/workload/participant/remove.d.ts +10 -0
  94. package/dist/commands/workload/participant/remove.js +23 -0
  95. package/dist/commands/workload/promote.d.ts +17 -0
  96. package/dist/commands/workload/promote.js +58 -0
  97. package/dist/commands/workload/show.d.ts +9 -0
  98. package/dist/commands/workload/show.js +24 -0
  99. package/dist/commands/workload/update.d.ts +17 -0
  100. package/dist/commands/workload/update.js +45 -0
  101. package/dist/errors/error-codes.d.ts +17 -0
  102. package/dist/errors/error-codes.js +36 -0
  103. package/dist/lib/api.d.ts +7 -1
  104. package/dist/lib/api.js +8 -0
  105. package/dist/lib/assistant.d.ts +29 -0
  106. package/dist/lib/assistant.js +76 -0
  107. package/dist/lib/bulk-triage.d.ts +35 -0
  108. package/dist/lib/bulk-triage.js +55 -0
  109. package/dist/lib/grouping-rules.d.ts +28 -0
  110. package/dist/lib/grouping-rules.js +39 -0
  111. package/dist/lib/knowledge.d.ts +15 -0
  112. package/dist/lib/knowledge.js +36 -0
  113. package/dist/lib/output.js +10 -0
  114. package/dist/lib/poll.d.ts +51 -0
  115. package/dist/lib/poll.js +59 -0
  116. package/dist/lib/service-accounts.d.ts +65 -0
  117. package/dist/lib/service-accounts.js +132 -0
  118. package/dist/lib/ticket-clarifications.d.ts +13 -0
  119. package/dist/lib/ticket-clarifications.js +31 -0
  120. package/dist/lib/vault-requests.d.ts +38 -0
  121. package/dist/lib/vault-requests.js +125 -0
  122. package/dist/lib/workload.d.ts +37 -0
  123. package/dist/lib/workload.js +64 -0
  124. package/oclif.manifest.json +7495 -4994
  125. package/opencli.json +7424 -5644
  126. package/package.json +27 -3
@@ -0,0 +1,58 @@
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 limits_1 = require("../../lib/limits");
6
+ const output_1 = require("../../lib/output");
7
+ const ticket_lookup_1 = require("../../lib/ticket-lookup");
8
+ const workload_1 = require("../../lib/workload");
9
+ class WorkloadPromote extends base_1.BaseCommand {
10
+ static args = {
11
+ action: core_1.Args.string({ description: 'Workload action id', required: true }),
12
+ };
13
+ static description = 'Generate a ticket from a workload action and link it back (the action keeps existing; an already linked action is refused)';
14
+ static examples = [
15
+ '<%= config.bin %> workload promote <action-id> --project acme-api',
16
+ '<%= config.bin %> workload promote <action-id> -p acme-api --title "Implement the partner API" --description "Agreed in the meeting" --kind story --priority High',
17
+ ];
18
+ static flags = {
19
+ ...base_1.projectFlag,
20
+ title: core_1.Flags.string({
21
+ description: `Ticket title (default: the action title, max ${limits_1.LENGTH_LIMITS.title} characters)`,
22
+ }),
23
+ description: core_1.Flags.string({ description: `Ticket description (max ${limits_1.LENGTH_LIMITS.description} characters)` }),
24
+ kind: core_1.Flags.string({ description: 'Ticket kind (default: task)', options: ['bug', 'story', 'task', 'epic'] }),
25
+ ...ticket_lookup_1.newTicketLookupFlags,
26
+ };
27
+ async run() {
28
+ const { args, flags } = await this.parse(WorkloadPromote);
29
+ // Il ticket nasce qui: valgono i tetti di `tickets create`, e si leggono tutti in un rifiuto
30
+ // solo, prima di qualunque chiamata (CYCL-32).
31
+ const tooLong = (0, limits_1.tooLongMessage)([
32
+ { flag: '--title', value: flags.title, max: limits_1.LENGTH_LIMITS.title, orthography: true },
33
+ { flag: '--description', value: flags.description, max: limits_1.LENGTH_LIMITS.description, orthography: true },
34
+ ]);
35
+ if (tooLong)
36
+ this.error(tooLong, { exit: 2 });
37
+ const projectId = await this.resolveProjectId(flags.project);
38
+ const body = { project_id: projectId };
39
+ if (flags.title !== undefined)
40
+ body.title = flags.title;
41
+ if (flags.description !== undefined)
42
+ body.description = flags.description;
43
+ if (flags.kind !== undefined)
44
+ body.kind = flags.kind;
45
+ if (flags.status !== undefined)
46
+ body.status_id = await this.resolveTicketLookupId(ticket_lookup_1.TICKET_STATUS, flags.status);
47
+ if (flags.priority !== undefined) {
48
+ body.priority_id = await this.resolveTicketLookupId(ticket_lookup_1.TICKET_PRIORITY, flags.priority);
49
+ }
50
+ const res = await this.api.post(`${workload_1.WORKLOAD_ACTIONS_PATH}/${encodeURIComponent(args.action)}/promotion`, body);
51
+ if (!this.jsonEnabled()) {
52
+ this.log('Ticket created from the workload action:');
53
+ this.log((0, output_1.renderRecord)(res.data ?? {}));
54
+ }
55
+ return res;
56
+ }
57
+ }
58
+ exports.default = WorkloadPromote;
@@ -0,0 +1,9 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class WorkloadShow extends BaseCommand {
3
+ static args: {
4
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ run(): Promise<unknown>;
9
+ }
@@ -0,0 +1,24 @@
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
+ const workload_1 = require("../../lib/workload");
7
+ class WorkloadShow extends base_1.BaseCommand {
8
+ static args = {
9
+ id: core_1.Args.string({ description: 'Workload action id', required: true }),
10
+ };
11
+ static description = 'Show one workload action (an action of another team answers 404, never 403)';
12
+ static examples = ['<%= config.bin %> workload show <action-id>'];
13
+ async run() {
14
+ const { args } = await this.parse(WorkloadShow);
15
+ const res = await this.api.get(`${workload_1.WORKLOAD_ACTIONS_PATH}/${encodeURIComponent(args.id)}`);
16
+ if (!this.jsonEnabled()) {
17
+ const data = res.data ?? {};
18
+ this.log((0, output_1.renderRecord)(data));
19
+ this.log((0, workload_1.participantsLine)(data));
20
+ }
21
+ return res;
22
+ }
23
+ }
24
+ exports.default = WorkloadShow;
@@ -0,0 +1,17 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class WorkloadUpdate extends BaseCommand {
3
+ static args: {
4
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ description: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ status: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ 'scheduled-at': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
+ 'due-at': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
+ ticket: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
14
+ title: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
15
+ };
16
+ run(): Promise<unknown>;
17
+ }
@@ -0,0 +1,45 @@
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
+ const workload_1 = require("../../lib/workload");
7
+ class WorkloadUpdate extends base_1.BaseCommand {
8
+ static args = {
9
+ id: core_1.Args.string({ description: 'Workload action id', required: true }),
10
+ };
11
+ static description = 'Update a workload action (only the passed fields change). The team never changes: moving an action would change who can see it.';
12
+ static examples = [
13
+ '<%= config.bin %> workload update <action-id> --status done',
14
+ '<%= config.bin %> workload update <action-id> --title "Fiera 2027" --due-at 2027-09-10T18:00:00Z',
15
+ '<%= config.bin %> workload update <action-id> --ticket ""',
16
+ ];
17
+ static flags = {
18
+ title: core_1.Flags.string({ description: 'New title' }),
19
+ ...workload_1.actionFieldFlags,
20
+ };
21
+ async run() {
22
+ const { args, flags } = await this.parse(WorkloadUpdate);
23
+ const body = {};
24
+ if (flags.title !== undefined)
25
+ body.title = flags.title;
26
+ (0, workload_1.applyActionBody)(body, flags);
27
+ const ticketId = flags.ticket ? await this.resolveTicketId(flags.ticket) : undefined;
28
+ if (flags.ticket !== undefined)
29
+ body.ticket_id = ticketId ?? null;
30
+ const res = await this.api.put(`${workload_1.WORKLOAD_ACTIONS_PATH}/${encodeURIComponent(args.id)}`, body);
31
+ const data = res.data ?? {};
32
+ // Su una modifica il rifiuto silenzioso del server non lascia le cose come stavano: azzera il
33
+ // campo, quindi porta via anche il ticket che era collegato prima. Dirlo per intero.
34
+ if ((0, workload_1.ticketLinkMissing)(flags.ticket, ticketId, data)) {
35
+ this.warn(`Ticket ${flags.ticket} is not visible to you: the action now has no linked ticket at all, not even the one it had before.`);
36
+ }
37
+ if (!this.jsonEnabled()) {
38
+ this.log('Workload action updated:');
39
+ this.log((0, output_1.renderRecord)(data));
40
+ this.log((0, workload_1.participantsLine)(data));
41
+ }
42
+ return res;
43
+ }
44
+ }
45
+ exports.default = WorkloadUpdate;
@@ -22,9 +22,22 @@ export declare const ErrorCodes: {
22
22
  readonly Team: {
23
23
  readonly notFound: "C404-TEM-001";
24
24
  };
25
+ readonly ErrorGroup: {
26
+ readonly notFound: "C404-ERG-001";
27
+ };
28
+ readonly MetricGroup: {
29
+ readonly notFound: "C404-MTG-001";
30
+ };
25
31
  readonly Member: {
26
32
  readonly notFound: "C404-MBR-001";
27
33
  };
34
+ readonly ServiceAccount: {
35
+ readonly boundaryMismatch: "C404-SVA-001";
36
+ readonly restrictionNotApplied: "C500-SVA-002";
37
+ };
38
+ readonly Ticket: {
39
+ readonly notFound: "C404-TKT-001";
40
+ };
28
41
  readonly Knowledge: {
29
42
  readonly publishUnavailable: "C404-KNW-001";
30
43
  };
@@ -38,6 +51,10 @@ export declare const ErrorCodes: {
38
51
  readonly expired: "C408-DEV-001";
39
52
  readonly failed: "C400-DEV-002";
40
53
  };
54
+ readonly Ask: {
55
+ readonly timeout: "C408-ASK-001";
56
+ readonly failed: "C502-ASK-002";
57
+ };
41
58
  readonly Input: {
42
59
  readonly invalid: "C400-INP-001";
43
60
  };
@@ -27,9 +27,37 @@ exports.ErrorCodes = {
27
27
  Team: {
28
28
  notFound: 'C404-TEM-001',
29
29
  },
30
+ // Gruppi di monitoring chiesti a un'operazione in massa e non ritrovati nella risposta. Il server
31
+ // filtra la selezione sul progetto e risponde 200 su ciò che resta (`scope.where(id:)`), quindi
32
+ // un id di un altro progetto — o di un dato ormai vecchio — sparisce senza una parola: chi ha
33
+ // chiesto di chiudere trenta gruppi ne vedrebbe chiusi ventotto e nessuno glielo direbbe (CYCL-44).
34
+ ErrorGroup: {
35
+ notFound: 'C404-ERG-001',
36
+ },
37
+ MetricGroup: {
38
+ notFound: 'C404-MTG-001',
39
+ },
30
40
  Member: {
31
41
  notFound: 'C404-MBR-001',
32
42
  },
43
+ // Il confine ambienti di un account di servizio, chiesto e non applicato (CYCL-46). Il server
44
+ // interseca i code ricevuti con quelli dell'organizzazione e risponde 200 su ciò che resta: un
45
+ // refuso non restringe niente, CANCELLA il confine — e una allow-list vuota vuol dire «ogni
46
+ // ambiente», cioè l'opposto di quello che si stava chiedendo, senza una parola. Il comando
47
+ // controlla i code prima di scrivere, ma leggere gli ambienti chiede un permesso che può mancare:
48
+ // il confronto fra chiesto e applicato resta la rete sotto, e vale in entrambi i versi (un code
49
+ // sparito, o uno comparso che nessuno aveva chiesto).
50
+ ServiceAccount: {
51
+ boundaryMismatch: 'C404-SVA-001',
52
+ restrictionNotApplied: 'C500-SVA-002',
53
+ },
54
+ // Un ticket indicato per collegarlo altrove (una attività del carico di lavoro) e non trovato: il
55
+ // riferimento non è né un id né un code leggibile ("DRFL-3"), oppure quel code non esiste tra i
56
+ // ticket visibili. Il server, ricevendo un id che non si può vedere, salverebbe «nessun ticket»
57
+ // senza dire niente (CYCL-42).
58
+ Ticket: {
59
+ notFound: 'C404-TKT-001',
60
+ },
33
61
  // Il backend risponde 404 senza codice applicativo sulla route della pubblicazione atomica:
34
62
  // non è il progetto a mancare, è il server a essere più vecchio del comando (CYCL-8).
35
63
  Knowledge: {
@@ -47,6 +75,14 @@ exports.ErrorCodes = {
47
75
  expired: 'C408-DEV-001',
48
76
  failed: 'C400-DEV-002',
49
77
  },
78
+ // Attesa di una risposta preparata altrove (l'assistente, «chiedi ai ticket»). `timeout` non è un
79
+ // guasto del server: la domanda è partita e la risposta sta ancora arrivando, è il comando che ha
80
+ // smesso di aspettare — il messaggio dice dove ritrovarla. `failed` copre il caso in cui il
81
+ // server dichiara fallita la risposta senza allegare un codice proprio (CYCL-43).
82
+ Ask: {
83
+ timeout: 'C408-ASK-001',
84
+ failed: 'C502-ASK-002',
85
+ },
50
86
  // The command line itself was rejected (missing/unknown flag or arg, value outside the allowed
51
87
  // options, a `this.error(...)` guard): the message says what to fix, and it never reached the
52
88
  // server — the opposite of System.unexpected, which stays for real failures (CYCL-33).
package/dist/lib/api.d.ts CHANGED
@@ -22,7 +22,7 @@ export interface UploadFile {
22
22
  filename: string;
23
23
  data: Blob;
24
24
  }
25
- type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
25
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
26
26
  export declare class CliApi {
27
27
  private readonly config;
28
28
  /**
@@ -47,6 +47,12 @@ export declare class CliApi {
47
47
  get<T = unknown>(path: string, opts?: RequestOptions): Promise<Envelope<T>>;
48
48
  post<T = unknown>(path: string, body?: unknown, opts?: RequestOptions): Promise<Envelope<T>>;
49
49
  put<T = unknown>(path: string, body?: unknown, opts?: RequestOptions): Promise<Envelope<T>>;
50
+ /**
51
+ * PATCH. Rails routes a resourceful `update` under both PUT and PATCH, so most commands here use
52
+ * `put`; a custom member route declared with `patch` (error group assignment, CYCL-44) answers to
53
+ * PATCH alone, and sending PUT to it is a plain 404.
54
+ */
55
+ patch<T = unknown>(path: string, body?: unknown, opts?: RequestOptions): Promise<Envelope<T>>;
50
56
  delete<T = unknown>(path: string, opts?: RequestOptions): Promise<Envelope<T>>;
51
57
  /**
52
58
  * Download a binary payload (e.g. a ticket attachment). The endpoint redirects to a signed
package/dist/lib/api.js CHANGED
@@ -117,6 +117,14 @@ class CliApi {
117
117
  put(path, body, opts) {
118
118
  return this.request('PUT', path, { ...opts, body });
119
119
  }
120
+ /**
121
+ * PATCH. Rails routes a resourceful `update` under both PUT and PATCH, so most commands here use
122
+ * `put`; a custom member route declared with `patch` (error group assignment, CYCL-44) answers to
123
+ * PATCH alone, and sending PUT to it is a plain 404.
124
+ */
125
+ patch(path, body, opts) {
126
+ return this.request('PATCH', path, { ...opts, body });
127
+ }
120
128
  delete(path, opts) {
121
129
  return this.request('DELETE', path, opts);
122
130
  }
@@ -0,0 +1,29 @@
1
+ /** A message of an assistant conversation, as the backend serializes it (AssistantMessageSerializer). */
2
+ export interface AssistantMessage extends Record<string, unknown> {
3
+ id?: string;
4
+ role?: string;
5
+ /** `streaming` = still being written; `complete`/`failed` = final. */
6
+ status?: string;
7
+ content?: string | null;
8
+ tools_used?: unknown;
9
+ error_code?: string | null;
10
+ created_at?: string;
11
+ }
12
+ /** `{code, message}` for a failed reply: the server code when there is one, ours when there is not. */
13
+ export declare function assistantFailure(errorCode?: string | null): {
14
+ code: string;
15
+ message: string;
16
+ };
17
+ /**
18
+ * The `🧰 Tools used:` line, or '' when the reply used none. What the assistant read is what makes
19
+ * the answer checkable, so it is printed next to the answer and not hidden behind `--json`.
20
+ */
21
+ export declare function renderToolsUsed(tools: unknown, prefix?: string): string;
22
+ /** Every message of a conversation, oldest first (the order the backend serializes them in). */
23
+ export declare function renderMessages(messages: unknown): string;
24
+ /**
25
+ * The conversation list. ID first: it is the argument of `assistant conversations show|delete` and
26
+ * the value of `assistant ask --conversation`, so a table without it would force `--json` just to
27
+ * carry on the conversation you are looking at.
28
+ */
29
+ export declare function renderConversationsTable(rows: Array<Record<string, unknown>>): string;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assistantFailure = assistantFailure;
4
+ exports.renderToolsUsed = renderToolsUsed;
5
+ exports.renderMessages = renderMessages;
6
+ exports.renderConversationsTable = renderConversationsTable;
7
+ const error_codes_1 = require("../errors/error-codes");
8
+ const output_1 = require("./output");
9
+ /**
10
+ * Why a reply failed, said in a way that tells the reader what to do next.
11
+ *
12
+ * The backend ships only the code (`Assistant::Message#error_kind` does the same reading on the web
13
+ * side), and the three cases ask for three different reactions: nothing will change until someone
14
+ * connects the AI service, or the question itself must be rewritten, or it is worth trying again in
15
+ * a minute. Collapsing them into one "the assistant failed" would send people to retry forever
16
+ * something that cannot succeed.
17
+ */
18
+ const FAILURE_MESSAGES = {
19
+ // Integrations::Providers::NOT_CONNECTED_CODE
20
+ 'R503-INTEGRATION-006': 'The AI service is not connected for your organization: connect it from the web app, then ask again.',
21
+ // Assistant::Message::NO_ANSWER_ERROR_CODES
22
+ 'R502-GEMINI-004': 'The assistant did not produce an answer for this question — try rephrasing it.',
23
+ };
24
+ const FAILURE_FALLBACK = 'The assistant could not answer (temporary problem upstream); try again in a moment.';
25
+ /** `{code, message}` for a failed reply: the server code when there is one, ours when there is not. */
26
+ function assistantFailure(errorCode) {
27
+ const code = errorCode ?? undefined;
28
+ return {
29
+ code: code ?? error_codes_1.ErrorCodes.Ask.failed,
30
+ message: (code ? FAILURE_MESSAGES[code] : undefined) ?? FAILURE_FALLBACK,
31
+ };
32
+ }
33
+ /**
34
+ * The `🧰 Tools used:` line, or '' when the reply used none. What the assistant read is what makes
35
+ * the answer checkable, so it is printed next to the answer and not hidden behind `--json`.
36
+ */
37
+ function renderToolsUsed(tools, prefix = '') {
38
+ const names = Array.isArray(tools) ? tools.map((tool) => (0, output_1.sanitize)(tool)).filter((tool) => tool.trim() !== '') : [];
39
+ return names.length === 0 ? '' : `${prefix}🧰 Tools used: ${names.join(', ')}`;
40
+ }
41
+ /** One message: who spoke and when, the text, the reason when it failed, the tools it used. */
42
+ function renderMessage(message) {
43
+ const when = (0, output_1.sanitize)(message.created_at ?? '');
44
+ const lines = [`${(0, output_1.statusDot)(message.status)} [${(0, output_1.sanitize)(message.role ?? '-')}] ${when}`.trimEnd()];
45
+ const content = String(message.content ?? '').trim();
46
+ if (content !== '')
47
+ lines.push((0, output_1.sanitizeMultiline)(message.content));
48
+ // A failed reply carries no text at all: without this line the conversation would show an empty
49
+ // bubble and no hint of what went wrong.
50
+ if (message.status === 'failed') {
51
+ const failure = assistantFailure(message.error_code);
52
+ lines.push(`⚠️ ${failure.code} — ${failure.message}`);
53
+ }
54
+ const tools = renderToolsUsed(message.tools_used);
55
+ if (tools !== '')
56
+ lines.push(tools);
57
+ return lines.join('\n');
58
+ }
59
+ /** Every message of a conversation, oldest first (the order the backend serializes them in). */
60
+ function renderMessages(messages) {
61
+ const rows = Array.isArray(messages) ? messages : [];
62
+ return rows.length === 0 ? '(no messages)' : rows.map((message) => renderMessage(message)).join('\n\n');
63
+ }
64
+ /**
65
+ * The conversation list. ID first: it is the argument of `assistant conversations show|delete` and
66
+ * the value of `assistant ask --conversation`, so a table without it would force `--json` just to
67
+ * carry on the conversation you are looking at.
68
+ */
69
+ function renderConversationsTable(rows) {
70
+ return (0, output_1.renderTable)(['ID', 'TITLE', 'LAST MESSAGE', 'CREATED'], rows.map((row) => [
71
+ String(row.id ?? ''),
72
+ String(row.title ?? ''),
73
+ String(row.last_message_at ?? ''),
74
+ String(row.created_at ?? ''),
75
+ ]));
76
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Bulk triage of monitoring groups (errors, metrics) — CYCL-44.
3
+ *
4
+ * The two endpoints are the same shape (`POST .../bulk_triage` with `ids[]` + `bulk_action`) and
5
+ * share the same blind spot, so the guard lives here once instead of in both commands.
6
+ */
7
+ /** The three actions the backend accepts, for errors and metrics alike (`Observability::Triage::ACTIONS`). */
8
+ export declare const TRIAGE_ACTIONS: readonly ["resolve", "ignore", "reopen"];
9
+ /**
10
+ * The ids to send: the order they were written in, each one once.
11
+ *
12
+ * The server matches with `where(id: ids)`, which never returns the same row twice — so a repeated
13
+ * `--group` would come back as one row and the check below would read it as "one went missing".
14
+ * Deduplicating is safe here precisely because this endpoint is not all-or-nothing: unlike merge,
15
+ * where the raw list is the evidence the server needs to refuse the whole operation.
16
+ */
17
+ export declare function uniqueIds(values: string[]): string[];
18
+ /**
19
+ * Refuse a partial triage the caller cannot see.
20
+ *
21
+ * `bulk_triage` scopes the selection to the project and answers 200 on whatever is left, so an id
22
+ * belonging to another project (or to a page refreshed on stale data) is dropped without a word.
23
+ * A script would go on believing it closed thirty groups after closing twenty-eight. Everything the
24
+ * server did touch stays done — the triage is reversible, and undoing the good part would be worse
25
+ * — but the command says so and exits non-zero.
26
+ */
27
+ export declare function assertNothingDropped(opts: {
28
+ action: string;
29
+ code: string;
30
+ noun: string;
31
+ requested: string[];
32
+ rows: Array<Record<string, unknown>>;
33
+ }): void;
34
+ /** `1 error group` / `3 error groups` — the count line both commands print. */
35
+ export declare function triagedLine(count: number, noun: string, action: string): string;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TRIAGE_ACTIONS = void 0;
4
+ exports.uniqueIds = uniqueIds;
5
+ exports.assertNothingDropped = assertNothingDropped;
6
+ exports.triagedLine = triagedLine;
7
+ const api_1 = require("./api");
8
+ /**
9
+ * Bulk triage of monitoring groups (errors, metrics) — CYCL-44.
10
+ *
11
+ * The two endpoints are the same shape (`POST .../bulk_triage` with `ids[]` + `bulk_action`) and
12
+ * share the same blind spot, so the guard lives here once instead of in both commands.
13
+ */
14
+ /** The three actions the backend accepts, for errors and metrics alike (`Observability::Triage::ACTIONS`). */
15
+ exports.TRIAGE_ACTIONS = ['resolve', 'ignore', 'reopen'];
16
+ /**
17
+ * The ids to send: the order they were written in, each one once.
18
+ *
19
+ * The server matches with `where(id: ids)`, which never returns the same row twice — so a repeated
20
+ * `--group` would come back as one row and the check below would read it as "one went missing".
21
+ * Deduplicating is safe here precisely because this endpoint is not all-or-nothing: unlike merge,
22
+ * where the raw list is the evidence the server needs to refuse the whole operation.
23
+ */
24
+ function uniqueIds(values) {
25
+ const seen = new Set();
26
+ return values.filter((value) => {
27
+ const key = value.toLowerCase();
28
+ if (seen.has(key))
29
+ return false;
30
+ seen.add(key);
31
+ return true;
32
+ });
33
+ }
34
+ /**
35
+ * Refuse a partial triage the caller cannot see.
36
+ *
37
+ * `bulk_triage` scopes the selection to the project and answers 200 on whatever is left, so an id
38
+ * belonging to another project (or to a page refreshed on stale data) is dropped without a word.
39
+ * A script would go on believing it closed thirty groups after closing twenty-eight. Everything the
40
+ * server did touch stays done — the triage is reversible, and undoing the good part would be worse
41
+ * — but the command says so and exits non-zero.
42
+ */
43
+ function assertNothingDropped(opts) {
44
+ const returned = new Set(opts.rows.map((row) => String(row.id ?? '').toLowerCase()));
45
+ const missing = opts.requested.filter((id) => !returned.has(id.toLowerCase()));
46
+ if (missing.length === 0)
47
+ return;
48
+ throw new api_1.ApiRequestError(404, opts.code, `Triaged ${opts.rows.length} of ${opts.requested.length} ${opts.noun}s (${opts.action}). ` +
49
+ `Not found in this project: ${missing.join(', ')} — those were left untouched, ` +
50
+ `the other ${opts.rows.length} were changed.`);
51
+ }
52
+ /** `1 error group` / `3 error groups` — the count line both commands print. */
53
+ function triagedLine(count, noun, action) {
54
+ return `Triaged ${count} ${noun}${count === 1 ? '' : 's'} (${action}).`;
55
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Custom error grouping rules (CYCL-44, server side CYRA-153).
3
+ *
4
+ * At ingest time the first active rule, in `position` order, whose match succeeds rewrites the
5
+ * occurrence's fingerprint: everything hitting rules with the same `fingerprint_key` lands in one
6
+ * group. This is how a team bends the automatic grouping.
7
+ */
8
+ /** Occurrence field a rule looks at (`Errors::GroupingRule.fields`). */
9
+ export declare const GROUPING_RULE_FIELDS: readonly ["exception_type", "message", "culprit", "transaction"];
10
+ /**
11
+ * How the field is compared (`Errors::GroupingRule.operators`). Always case-insensitive and never a
12
+ * regular expression: on the ingest hot path a user-supplied pattern would be an open door to ReDoS.
13
+ */
14
+ export declare const GROUPING_RULE_OPERATORS: readonly ["contains", "equals", "starts_with"];
15
+ export interface GroupingRuleFlags {
16
+ active?: boolean;
17
+ field?: string;
18
+ 'fingerprint-key'?: string;
19
+ operator?: string;
20
+ position?: number;
21
+ value?: string;
22
+ }
23
+ /**
24
+ * The rule attributes to send, skipping every flag left out. `create` fills the required ones by
25
+ * declaring them required; `update` sends only what it was told to change, so an omitted flag keeps
26
+ * the value the rule already has instead of blanking it.
27
+ */
28
+ export declare function groupingRuleBody(flags: GroupingRuleFlags): Record<string, unknown>;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ /**
3
+ * Custom error grouping rules (CYCL-44, server side CYRA-153).
4
+ *
5
+ * At ingest time the first active rule, in `position` order, whose match succeeds rewrites the
6
+ * occurrence's fingerprint: everything hitting rules with the same `fingerprint_key` lands in one
7
+ * group. This is how a team bends the automatic grouping.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.GROUPING_RULE_OPERATORS = exports.GROUPING_RULE_FIELDS = void 0;
11
+ exports.groupingRuleBody = groupingRuleBody;
12
+ /** Occurrence field a rule looks at (`Errors::GroupingRule.fields`). */
13
+ exports.GROUPING_RULE_FIELDS = ['exception_type', 'message', 'culprit', 'transaction'];
14
+ /**
15
+ * How the field is compared (`Errors::GroupingRule.operators`). Always case-insensitive and never a
16
+ * regular expression: on the ingest hot path a user-supplied pattern would be an open door to ReDoS.
17
+ */
18
+ exports.GROUPING_RULE_OPERATORS = ['contains', 'equals', 'starts_with'];
19
+ /**
20
+ * The rule attributes to send, skipping every flag left out. `create` fills the required ones by
21
+ * declaring them required; `update` sends only what it was told to change, so an omitted flag keeps
22
+ * the value the rule already has instead of blanking it.
23
+ */
24
+ function groupingRuleBody(flags) {
25
+ const body = {};
26
+ if (flags.field !== undefined)
27
+ body.field = flags.field;
28
+ if (flags.operator !== undefined)
29
+ body.operator = flags.operator;
30
+ if (flags.value !== undefined)
31
+ body.value = flags.value;
32
+ if (flags['fingerprint-key'] !== undefined)
33
+ body.fingerprint_key = flags['fingerprint-key'];
34
+ if (flags.position !== undefined)
35
+ body.position = flags.position;
36
+ if (flags.active !== undefined)
37
+ body.active = flags.active;
38
+ return body;
39
+ }
@@ -5,6 +5,11 @@ export declare const KNOWLEDGE_KINDS: readonly ["note", "decision", "guide"];
5
5
  * `--kind note --kind decision,guide` → ['note', 'decision', 'guide'].
6
6
  */
7
7
  export declare function normalizeKinds(values?: string[]): string[];
8
+ /**
9
+ * Clean a repeatable flag into a list: trimmed, empties dropped, order preserved.
10
+ * `--tag ""` therefore means "leave nothing" — the deliberate way to clear tags on update.
11
+ */
12
+ export declare function cleanList(values?: string[]): string[];
8
13
  /** Collapse whitespace and truncate a cell so the list table stays single-line. */
9
14
  export declare function truncate(value: string, max?: number): string;
10
15
  /**
@@ -59,6 +64,16 @@ export declare function buildBooksQuery(opts: {
59
64
  project?: string;
60
65
  q?: string;
61
66
  }): string;
67
+ /**
68
+ * The scope lines of a page — every project it belongs to, its groups, its tags — as readable
69
+ * `key: a, b` rows. `renderRecord` drops arrays, so without these a page connected to five projects
70
+ * read as if it had one, and its tags were invisible from the terminal (CYCL-45).
71
+ *
72
+ * `projects` shows up only past the first one: with a single project the `project` line above
73
+ * already says it, and repeating it would be noise on the pages that have always had just one.
74
+ * Groups and tags appear whenever there are any.
75
+ */
76
+ export declare function pageScopeLines(page: Record<string, unknown>): Record<string, string>;
62
77
  /**
63
78
  * Render the `kb book list` table. ID first: it is the argument of `kb book show` and of
64
79
  * `kb book add-page`, so a table without it would force `--json` just to act on what you are
@@ -2,11 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KNOWLEDGE_STATUSES = exports.KNOWLEDGE_KINDS = void 0;
4
4
  exports.normalizeKinds = normalizeKinds;
5
+ exports.cleanList = cleanList;
5
6
  exports.truncate = truncate;
6
7
  exports.buildPagesQuery = buildPagesQuery;
7
8
  exports.renderRelated = renderRelated;
8
9
  exports.renderRelatedGroups = renderRelatedGroups;
9
10
  exports.buildBooksQuery = buildBooksQuery;
11
+ exports.pageScopeLines = pageScopeLines;
10
12
  exports.renderBooksTable = renderBooksTable;
11
13
  exports.renderBook = renderBook;
12
14
  exports.renderPagesTable = renderPagesTable;
@@ -25,6 +27,15 @@ function normalizeKinds(values) {
25
27
  .map((value) => value.trim())
26
28
  .filter((value) => value.length > 0);
27
29
  }
30
+ /**
31
+ * Clean a repeatable flag into a list: trimmed, empties dropped, order preserved.
32
+ * `--tag ""` therefore means "leave nothing" — the deliberate way to clear tags on update.
33
+ */
34
+ function cleanList(values) {
35
+ if (!values)
36
+ return [];
37
+ return values.map((value) => value.trim()).filter((value) => value.length > 0);
38
+ }
28
39
  /** Collapse whitespace and truncate a cell so the list table stays single-line. */
29
40
  function truncate(value, max = 60) {
30
41
  const flat = value.replace(/\s+/g, ' ').trim();
@@ -107,6 +118,31 @@ function joinList(value) {
107
118
  return undefined;
108
119
  return value.map((entry) => String(entry)).join(', ');
109
120
  }
121
+ /**
122
+ * The scope lines of a page — every project it belongs to, its groups, its tags — as readable
123
+ * `key: a, b` rows. `renderRecord` drops arrays, so without these a page connected to five projects
124
+ * read as if it had one, and its tags were invisible from the terminal (CYCL-45).
125
+ *
126
+ * `projects` shows up only past the first one: with a single project the `project` line above
127
+ * already says it, and repeating it would be noise on the pages that have always had just one.
128
+ * Groups and tags appear whenever there are any.
129
+ */
130
+ function pageScopeLines(page) {
131
+ const lines = {};
132
+ const projects = Array.isArray(page.projects) ? page.projects : [];
133
+ if (projects.length > 1) {
134
+ const keys = projects.map((project) => String(project.key ?? '')).filter(Boolean);
135
+ if (keys.length > 0)
136
+ lines.projects = keys.join(', ');
137
+ }
138
+ const groups = joinList(page.groups);
139
+ if (groups)
140
+ lines.groups = groups;
141
+ const tags = joinList(page.tags);
142
+ if (tags)
143
+ lines.tags = tags;
144
+ return lines;
145
+ }
110
146
  /**
111
147
  * Render the `kb book list` table. ID first: it is the argument of `kb book show` and of
112
148
  * `kb book add-page`, so a table without it would force `--json` just to act on what you are