@bussolabs/closeyourit-cli 0.20.0 → 0.21.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.
- package/README.md +10 -0
- package/dist/base.d.ts +11 -0
- package/dist/base.js +15 -0
- package/dist/commands/kb/book/add-page.d.ts +18 -0
- package/dist/commands/kb/book/add-page.js +55 -0
- package/dist/commands/kb/book/list.d.ts +12 -0
- package/dist/commands/kb/book/list.js +33 -0
- package/dist/commands/kb/book/show.d.ts +9 -0
- package/dist/commands/kb/book/show.js +22 -0
- package/dist/commands/kb/publish.d.ts +15 -0
- package/dist/commands/kb/publish.js +106 -0
- package/dist/errors/error-codes.d.ts +3 -0
- package/dist/errors/error-codes.js +5 -0
- package/dist/lib/knowledge.d.ts +24 -0
- package/dist/lib/knowledge.js +68 -0
- package/oclif.manifest.json +2984 -2739
- package/opencli.json +192 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -142,10 +142,14 @@ CLOSEYOURIT_TOKEN=cyi_u_… CLOSEYOURIT_API_URL=https://www.closeyour.it \
|
|
|
142
142
|
| `kb show <id> [--related] [--question <q>]` | Show one knowledge page: metadata header, raw markdown body, then the technical section. |
|
|
143
143
|
| `kb related <id> [--question <q>] [--links-only]` | Pages to read next: linked with `[[wiki links]]` plus close matches by meaning. |
|
|
144
144
|
| `kb create --project <id\|key> --title <t> [--kind note\|decision\|guide] (--body <md> \| --body-file <path>) [--tech-spec <md> \| --tech-spec-file <path>] [--in-review] [--review-note <line>]` | Create a knowledge page (optional technical section, kept separate from the body). 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. |
|
|
145
|
+
| `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`. |
|
|
145
146
|
| `kb consolidate <id> --path <doc-path>` | Mark an accepted page as written to the versioned docs, recording where the document lives. |
|
|
146
147
|
| `kb update <id> [--title] [--kind] [--body \| --body-file] [--tech-spec \| --tech-spec-file]` | Update a page (unpassed fields keep their current value). Same length caps as `kb create`; text already over the cap is refused only if it grows. |
|
|
147
148
|
| `kb delete <id> --confirm` | Delete a knowledge page (irreversible). |
|
|
148
149
|
| `kb ask <question…>` | Ask a question; the AI answers from the knowledge base and cites the pages it used. |
|
|
150
|
+
| `kb book list [--project <id\|key>] [--q <text>] [--per] [--page]` | List knowledge books (ordered collections of pages). Cross-project: omit `--project` for every visible project. |
|
|
151
|
+
| `kb book show <id>` | Show a book: metadata, then the summary of the pages it collects (`#` is the 0-based position). |
|
|
152
|
+
| `kb book add-page <id> --page <page-id> [--position <n>]` | Add a page to a book's summary. `--page` takes a page **id** (alias: `--page-id`), not a page number; `--position` is 0-based (`0` = top, omitted = last), like the position the summary shows back. Idempotent: repeating it never lists the page twice. |
|
|
149
153
|
| `servers list [--status] [--page]` | List the monitored servers (fleet snapshot: status, cpu/mem/disk, last seen). |
|
|
150
154
|
| `servers show <id>` | Show a monitored server (latest snapshot + machine details). |
|
|
151
155
|
| `servers rename <id> --name <name>` | Rename a server (display name only). |
|
|
@@ -232,6 +236,12 @@ CLOSEYOURIT_TOKEN=cyi_u_… CLOSEYOURIT_API_URL=https://www.closeyour.it \
|
|
|
232
236
|
Every command accepts `--json` for machine-readable output and `--help` for usage details.
|
|
233
237
|
`--project` accepts either a project UUID or its key (matched case-insensitively).
|
|
234
238
|
|
|
239
|
+
`kb publish` needs a backend exposing the atomic Knowledge publication route; an older one fails with
|
|
240
|
+
`C404-KNW-001` and says to upgrade. An unknown project key fails locally with `C404-PRJ-001`; the
|
|
241
|
+
backend `R404-KNOWLEDGE-001` is reserved for a project UUID that is missing or not visible to the
|
|
242
|
+
caller. `R403-KNOWLEDGE-003` means the key already belongs to a page you cannot manage, and
|
|
243
|
+
`R409-KNOWLEDGE-001` that two untagged pages share the title the key would adopt.
|
|
244
|
+
|
|
235
245
|
When a command fails in `--json` mode it exits non-zero and prints an error envelope on stdout —
|
|
236
246
|
never a silent success:
|
|
237
247
|
|
package/dist/base.d.ts
CHANGED
|
@@ -15,6 +15,17 @@ export declare const environmentFlag: {
|
|
|
15
15
|
export declare const pageFlag: {
|
|
16
16
|
page: Interfaces.OptionFlag<number, Interfaces.CustomOptions>;
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Message for a `--page`/`--per` outside the sensible range, or undefined when both are fine.
|
|
20
|
+
*
|
|
21
|
+
* The backend silently clamps them (`Pagination`): `--per 0` quietly returns the default page size
|
|
22
|
+
* and `--page 0` returns page 1, so a script that computed the wrong number gets plausible data
|
|
23
|
+
* back instead of an error. Say it before the call, and exit 2 like any other rejected command line.
|
|
24
|
+
*/
|
|
25
|
+
export declare function paginationError(opts: {
|
|
26
|
+
page?: number;
|
|
27
|
+
per?: number;
|
|
28
|
+
}): string | undefined;
|
|
18
29
|
export declare abstract class BaseCommand extends Command {
|
|
19
30
|
static enableJsonFlag: boolean;
|
|
20
31
|
protected cfg: CliConfig;
|
package/dist/base.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BaseCommand = exports.pageFlag = exports.environmentFlag = exports.projectFlag = exports.UUID_RE = void 0;
|
|
4
|
+
exports.paginationError = paginationError;
|
|
4
5
|
const core_1 = require("@oclif/core");
|
|
5
6
|
const config_1 = require("./lib/config");
|
|
6
7
|
const api_1 = require("./lib/api");
|
|
@@ -21,6 +22,20 @@ exports.environmentFlag = {
|
|
|
21
22
|
exports.pageFlag = {
|
|
22
23
|
page: core_1.Flags.integer({ description: 'Page number', default: 1 }),
|
|
23
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Message for a `--page`/`--per` outside the sensible range, or undefined when both are fine.
|
|
27
|
+
*
|
|
28
|
+
* The backend silently clamps them (`Pagination`): `--per 0` quietly returns the default page size
|
|
29
|
+
* and `--page 0` returns page 1, so a script that computed the wrong number gets plausible data
|
|
30
|
+
* back instead of an error. Say it before the call, and exit 2 like any other rejected command line.
|
|
31
|
+
*/
|
|
32
|
+
function paginationError(opts) {
|
|
33
|
+
if (opts.page !== undefined && opts.page < 1)
|
|
34
|
+
return `Invalid --page ${opts.page}: the first page is 1.`;
|
|
35
|
+
if (opts.per !== undefined && opts.per < 1)
|
|
36
|
+
return `Invalid --per ${opts.per}: ask for at least 1 row.`;
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
24
39
|
class BaseCommand extends core_1.Command {
|
|
25
40
|
static enableJsonFlag = true;
|
|
26
41
|
cfg;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base';
|
|
2
|
+
/**
|
|
3
|
+
* Put one page in a book's summary. Idempotent server-side: repeating the same call leaves the
|
|
4
|
+
* summary as it is (the page is moved, never added twice), which is what makes it safe for an
|
|
5
|
+
* automation that cannot know whether the previous run got through.
|
|
6
|
+
*/
|
|
7
|
+
export default class KbBookAddPage extends BaseCommand {
|
|
8
|
+
static description: string;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static args: {
|
|
11
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
12
|
+
};
|
|
13
|
+
static flags: {
|
|
14
|
+
page: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
|
+
position: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<unknown>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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 knowledge_1 = require("../../../lib/knowledge");
|
|
6
|
+
/**
|
|
7
|
+
* Put one page in a book's summary. Idempotent server-side: repeating the same call leaves the
|
|
8
|
+
* summary as it is (the page is moved, never added twice), which is what makes it safe for an
|
|
9
|
+
* automation that cannot know whether the previous run got through.
|
|
10
|
+
*/
|
|
11
|
+
class KbBookAddPage extends base_1.BaseCommand {
|
|
12
|
+
static description = 'Add a knowledge page to a book, optionally at a given position in the summary';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> kb book add-page <book-id> --page <page-id>',
|
|
15
|
+
'<%= config.bin %> kb book add-page <book-id> --page <page-id> --position 0',
|
|
16
|
+
'<%= config.bin %> kb book add-page <book-id> --page <page-id> --json',
|
|
17
|
+
];
|
|
18
|
+
static args = {
|
|
19
|
+
id: core_1.Args.string({ description: 'Knowledge book id', required: true }),
|
|
20
|
+
};
|
|
21
|
+
static flags = {
|
|
22
|
+
// `--page` is the name the Automator already calls (closeyourit-automator, lib/book.ts
|
|
23
|
+
// `assignPage`), and this command has no pagination, so there is nothing to collide with. It
|
|
24
|
+
// still takes a page *id*, unlike the `--page` of every list command: `--page-id` is accepted
|
|
25
|
+
// as an alias so a human can be explicit about that.
|
|
26
|
+
page: core_1.Flags.string({
|
|
27
|
+
aliases: ['page-id'],
|
|
28
|
+
description: 'Id of the knowledge page to add (an id, not a page number; alias: --page-id)',
|
|
29
|
+
required: true,
|
|
30
|
+
}),
|
|
31
|
+
// 0-based, like the server (Knowledge::Books::AddPage) and like the `position` the summary
|
|
32
|
+
// shows back. A caller counting from 1 must subtract one: passing its own number would put the
|
|
33
|
+
// page one slot too low, and the backend clamps instead of refusing.
|
|
34
|
+
position: core_1.Flags.integer({ description: 'Where to put it in the summary (0 = top; omit to append at the end)' }),
|
|
35
|
+
};
|
|
36
|
+
async run() {
|
|
37
|
+
const { args, flags } = await this.parse(KbBookAddPage);
|
|
38
|
+
if (flags.position !== undefined && flags.position < 0) {
|
|
39
|
+
this.error(`Invalid --position ${flags.position}: the summary starts at 0 (the top).`, { exit: 2 });
|
|
40
|
+
}
|
|
41
|
+
// `!== undefined`, not a truthiness test: position 0 is the top of the summary, not "unset".
|
|
42
|
+
const body = { page_id: flags.page };
|
|
43
|
+
if (flags.position !== undefined)
|
|
44
|
+
body.position = flags.position;
|
|
45
|
+
const res = await this.api.post(`/cli/v1/knowledge/books/${encodeURIComponent(args.id)}/pages`, body);
|
|
46
|
+
if (!this.jsonEnabled()) {
|
|
47
|
+
// "updated", not "added": the call is idempotent, so on a repeat nothing was inserted — what
|
|
48
|
+
// is worth showing either way is the resulting order.
|
|
49
|
+
this.log('Book summary updated:');
|
|
50
|
+
this.log((0, knowledge_1.renderBook)(res.data ?? {}));
|
|
51
|
+
}
|
|
52
|
+
return res;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.default = KbBookAddPage;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base';
|
|
2
|
+
export default class KbBookList 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
|
+
q: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
per: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<unknown>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
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 knowledge_1 = require("../../../lib/knowledge");
|
|
6
|
+
class KbBookList extends base_1.BaseCommand {
|
|
7
|
+
static description = 'List knowledge books (ordered collections of pages) across the projects you can see';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> kb book list',
|
|
10
|
+
'<%= config.bin %> kb book list --project acme-api',
|
|
11
|
+
'<%= config.bin %> kb book list --q deploy --per 50',
|
|
12
|
+
'<%= config.bin %> kb book list --json',
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
// Optional (the endpoint is cross-project): omit it to span every visible project.
|
|
16
|
+
project: core_1.Flags.string({ char: 'p', description: 'Filter by project (key or UUID)' }),
|
|
17
|
+
q: core_1.Flags.string({ description: 'Filter by title (substring, case-insensitive)' }),
|
|
18
|
+
per: core_1.Flags.integer({ description: 'Page size' }),
|
|
19
|
+
...base_1.pageFlag,
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const { flags } = await this.parse(KbBookList);
|
|
23
|
+
const invalid = (0, base_1.paginationError)(flags);
|
|
24
|
+
if (invalid)
|
|
25
|
+
this.error(invalid, { exit: 2 });
|
|
26
|
+
const res = await this.api.get(`/cli/v1/knowledge/books?${(0, knowledge_1.buildBooksQuery)(flags)}`);
|
|
27
|
+
if (!this.jsonEnabled()) {
|
|
28
|
+
this.log((0, knowledge_1.renderBooksTable)(res.data ?? []));
|
|
29
|
+
}
|
|
30
|
+
return res;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.default = KbBookList;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base';
|
|
2
|
+
export default class KbBookShow extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<unknown>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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 knowledge_1 = require("../../../lib/knowledge");
|
|
6
|
+
class KbBookShow extends base_1.BaseCommand {
|
|
7
|
+
static description = 'Show a knowledge book: its metadata, then the summary of the pages it collects';
|
|
8
|
+
static examples = ['<%= config.bin %> kb book show <book-id>', '<%= config.bin %> kb book show <book-id> --json'];
|
|
9
|
+
static args = {
|
|
10
|
+
id: core_1.Args.string({ description: 'Knowledge book id', required: true }),
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { args } = await this.parse(KbBookShow);
|
|
14
|
+
const res = await this.api.get(`/cli/v1/knowledge/books/${encodeURIComponent(args.id)}`);
|
|
15
|
+
// The summary only lists the pages of the projects the reader can see, so a multi-project book
|
|
16
|
+
// never shows titles from a project this account has no access to.
|
|
17
|
+
if (!this.jsonEnabled())
|
|
18
|
+
this.log((0, knowledge_1.renderBook)(res.data ?? {}));
|
|
19
|
+
return res;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.default = KbBookShow;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class KbPublish extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static usage: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
'publication-key': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
title: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
kind: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
body: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
'body-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<unknown>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const promises_1 = require("node:fs/promises");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const base_1 = require("../../base");
|
|
6
|
+
const error_codes_1 = require("../../errors/error-codes");
|
|
7
|
+
const api_1 = require("../../lib/api");
|
|
8
|
+
const limits_1 = require("../../lib/limits");
|
|
9
|
+
const output_1 = require("../../lib/output");
|
|
10
|
+
// Copia dichiarata di `Knowledge::Page::PUBLICATION_KEY_FORMAT`: una chiave malformata è l'unico
|
|
11
|
+
// errore che si può riconoscere senza chiamare il server, e riconoscerlo qui evita di scoprire a
|
|
12
|
+
// metà di un ciclo di retry che tutte le chiamate erano destinate a un 422.
|
|
13
|
+
const PUBLICATION_KEY_RE = /^[A-Za-z0-9][A-Za-z0-9._~:-]{0,254}$/;
|
|
14
|
+
// Un errore che il backend ha davvero formulato: `error-codes.ts` riserva le iniziali R/G ai codici
|
|
15
|
+
// applicativi e li propaga intatti, mentre tutto ciò che inizia per C lo ha coniato la CLI davanti a
|
|
16
|
+
// una risposta che non parlava — corpo non JSON (`C000-NET-001`) o envelope senza codice
|
|
17
|
+
// (`C404-API-000`), che è la forma in cui si presenta una route inesistente.
|
|
18
|
+
const BACKEND_CODE_RE = /^[RG]/;
|
|
19
|
+
class KbPublish extends base_1.BaseCommand {
|
|
20
|
+
static description = `Idempotently create or update a Knowledge page by publication key.
|
|
21
|
+
|
|
22
|
+
Exactly one of --body or --body-file is required.`;
|
|
23
|
+
static usage = '-p <project> --publication-key <key> --title <title> [--kind <kind>] (--body <markdown> | --body-file <path>)';
|
|
24
|
+
static examples = [
|
|
25
|
+
'<%= config.bin %> kb publish --project CYCL --publication-key docs:release --title "Release guide" --body "Steps…"',
|
|
26
|
+
'<%= config.bin %> kb publish -p CYCL --publication-key docs:release --title "Release guide" --kind guide --body-file ./release.md',
|
|
27
|
+
];
|
|
28
|
+
static flags = {
|
|
29
|
+
...base_1.projectFlag,
|
|
30
|
+
'publication-key': core_1.Flags.string({
|
|
31
|
+
description: 'Stable publication key (1-255 URL-safe characters)',
|
|
32
|
+
required: true,
|
|
33
|
+
}),
|
|
34
|
+
title: core_1.Flags.string({ description: `Page title (max ${limits_1.LENGTH_LIMITS.title} characters)`, required: true }),
|
|
35
|
+
// Nessun default qui: il server mette `note` solo quando la pagina nasce e altrimenti tiene il
|
|
36
|
+
// kind che ha già. Spedire `note` a ogni chiamata declasserebbe una guida a nota al primo
|
|
37
|
+
// publish ripetuto senza `--kind`, che è esattamente ciò che una fonte idempotente non deve fare.
|
|
38
|
+
kind: core_1.Flags.string({ description: 'Page kind: note|decision|guide (new page: note; existing: unchanged)' }),
|
|
39
|
+
body: core_1.Flags.string({
|
|
40
|
+
description: `Page body (markdown, max ${limits_1.LENGTH_LIMITS.pageBody} characters); exactly one of --body or --body-file is required`,
|
|
41
|
+
exactlyOne: ['body', 'body-file'],
|
|
42
|
+
}),
|
|
43
|
+
'body-file': core_1.Flags.string({
|
|
44
|
+
description: `Read the page body (markdown, max ${limits_1.LENGTH_LIMITS.pageBody} characters) from a local file; exactly one of --body or --body-file is required`,
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
async run() {
|
|
48
|
+
const { flags } = await this.parse(KbPublish);
|
|
49
|
+
const publicationKey = flags['publication-key'];
|
|
50
|
+
if (!PUBLICATION_KEY_RE.test(publicationKey)) {
|
|
51
|
+
this.error('Invalid --publication-key: use 1-255 characters, start with a letter or number, then only letters, numbers, . _ ~ : -.', { exit: 2 });
|
|
52
|
+
}
|
|
53
|
+
let body;
|
|
54
|
+
let bodyFlag = '--body';
|
|
55
|
+
if (flags.body === undefined) {
|
|
56
|
+
bodyFlag = '--body-file';
|
|
57
|
+
try {
|
|
58
|
+
body = await (0, promises_1.readFile)(flags['body-file'], 'utf8');
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
this.error(`File not found or unreadable: ${flags['body-file']}`, { exit: 2 });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
body = flags.body;
|
|
66
|
+
}
|
|
67
|
+
// Il publish è un upsert atomico e non rilegge la pagina prima di scrivere: senza un valore
|
|
68
|
+
// attuale da usare come salvaguardia il tetto vale pieno, come su `kb create` (CYCL-32).
|
|
69
|
+
const tooLong = (0, limits_1.tooLongMessage)([
|
|
70
|
+
{ flag: '--title', value: flags.title, max: limits_1.LENGTH_LIMITS.title },
|
|
71
|
+
{ flag: bodyFlag, value: body, max: limits_1.LENGTH_LIMITS.pageBody },
|
|
72
|
+
]);
|
|
73
|
+
if (tooLong)
|
|
74
|
+
this.error(tooLong, { exit: 2 });
|
|
75
|
+
const projectId = await this.resolveProjectId(flags.project);
|
|
76
|
+
const path = `/cli/v1/projects/${projectId}/knowledge/publications/${encodeURIComponent(publicationKey)}`;
|
|
77
|
+
// `kind` esce dal payload quando non è stato passato: il server distingue "non me l'hai detto"
|
|
78
|
+
// (tiene quello che ha) da un valore esplicito, e una chiave presente ma vuota non è la stessa cosa.
|
|
79
|
+
const payload = { title: flags.title, ...(flags.kind === undefined ? {} : { kind: flags.kind }), body };
|
|
80
|
+
let res;
|
|
81
|
+
try {
|
|
82
|
+
res = await this.api.put(path, payload);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// Un 404 che il backend non ha formulato è la route che non c'è: su un server più vecchio
|
|
86
|
+
// della pubblicazione atomica il messaggio dice cosa aggiornare, invece di far sembrare
|
|
87
|
+
// sparito il progetto. Ogni 404 applicativo passa intatto — R404-KNOWLEDGE-001 per il progetto
|
|
88
|
+
// invisibile e chiunque altro il backend aggiunga dopo, che qui non va mascherato.
|
|
89
|
+
if (error instanceof api_1.ApiRequestError && error.status === 404 && !BACKEND_CODE_RE.test(error.code)) {
|
|
90
|
+
throw new api_1.ApiRequestError(404, error_codes_1.ErrorCodes.Knowledge.publishUnavailable, 'Knowledge publish is not supported by this backend. Upgrade CloseYourIt and retry.');
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
if (!this.jsonEnabled()) {
|
|
95
|
+
const meta = (res.meta ?? {});
|
|
96
|
+
const operation = meta.operation === 'created' ? 'created' : 'updated';
|
|
97
|
+
// Il corpo è appena stato scritto da chi ha lanciato il comando: ristamparlo per intero
|
|
98
|
+
// seppellirebbe l'unica cosa nuova, cioè se la pagina è nata ora o è stata aggiornata.
|
|
99
|
+
const { body: _body, ...page } = res.data ?? {};
|
|
100
|
+
this.log(`Knowledge publication ${operation}:`);
|
|
101
|
+
this.log((0, output_1.renderRecord)({ ...page, operation: meta.operation, adopted_legacy: meta.adopted_legacy }));
|
|
102
|
+
}
|
|
103
|
+
return res;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.default = KbPublish;
|
|
@@ -30,6 +30,11 @@ exports.ErrorCodes = {
|
|
|
30
30
|
Member: {
|
|
31
31
|
notFound: 'C404-MBR-001',
|
|
32
32
|
},
|
|
33
|
+
// Il backend risponde 404 senza codice applicativo sulla route della pubblicazione atomica:
|
|
34
|
+
// non è il progetto a mancare, è il server a essere più vecchio del comando (CYCL-8).
|
|
35
|
+
Knowledge: {
|
|
36
|
+
publishUnavailable: 'C404-KNW-001',
|
|
37
|
+
},
|
|
33
38
|
// Stato e priorità di un ticket, indicati per code o etichetta e non trovati nelle lookup
|
|
34
39
|
// dell'organizzazione (o corrispondenti a più di una voce) — CYCL-34.
|
|
35
40
|
TicketStatus: {
|
package/dist/lib/knowledge.d.ts
CHANGED
|
@@ -48,6 +48,30 @@ export declare function renderRelated(rows: RelatedRow[], heading?: string, inde
|
|
|
48
48
|
* order as the table above it. Results without related pages are simply left out.
|
|
49
49
|
*/
|
|
50
50
|
export declare function renderRelatedGroups(pages: Array<Record<string, unknown>>, grouped: Record<string, RelatedRow[]>): string;
|
|
51
|
+
/**
|
|
52
|
+
* Build the query string for `kb book list`. `project` goes out as the raw reference: the books
|
|
53
|
+
* endpoint resolves a key or a UUID by itself (and 404s outside the scope), exactly like the pages
|
|
54
|
+
* endpoint — so no extra lookup call here.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildBooksQuery(opts: {
|
|
57
|
+
page: number;
|
|
58
|
+
per?: number;
|
|
59
|
+
project?: string;
|
|
60
|
+
q?: string;
|
|
61
|
+
}): string;
|
|
62
|
+
/**
|
|
63
|
+
* Render the `kb book list` table. ID first: it is the argument of `kb book show` and of
|
|
64
|
+
* `kb book add-page`, so a table without it would force `--json` just to act on what you are
|
|
65
|
+
* already looking at.
|
|
66
|
+
*/
|
|
67
|
+
export declare function renderBooksTable(books: Array<Record<string, unknown>>): string;
|
|
68
|
+
/**
|
|
69
|
+
* Render a book with its summary: the metadata header, then the table of contents — the response
|
|
70
|
+
* both `kb book show` and `kb book add-page` get back, so the reader sees the resulting order in
|
|
71
|
+
* the same shape either way. `#` is the 0-based position, the very number `--position` takes.
|
|
72
|
+
* Page bodies are not here on purpose: the TOC is for orientation, `kb show <page>` for reading.
|
|
73
|
+
*/
|
|
74
|
+
export declare function renderBook(book: Record<string, unknown>): string;
|
|
51
75
|
/**
|
|
52
76
|
* Render the shared list/search table: TITLE, KIND, PROJECT, AUTHOR, UPDATED.
|
|
53
77
|
*
|
package/dist/lib/knowledge.js
CHANGED
|
@@ -6,6 +6,9 @@ exports.truncate = truncate;
|
|
|
6
6
|
exports.buildPagesQuery = buildPagesQuery;
|
|
7
7
|
exports.renderRelated = renderRelated;
|
|
8
8
|
exports.renderRelatedGroups = renderRelatedGroups;
|
|
9
|
+
exports.buildBooksQuery = buildBooksQuery;
|
|
10
|
+
exports.renderBooksTable = renderBooksTable;
|
|
11
|
+
exports.renderBook = renderBook;
|
|
9
12
|
exports.renderPagesTable = renderPagesTable;
|
|
10
13
|
const output_1 = require("./output");
|
|
11
14
|
/** Knowledge page kinds accepted by the backend (Knowledge::Page). */
|
|
@@ -82,6 +85,71 @@ function renderRelatedGroups(pages, grouped) {
|
|
|
82
85
|
.map(({ rows, title }) => renderRelated(rows, ` ${(0, output_1.sanitize)(title)}`, ' '));
|
|
83
86
|
return blocks.length === 0 ? '' : ['\n🔗 Related:', ...blocks].join('\n');
|
|
84
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Build the query string for `kb book list`. `project` goes out as the raw reference: the books
|
|
90
|
+
* endpoint resolves a key or a UUID by itself (and 404s outside the scope), exactly like the pages
|
|
91
|
+
* endpoint — so no extra lookup call here.
|
|
92
|
+
*/
|
|
93
|
+
function buildBooksQuery(opts) {
|
|
94
|
+
const query = new URLSearchParams();
|
|
95
|
+
query.set('page', String(opts.page));
|
|
96
|
+
if (opts.per !== undefined)
|
|
97
|
+
query.set('per', String(opts.per));
|
|
98
|
+
if (opts.project)
|
|
99
|
+
query.set('project', opts.project);
|
|
100
|
+
if (opts.q)
|
|
101
|
+
query.set('q', opts.q);
|
|
102
|
+
return query.toString();
|
|
103
|
+
}
|
|
104
|
+
/** A list of names (project keys, group names) as one cell, or undefined when there is nothing. */
|
|
105
|
+
function joinList(value) {
|
|
106
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
107
|
+
return undefined;
|
|
108
|
+
return value.map((entry) => String(entry)).join(', ');
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Render the `kb book list` table. ID first: it is the argument of `kb book show` and of
|
|
112
|
+
* `kb book add-page`, so a table without it would force `--json` just to act on what you are
|
|
113
|
+
* already looking at.
|
|
114
|
+
*/
|
|
115
|
+
function renderBooksTable(books) {
|
|
116
|
+
return (0, output_1.renderTable)(['ID', 'TITLE', 'PAGES', 'PROJECTS', 'AUTHOR', 'UPDATED'], books.map((book) => [
|
|
117
|
+
String(book.id ?? ''),
|
|
118
|
+
truncate(String(book.title ?? '')),
|
|
119
|
+
String(book.pages_count ?? ''),
|
|
120
|
+
joinList(book.projects) ?? '',
|
|
121
|
+
String(book.author ?? ''),
|
|
122
|
+
String(book.updated_at ?? ''),
|
|
123
|
+
]));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Render a book with its summary: the metadata header, then the table of contents — the response
|
|
127
|
+
* both `kb book show` and `kb book add-page` get back, so the reader sees the resulting order in
|
|
128
|
+
* the same shape either way. `#` is the 0-based position, the very number `--position` takes.
|
|
129
|
+
* Page bodies are not here on purpose: the TOC is for orientation, `kb show <page>` for reading.
|
|
130
|
+
*/
|
|
131
|
+
function renderBook(book) {
|
|
132
|
+
const pages = Array.isArray(book.pages) ? book.pages : [];
|
|
133
|
+
const header = (0, output_1.renderRecord)({
|
|
134
|
+
id: book.id,
|
|
135
|
+
title: book.title,
|
|
136
|
+
description: book.description,
|
|
137
|
+
projects: joinList(book.projects),
|
|
138
|
+
groups: joinList(book.groups),
|
|
139
|
+
author: book.author,
|
|
140
|
+
pages_count: book.pages_count,
|
|
141
|
+
created_at: book.created_at,
|
|
142
|
+
updated_at: book.updated_at,
|
|
143
|
+
});
|
|
144
|
+
const toc = (0, output_1.renderTable)(['#', 'TITLE', 'KIND', 'PROJECT', 'ID'], pages.map((page) => [
|
|
145
|
+
String(page.position ?? ''),
|
|
146
|
+
truncate(String(page.title ?? '')),
|
|
147
|
+
String(page.kind ?? ''),
|
|
148
|
+
String(page.project ?? ''),
|
|
149
|
+
String(page.id ?? ''),
|
|
150
|
+
]));
|
|
151
|
+
return [header, (0, output_1.section)('Contents', '📖'), toc].join('\n');
|
|
152
|
+
}
|
|
85
153
|
/**
|
|
86
154
|
* Render the shared list/search table: TITLE, KIND, PROJECT, AUTHOR, UPDATED.
|
|
87
155
|
*
|