@bussolabs/closeyourit-cli 0.24.2 → 0.25.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.
- package/README.md +2 -0
- package/dist/base.js +44 -1
- package/dist/commands/kb/approve.d.ts +15 -0
- package/dist/commands/kb/approve.js +28 -0
- package/dist/commands/kb/reject.d.ts +14 -0
- package/dist/commands/kb/reject.js +27 -0
- package/dist/commands/tickets/create.js +24 -3
- package/oclif.manifest.json +4060 -3970
- package/opencli.json +43 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,6 +162,8 @@ CLOSEYOURIT_TOKEN=cyi_u_… CLOSEYOURIT_API_URL=https://www.closeyour.it \
|
|
|
162
162
|
| `kb related <id> [--question <q>] [--links-only]` | Pages to read next: linked with `[[wiki links]]` plus close matches by meaning. |
|
|
163
163
|
| `kb create [--project <id\|key>…] [--group <id\|name>…] [--tag <tag>…] --title <t> [--kind note\|decision\|guide] (--body <md> \| --body-file <path>) [--tech-spec <md> \| --tech-spec-file <path>] [--in-review] [--review-note <line>] [--author-origin <name>]` | Create a knowledge page (optional technical section, kept separate from the body). `--project`, `--group` and `--tag` are repeatable — a page can belong to several projects and groups at once, and to groups alone; at least one project or group is required. Length caps: title 255, body 4000, technical section 1500, review note 240 characters — every field over the cap is listed at once, before the call. With `--in-review` the page waits for a human and stays out of search, answers and related panels. `--author-origin` declares the assistant or skill that wrote the text, which is not the owner of the token. |
|
|
164
164
|
| `kb publish --project <id\|key> --publication-key <key> --title <t> [--kind note\|decision\|guide] (--body <md> \| --body-file <path>)` | Create or update a knowledge page in one atomic call, keyed by a stable `--publication-key` (1-255 URL-safe characters). Safe to repeat: the same key always lands on the same page, and the output says whether it was `created` or `updated`. Exactly one body source is required. Omitting `--kind` keeps the kind the page already has (a new page is a `note`). Same title/body caps as `kb create`. |
|
|
165
|
+
| `kb approve <id>` | Accept a page waiting for review: it enters search, answers and related panels. A human decision: a service token is refused, and a page the automatic reviewer rejected must be fixed and saved first. |
|
|
166
|
+
| `kb reject <id>` | Discard a page waiting for review: it stays archived as rejected, out of search and answers, so it is not proposed again. Nothing is deleted. |
|
|
165
167
|
| `kb consolidate <id> --path <doc-path>` | Mark an accepted page as written to the versioned docs, recording where the document lives. |
|
|
166
168
|
| `kb update <id> [--title] [--kind] [--body \| --body-file] [--tech-spec \| --tech-spec-file] [--project <id\|key>…] [--group <id\|name>…] [--tag <tag>…] [--author-origin <name>]` | Update a page (unpassed fields keep their current value — including projects, groups, tags and attachments). Passing `--project`, `--group` or `--tag` **replaces** that list with what you name; `--tag ""` clears the tags. Changing only scope or tags never rewrites the text, so a concurrent edit is not overwritten. Same length caps as `kb create`; text already over the cap is refused only if it grows. |
|
|
167
169
|
| `kb delete <id> --confirm` | Delete a knowledge page (irreversible). |
|
package/dist/base.js
CHANGED
|
@@ -36,6 +36,13 @@ function paginationError(opts) {
|
|
|
36
36
|
return `Invalid --per ${opts.per}: ask for at least 1 row.`;
|
|
37
37
|
return undefined;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* `this.exit(n)` di oclif lancia un ExitError: si riconosce dal codice `EEXIT`, non dalla classe —
|
|
41
|
+
* importarla legherebbe la base a un percorso interno di oclif che cambia fra le versioni.
|
|
42
|
+
*/
|
|
43
|
+
function isExitError(error) {
|
|
44
|
+
return typeof error === 'object' && error !== null && error.code === 'EEXIT';
|
|
45
|
+
}
|
|
39
46
|
class BaseCommand extends core_1.Command {
|
|
40
47
|
static enableJsonFlag = true;
|
|
41
48
|
/**
|
|
@@ -205,12 +212,20 @@ class BaseCommand extends core_1.Command {
|
|
|
205
212
|
* swallowed (empty stdout, exit looks like a silent success) for machine consumers (CYCL-1).
|
|
206
213
|
*/
|
|
207
214
|
async catch(error) {
|
|
215
|
+
// CYCL-52 — `this.exit(n)` è una decisione presa dal comando, non un guasto da raccontare: chi
|
|
216
|
+
// l'ha chiamata ha già detto la sua sull'uscita. Senza questa riga l'ExitError arriva quaggiù e
|
|
217
|
+
// il ramo generico emette un SECONDO envelope, così chi legge in modalità dati si trova due
|
|
218
|
+
// oggetti JSON sulla stessa uscita e non sa quale dei due sia la risposta.
|
|
219
|
+
if (isExitError(error))
|
|
220
|
+
throw error;
|
|
208
221
|
if (error instanceof api_1.ApiRequestError) {
|
|
209
222
|
if (this.jsonEnabled()) {
|
|
210
|
-
this.logJson({ error: { code: error.code, message: error.message } });
|
|
223
|
+
this.logJson({ error: { code: error.code, message: error.message, ...(error.details === undefined ? {} : { details: error.details }) } });
|
|
211
224
|
}
|
|
212
225
|
else {
|
|
213
226
|
this.logToStderr(`${error.code}: ${error.message}`);
|
|
227
|
+
for (const line of reviewVerdictLines(error))
|
|
228
|
+
this.logToStderr(line);
|
|
214
229
|
}
|
|
215
230
|
return this.exit(1);
|
|
216
231
|
}
|
|
@@ -253,3 +268,31 @@ class BaseCommand extends core_1.Command {
|
|
|
253
268
|
}
|
|
254
269
|
}
|
|
255
270
|
exports.BaseCommand = BaseCommand;
|
|
271
|
+
/**
|
|
272
|
+
* The automatic reviewer of knowledge pages (R422-KNOWLEDGE-013, CYRA-764) answers with the whole
|
|
273
|
+
* verdict in `details`: every rule violated, the format it recognised, a suggested title, the page it
|
|
274
|
+
* looks like a duplicate of. The message carries only the first three rules, so a human reading the
|
|
275
|
+
* terminal would otherwise have to open the site to learn what to fix.
|
|
276
|
+
*/
|
|
277
|
+
function reviewVerdictLines(error) {
|
|
278
|
+
if (error.code !== 'R422-KNOWLEDGE-013' || typeof error.details !== 'object' || error.details === null)
|
|
279
|
+
return [];
|
|
280
|
+
const details = error.details;
|
|
281
|
+
const lines = [];
|
|
282
|
+
const violations = Array.isArray(details.violations) ? details.violations : [];
|
|
283
|
+
if (violations.length > 0) {
|
|
284
|
+
lines.push('Rules violated:');
|
|
285
|
+
for (const v of violations)
|
|
286
|
+
lines.push(` ${v.code ?? '?'}${v.blocking === false ? ' (warning)' : ''}: ${v.message ?? ''}`);
|
|
287
|
+
}
|
|
288
|
+
if (details.format && details.format !== 'unknown')
|
|
289
|
+
lines.push(`Format recognised: ${details.format}`);
|
|
290
|
+
if (details.suggested_title)
|
|
291
|
+
lines.push(`Suggested title: ${details.suggested_title}`);
|
|
292
|
+
if (details.duplicate_of)
|
|
293
|
+
lines.push(`Looks like a duplicate of: ${details.duplicate_of}`);
|
|
294
|
+
if (Array.isArray(details.split_suggestion) && details.split_suggestion.length > 0) {
|
|
295
|
+
lines.push(`Split into: ${details.split_suggestion.join(' · ')}`);
|
|
296
|
+
}
|
|
297
|
+
return lines;
|
|
298
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
/**
|
|
3
|
+
* First step of the review flow: a page proposed with `kb create --in-review` enters the knowledge
|
|
4
|
+
* (search, answers, related panels). The server insists the decision comes from a person: a service
|
|
5
|
+
* token gets R403-KNOWLEDGE-005, and a page the automatic reviewer rejected must be fixed and saved
|
|
6
|
+
* first (R422-KNOWLEDGE-009).
|
|
7
|
+
*/
|
|
8
|
+
export default class KbApprove extends BaseCommand {
|
|
9
|
+
static args: {
|
|
10
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
11
|
+
};
|
|
12
|
+
static description: string;
|
|
13
|
+
static examples: string[];
|
|
14
|
+
run(): Promise<unknown>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* First step of the review flow: a page proposed with `kb create --in-review` enters the knowledge
|
|
8
|
+
* (search, answers, related panels). The server insists the decision comes from a person: a service
|
|
9
|
+
* token gets R403-KNOWLEDGE-005, and a page the automatic reviewer rejected must be fixed and saved
|
|
10
|
+
* first (R422-KNOWLEDGE-009).
|
|
11
|
+
*/
|
|
12
|
+
class KbApprove extends base_1.BaseCommand {
|
|
13
|
+
static args = {
|
|
14
|
+
id: core_1.Args.string({ description: 'Knowledge page id', required: true }),
|
|
15
|
+
};
|
|
16
|
+
static description = 'Accept a page waiting for review: it enters search, answers and related panels';
|
|
17
|
+
static examples = ['<%= config.bin %> kb approve <page-id>', '<%= config.bin %> kb approve <page-id> --json'];
|
|
18
|
+
async run() {
|
|
19
|
+
const { args } = await this.parse(KbApprove);
|
|
20
|
+
const res = await this.api.post(`/cli/v1/knowledge/pages/${args.id}/approve`);
|
|
21
|
+
if (!this.jsonEnabled()) {
|
|
22
|
+
this.log('Knowledge page accepted:');
|
|
23
|
+
this.log((0, output_1.renderRecord)(res.data ?? {}));
|
|
24
|
+
}
|
|
25
|
+
return res;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.default = KbApprove;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
/**
|
|
3
|
+
* Mirror of `kb approve`: the proposal stays archived as rejected — out of search, answers, related
|
|
4
|
+
* panels and lists — so whoever proposes can see it when hunting duplicates and not propose it again.
|
|
5
|
+
* Nothing is deleted (that is `kb delete`). A human decision, like approve.
|
|
6
|
+
*/
|
|
7
|
+
export default class KbReject extends BaseCommand {
|
|
8
|
+
static args: {
|
|
9
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
10
|
+
};
|
|
11
|
+
static description: string;
|
|
12
|
+
static examples: string[];
|
|
13
|
+
run(): Promise<unknown>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* Mirror of `kb approve`: the proposal stays archived as rejected — out of search, answers, related
|
|
8
|
+
* panels and lists — so whoever proposes can see it when hunting duplicates and not propose it again.
|
|
9
|
+
* Nothing is deleted (that is `kb delete`). A human decision, like approve.
|
|
10
|
+
*/
|
|
11
|
+
class KbReject extends base_1.BaseCommand {
|
|
12
|
+
static args = {
|
|
13
|
+
id: core_1.Args.string({ description: 'Knowledge page id', required: true }),
|
|
14
|
+
};
|
|
15
|
+
static description = 'Discard a page waiting for review: it stays archived as rejected, out of search and answers';
|
|
16
|
+
static examples = ['<%= config.bin %> kb reject <page-id>', '<%= config.bin %> kb reject <page-id> --json'];
|
|
17
|
+
async run() {
|
|
18
|
+
const { args } = await this.parse(KbReject);
|
|
19
|
+
const res = await this.api.post(`/cli/v1/knowledge/pages/${args.id}/reject`);
|
|
20
|
+
if (!this.jsonEnabled()) {
|
|
21
|
+
this.log('Knowledge page rejected:');
|
|
22
|
+
this.log((0, output_1.renderRecord)(res.data ?? {}));
|
|
23
|
+
}
|
|
24
|
+
return res;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.default = KbReject;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_1 = require("@oclif/core");
|
|
4
4
|
const base_1 = require("../../base");
|
|
5
|
+
const error_codes_1 = require("../../errors/error-codes");
|
|
5
6
|
const limits_1 = require("../../lib/limits");
|
|
6
7
|
const output_1 = require("../../lib/output");
|
|
7
8
|
const ticket_body_1 = require("../../lib/ticket-body");
|
|
@@ -42,9 +43,29 @@ class TicketsCreate extends base_1.BaseCommand {
|
|
|
42
43
|
body.status_id = statusId;
|
|
43
44
|
body.priority_id = priorityId;
|
|
44
45
|
const res = await this.api.post(`/cli/v1/projects/${projectId}/tickets`, body);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
// CYCL-52 — da qui in poi il ticket ESISTE sul server, e qualunque cosa vada storta mentre lo si
|
|
47
|
+
// racconta non lo cancella. Se il comando esce non-zero senza dire niente, chi legge conclude che
|
|
48
|
+
// non è stato creato e lo ricrea: è così che sono nati CYRA-755/756, due volte lo stesso ticket.
|
|
49
|
+
// Perciò il codice e l'id vengono emessi comunque, e solo dopo l'errore vero risale.
|
|
50
|
+
try {
|
|
51
|
+
if (!this.jsonEnabled()) {
|
|
52
|
+
this.log('Ticket created:');
|
|
53
|
+
this.log((0, output_1.renderRecord)(res.data ?? {}));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const creato = res.data ?? {};
|
|
58
|
+
const identita = { code: creato.code, id: creato.id };
|
|
59
|
+
const motivo = error instanceof Error ? error.message : String(error);
|
|
60
|
+
// In modalità dati l'envelope è UNO: se si lasciasse risalire l'errore, la base ne emetterebbe
|
|
61
|
+
// un secondo e chi legge si troverebbe due oggetti sulla stessa uscita. Qui data ed error
|
|
62
|
+
// stanno insieme — il ticket c'è, e c'è anche cosa è andato storto — e si esce non-zero.
|
|
63
|
+
if (this.jsonEnabled()) {
|
|
64
|
+
this.logJson({ data: identita, error: { code: error_codes_1.ErrorCodes.System.unexpected, message: motivo } });
|
|
65
|
+
return this.exit(1);
|
|
66
|
+
}
|
|
67
|
+
this.logToStderr(`Ticket creato (${String(identita.code ?? identita.id ?? 'senza codice')}), ma la resa è fallita: ${motivo}`);
|
|
68
|
+
throw error;
|
|
48
69
|
}
|
|
49
70
|
return res;
|
|
50
71
|
}
|