@bussolabs/closeyourit-cli 0.7.3 → 0.8.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 +3 -2
- package/dist/commands/kb/ask.js +5 -0
- package/dist/commands/kb/related.d.ts +13 -0
- package/dist/commands/kb/related.js +37 -0
- package/dist/commands/kb/search.d.ts +1 -0
- package/dist/commands/kb/search.js +10 -1
- package/dist/commands/kb/show.d.ts +4 -0
- package/dist/commands/kb/show.js +18 -2
- package/dist/lib/knowledge.d.ts +22 -0
- package/dist/lib/knowledge.js +30 -0
- package/oclif.manifest.json +3362 -3288
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -116,8 +116,9 @@ CLOSEYOURIT_TOKEN=cyi_u_… CLOSEYOURIT_API_URL=https://www.closeyour.it \
|
|
|
116
116
|
| `logs link <id> --to <Errors::Group:uuid\|Ticketing::Ticket:uuid>` | Manually link a log entry to an error group or ticket. |
|
|
117
117
|
| `logs unlink <id> <link-id> --confirm` | Remove a manual log link. |
|
|
118
118
|
| `kb list [--project <id\|key>] [--kind] [--per] [--page]` | List knowledge pages. Cross-project: omit `--project` for every visible project. |
|
|
119
|
-
| `kb search <query…> [--project <id\|key>] [--kind] [--per] [--page]` | Search knowledge pages (semantic when available, title ILIKE fallback). |
|
|
120
|
-
| `kb show <id
|
|
119
|
+
| `kb search <query…> [--project <id\|key>] [--kind] [--per] [--page] [--with-related]` | Search knowledge pages (semantic when available, title ILIKE fallback). |
|
|
120
|
+
| `kb show <id> [--related] [--question <q>]` | Show one knowledge page: metadata header + raw markdown body. |
|
|
121
|
+
| `kb related <id> [--question <q>] [--links-only]` | Pages to read next: linked with `[[wiki links]]` plus close matches by meaning. |
|
|
121
122
|
| `kb create --project <id\|key> --title <t> [--kind note\|decision\|guide] (--body <md> \| --body-file <path>)` | Create a knowledge page. |
|
|
122
123
|
| `kb update <id> [--title] [--kind] [--body \| --body-file]` | Update a page (unpassed fields keep their current value). |
|
|
123
124
|
| `kb delete <id> --confirm` | Delete a knowledge page (irreversible). |
|
package/dist/commands/kb/ask.js
CHANGED
|
@@ -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 knowledge_1 = require("../../lib/knowledge");
|
|
5
6
|
const output_1 = require("../../lib/output");
|
|
6
7
|
class KbAsk extends base_1.BaseCommand {
|
|
7
8
|
// Variadic question: every positional word is joined into a single natural-language question.
|
|
@@ -38,6 +39,10 @@ class KbAsk extends base_1.BaseCommand {
|
|
|
38
39
|
this.log(` [${(0, output_1.sanitize)(page.kind)}] ${(0, output_1.sanitize)(page.title)} · ${(0, output_1.sanitize)(page.id)}`);
|
|
39
40
|
}
|
|
40
41
|
}
|
|
42
|
+
// Second hop: what the cited pages point to, kept only when it matches the question.
|
|
43
|
+
const related = Array.isArray(data.related) ? data.related : [];
|
|
44
|
+
if (related.length > 0)
|
|
45
|
+
this.log((0, knowledge_1.renderRelated)(related));
|
|
41
46
|
}
|
|
42
47
|
return res;
|
|
43
48
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class KbRelated 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
|
+
static flags: {
|
|
9
|
+
question: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
'links-only': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<unknown>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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 KbRelated extends base_1.BaseCommand {
|
|
7
|
+
static description = 'List the pages to read next: the ones this page links to (and that link to it), plus close matches by meaning';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> kb related <page-id>',
|
|
10
|
+
'<%= config.bin %> kb related <page-id> --question "how do we roll back a deploy?"',
|
|
11
|
+
'<%= config.bin %> kb related <page-id> --links-only --json',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
id: core_1.Args.string({ description: 'Knowledge page id', required: true }),
|
|
15
|
+
};
|
|
16
|
+
static flags = {
|
|
17
|
+
// With a question the server scores every candidate against it and drops what does not match.
|
|
18
|
+
question: core_1.Flags.string({ description: 'Keep only the pages relevant to this question' }),
|
|
19
|
+
'links-only': core_1.Flags.boolean({ description: 'Only pages linked with [[wiki links]], no close matches' }),
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const { args, flags } = await this.parse(KbRelated);
|
|
23
|
+
const query = new URLSearchParams();
|
|
24
|
+
if (flags.question)
|
|
25
|
+
query.set('question', flags.question);
|
|
26
|
+
if (flags['links-only'])
|
|
27
|
+
query.set('links_only', '1');
|
|
28
|
+
const suffix = query.toString() === '' ? '' : `?${query.toString()}`;
|
|
29
|
+
const res = await this.api.get(`/cli/v1/knowledge/pages/${encodeURIComponent(args.id)}/related${suffix}`);
|
|
30
|
+
const rows = res.data ?? [];
|
|
31
|
+
if (!this.jsonEnabled()) {
|
|
32
|
+
this.log(rows.length === 0 ? 'No related pages.' : (0, knowledge_1.renderRelated)(rows, '🔗 Related:'));
|
|
33
|
+
}
|
|
34
|
+
return res;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.default = KbRelated;
|
|
@@ -11,6 +11,7 @@ export default class KbSearch extends BaseCommand {
|
|
|
11
11
|
project: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
12
|
kind: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
13
|
per: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
|
+
'with-related': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
14
15
|
};
|
|
15
16
|
run(): Promise<unknown>;
|
|
16
17
|
}
|
|
@@ -10,6 +10,7 @@ class KbSearch extends base_1.BaseCommand {
|
|
|
10
10
|
static examples = [
|
|
11
11
|
'<%= config.bin %> kb search kamal deploy rollback',
|
|
12
12
|
'<%= config.bin %> kb search "connection pool timeout" --project acme-api',
|
|
13
|
+
'<%= config.bin %> kb search kamal rollback --with-related',
|
|
13
14
|
'<%= config.bin %> kb search retry backoff --kind decision --json',
|
|
14
15
|
];
|
|
15
16
|
// Non-required: with strict = false the arg validation is off, so we guard the empty case ourselves.
|
|
@@ -20,6 +21,8 @@ class KbSearch extends base_1.BaseCommand {
|
|
|
20
21
|
project: core_1.Flags.string({ char: 'p', description: 'Filter by project (key or UUID)' }),
|
|
21
22
|
kind: core_1.Flags.string({ description: 'Filter by kind: note|decision|guide (repeatable or comma-separated)', multiple: true }),
|
|
22
23
|
per: core_1.Flags.integer({ description: 'Page size' }),
|
|
24
|
+
// Opt-in: the server only computes this when asked, for the first few results.
|
|
25
|
+
'with-related': core_1.Flags.boolean({ description: 'Also show the pages each result links to' }),
|
|
23
26
|
...base_1.pageFlag,
|
|
24
27
|
};
|
|
25
28
|
async run() {
|
|
@@ -33,14 +36,20 @@ class KbSearch extends base_1.BaseCommand {
|
|
|
33
36
|
per: flags.per,
|
|
34
37
|
project: flags.project,
|
|
35
38
|
q,
|
|
39
|
+
related: flags['with-related'],
|
|
36
40
|
});
|
|
37
41
|
const res = await this.api.get(`/cli/v1/knowledge/pages?${query}`);
|
|
42
|
+
const pages = res.data ?? [];
|
|
38
43
|
if (!this.jsonEnabled()) {
|
|
39
44
|
// meta.search tells us whether the server ran a semantic query or fell back to a title ILIKE.
|
|
40
45
|
if (res.meta?.search === 'like') {
|
|
41
46
|
this.log('(semantic search unavailable — showing title matches)');
|
|
42
47
|
}
|
|
43
|
-
this.log((0, knowledge_1.renderPagesTable)(
|
|
48
|
+
this.log((0, knowledge_1.renderPagesTable)(pages));
|
|
49
|
+
const grouped = (res.meta?.related ?? {});
|
|
50
|
+
const block = (0, knowledge_1.renderRelatedGroups)(pages, grouped);
|
|
51
|
+
if (block !== '')
|
|
52
|
+
this.log(block);
|
|
44
53
|
}
|
|
45
54
|
return res;
|
|
46
55
|
}
|
|
@@ -5,5 +5,9 @@ export default class KbShow extends BaseCommand {
|
|
|
5
5
|
static args: {
|
|
6
6
|
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
7
|
};
|
|
8
|
+
static flags: {
|
|
9
|
+
related: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
question: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
8
12
|
run(): Promise<unknown>;
|
|
9
13
|
}
|
package/dist/commands/kb/show.js
CHANGED
|
@@ -2,19 +2,32 @@
|
|
|
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 knowledge_1 = require("../../lib/knowledge");
|
|
5
6
|
const output_1 = require("../../lib/output");
|
|
6
7
|
class KbShow extends base_1.BaseCommand {
|
|
7
8
|
static description = 'Show a single knowledge page: metadata header then the raw markdown body';
|
|
8
9
|
static examples = [
|
|
9
10
|
'<%= config.bin %> kb show <page-id>',
|
|
11
|
+
'<%= config.bin %> kb show <page-id> --question "how do we roll back a deploy?"',
|
|
10
12
|
'<%= config.bin %> kb show <page-id> --json',
|
|
11
13
|
];
|
|
12
14
|
static args = {
|
|
13
15
|
id: core_1.Args.string({ description: 'Knowledge page id', required: true }),
|
|
14
16
|
};
|
|
17
|
+
static flags = {
|
|
18
|
+
// Opt-in: without these flags the request is exactly what it has always been.
|
|
19
|
+
related: core_1.Flags.boolean({ description: 'Also list the pages to read next' }),
|
|
20
|
+
question: core_1.Flags.string({ description: 'Keep only the related pages relevant to this question (implies --related)' }),
|
|
21
|
+
};
|
|
15
22
|
async run() {
|
|
16
|
-
const { args } = await this.parse(KbShow);
|
|
17
|
-
const
|
|
23
|
+
const { args, flags } = await this.parse(KbShow);
|
|
24
|
+
const query = new URLSearchParams();
|
|
25
|
+
if (flags.question)
|
|
26
|
+
query.set('question', flags.question);
|
|
27
|
+
else if (flags.related)
|
|
28
|
+
query.set('related', '1');
|
|
29
|
+
const suffix = query.toString() === '' ? '' : `?${query.toString()}`;
|
|
30
|
+
const res = await this.api.get(`/cli/v1/knowledge/pages/${encodeURIComponent(args.id)}${suffix}`);
|
|
18
31
|
const page = res.data ?? {};
|
|
19
32
|
if (!this.jsonEnabled()) {
|
|
20
33
|
// Header first (scalar fields only), then the body on its own so the markdown stays intact.
|
|
@@ -33,6 +46,9 @@ class KbShow extends base_1.BaseCommand {
|
|
|
33
46
|
this.log('');
|
|
34
47
|
this.log((0, output_1.sanitizeMultiline)(body));
|
|
35
48
|
}
|
|
49
|
+
const related = (res.meta?.related ?? []);
|
|
50
|
+
if (related.length > 0)
|
|
51
|
+
this.log((0, knowledge_1.renderRelated)(related));
|
|
36
52
|
}
|
|
37
53
|
return res;
|
|
38
54
|
}
|
package/dist/lib/knowledge.d.ts
CHANGED
|
@@ -17,6 +17,28 @@ export declare function buildPagesQuery(opts: {
|
|
|
17
17
|
per?: number;
|
|
18
18
|
project?: string;
|
|
19
19
|
q?: string;
|
|
20
|
+
related?: boolean;
|
|
20
21
|
}): string;
|
|
22
|
+
/** A related-page row as returned by the server (KnowledgeRelatedPageSerializer). */
|
|
23
|
+
export interface RelatedRow extends Record<string, unknown> {
|
|
24
|
+
id?: string;
|
|
25
|
+
title?: string;
|
|
26
|
+
kind?: string;
|
|
27
|
+
project?: string;
|
|
28
|
+
via?: string;
|
|
29
|
+
relevance?: number | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Render the "🔗 Related" block shared by `kb show`, `kb ask` and `kb related`.
|
|
33
|
+
* `via` says why the page is here (link = someone wrote it, semantic = close in meaning) and
|
|
34
|
+
* `relevance` how well it matches the question, when one was asked. Empty list → empty string,
|
|
35
|
+
* so callers can print it unconditionally.
|
|
36
|
+
*/
|
|
37
|
+
export declare function renderRelated(rows: RelatedRow[], heading?: string, indent?: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Render `kb search --with-related`: one block per result that has related pages, in the same
|
|
40
|
+
* order as the table above it. Results without related pages are simply left out.
|
|
41
|
+
*/
|
|
42
|
+
export declare function renderRelatedGroups(pages: Array<Record<string, unknown>>, grouped: Record<string, RelatedRow[]>): string;
|
|
21
43
|
/** Render the shared list/search table: TITLE, KIND, PROJECT, AUTHOR, UPDATED. */
|
|
22
44
|
export declare function renderPagesTable(pages: Array<Record<string, unknown>>): string;
|
package/dist/lib/knowledge.js
CHANGED
|
@@ -4,6 +4,8 @@ exports.KNOWLEDGE_KINDS = void 0;
|
|
|
4
4
|
exports.normalizeKinds = normalizeKinds;
|
|
5
5
|
exports.truncate = truncate;
|
|
6
6
|
exports.buildPagesQuery = buildPagesQuery;
|
|
7
|
+
exports.renderRelated = renderRelated;
|
|
8
|
+
exports.renderRelatedGroups = renderRelatedGroups;
|
|
7
9
|
exports.renderPagesTable = renderPagesTable;
|
|
8
10
|
const output_1 = require("./output");
|
|
9
11
|
/** Knowledge page kinds accepted by the backend (Knowledge::Page). */
|
|
@@ -40,8 +42,36 @@ function buildPagesQuery(opts) {
|
|
|
40
42
|
query.append('kind[]', kind);
|
|
41
43
|
if (opts.q)
|
|
42
44
|
query.set('q', opts.q);
|
|
45
|
+
if (opts.related)
|
|
46
|
+
query.set('related', '1');
|
|
43
47
|
return query.toString();
|
|
44
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Render the "🔗 Related" block shared by `kb show`, `kb ask` and `kb related`.
|
|
51
|
+
* `via` says why the page is here (link = someone wrote it, semantic = close in meaning) and
|
|
52
|
+
* `relevance` how well it matches the question, when one was asked. Empty list → empty string,
|
|
53
|
+
* so callers can print it unconditionally.
|
|
54
|
+
*/
|
|
55
|
+
function renderRelated(rows, heading = '\n🔗 Related:', indent = ' ') {
|
|
56
|
+
if (rows.length === 0)
|
|
57
|
+
return '';
|
|
58
|
+
const lines = rows.map((row) => {
|
|
59
|
+
const score = typeof row.relevance === 'number' ? ` · ${row.relevance.toFixed(2)}` : '';
|
|
60
|
+
return `${indent}[${(0, output_1.sanitize)(row.kind)}] ${(0, output_1.sanitize)(row.title)} · ${(0, output_1.sanitize)(row.id)} (${(0, output_1.sanitize)(row.via)}${score})`;
|
|
61
|
+
});
|
|
62
|
+
return [heading, ...lines].join('\n');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Render `kb search --with-related`: one block per result that has related pages, in the same
|
|
66
|
+
* order as the table above it. Results without related pages are simply left out.
|
|
67
|
+
*/
|
|
68
|
+
function renderRelatedGroups(pages, grouped) {
|
|
69
|
+
const blocks = pages
|
|
70
|
+
.map((page) => ({ rows: grouped[String(page.id ?? '')] ?? [], title: String(page.title ?? '') }))
|
|
71
|
+
.filter(({ rows }) => rows.length > 0)
|
|
72
|
+
.map(({ rows, title }) => renderRelated(rows, ` ${(0, output_1.sanitize)(title)}`, ' '));
|
|
73
|
+
return blocks.length === 0 ? '' : ['\n🔗 Related:', ...blocks].join('\n');
|
|
74
|
+
}
|
|
45
75
|
/** Render the shared list/search table: TITLE, KIND, PROJECT, AUTHOR, UPDATED. */
|
|
46
76
|
function renderPagesTable(pages) {
|
|
47
77
|
return (0, output_1.renderTable)(['TITLE', 'KIND', 'PROJECT', 'AUTHOR', 'UPDATED'], pages.map((page) => [
|