@bussolabs/closeyourit-cli 0.16.0 → 0.17.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/consolidate.d.ts +17 -0
- package/dist/commands/kb/consolidate.js +33 -0
- package/dist/commands/kb/create.d.ts +2 -0
- package/dist/commands/kb/create.js +11 -1
- package/dist/commands/kb/list.d.ts +2 -0
- package/dist/commands/kb/list.js +10 -0
- package/dist/lib/knowledge.d.ts +16 -2
- package/dist/lib/knowledge.js +30 -10
- package/oclif.manifest.json +2486 -2404
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,11 +118,12 @@ CLOSEYOURIT_TOKEN=cyi_u_… CLOSEYOURIT_API_URL=https://www.closeyour.it \
|
|
|
118
118
|
| `logs show <id>` | Show a single log entry (attributes, trace id, correlation). |
|
|
119
119
|
| `logs link <id> --to <Errors::Group:uuid\|Ticketing::Ticket:uuid>` | Manually link a log entry to an error group or ticket. |
|
|
120
120
|
| `logs unlink <id> <link-id> --confirm` | Remove a manual log link. |
|
|
121
|
-
| `kb list [--project <id\|key>] [--kind] [--per] [--page]` | List knowledge pages. Cross-project: omit `--project` for every visible project. |
|
|
121
|
+
| `kb list [--project <id\|key>] [--kind] [--status] [--awaiting-consolidation] [--per] [--page]` | List knowledge pages. Cross-project: omit `--project` for every visible project. Published only unless `--status` says otherwise. |
|
|
122
122
|
| `kb search <query…> [--project <id\|key>] [--kind] [--per] [--page] [--with-related]` | Search knowledge pages (semantic when available, title ILIKE fallback). |
|
|
123
123
|
| `kb show <id> [--related] [--question <q>]` | Show one knowledge page: metadata header, raw markdown body, then the technical section. |
|
|
124
124
|
| `kb related <id> [--question <q>] [--links-only]` | Pages to read next: linked with `[[wiki links]]` plus close matches by meaning. |
|
|
125
|
-
| `kb create --project <id\|key> --title <t> [--kind note\|decision\|guide] (--body <md> \| --body-file <path>) [--tech-spec <md> \| --tech-spec-file <path>]` | Create a knowledge page (optional technical section, kept separate from the body). |
|
|
125
|
+
| `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). With `--in-review` the page waits for a human and stays out of search, answers and related panels. |
|
|
126
|
+
| `kb consolidate <id> --path <doc-path>` | Mark an accepted page as written to the versioned docs, recording where the document lives. |
|
|
126
127
|
| `kb update <id> [--title] [--kind] [--body \| --body-file] [--tech-spec \| --tech-spec-file]` | Update a page (unpassed fields keep their current value). |
|
|
127
128
|
| `kb delete <id> --confirm` | Delete a knowledge page (irreversible). |
|
|
128
129
|
| `kb ask <question…>` | Ask a question; the AI answers from the knowledge base and cites the pages it used. |
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
/**
|
|
3
|
+
* Second step of the review flow: the accepted page has also been written as a versioned document
|
|
4
|
+
* in the knowledge-base repo, so record where it lives. Accepting and discarding stay human gestures
|
|
5
|
+
* on the web review page — this command only closes the loop the CLI can close by itself.
|
|
6
|
+
*/
|
|
7
|
+
export default class KbConsolidate 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
|
+
static flags: {
|
|
14
|
+
path: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<unknown>;
|
|
17
|
+
}
|
|
@@ -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 output_1 = require("../../lib/output");
|
|
6
|
+
/**
|
|
7
|
+
* Second step of the review flow: the accepted page has also been written as a versioned document
|
|
8
|
+
* in the knowledge-base repo, so record where it lives. Accepting and discarding stay human gestures
|
|
9
|
+
* on the web review page — this command only closes the loop the CLI can close by itself.
|
|
10
|
+
*/
|
|
11
|
+
class KbConsolidate extends base_1.BaseCommand {
|
|
12
|
+
static args = {
|
|
13
|
+
id: core_1.Args.string({ description: 'Knowledge page id', required: true }),
|
|
14
|
+
};
|
|
15
|
+
static description = 'Mark an accepted page as written to the versioned docs, recording its path';
|
|
16
|
+
static examples = [
|
|
17
|
+
'<%= config.bin %> kb consolidate <page-id> --path troubleshooting/rails.md',
|
|
18
|
+
'<%= config.bin %> kb consolidate <page-id> --path global/git.md --json',
|
|
19
|
+
];
|
|
20
|
+
static flags = {
|
|
21
|
+
path: core_1.Flags.string({ description: 'Path of the document inside the knowledge-base repo', required: true }),
|
|
22
|
+
};
|
|
23
|
+
async run() {
|
|
24
|
+
const { args, flags } = await this.parse(KbConsolidate);
|
|
25
|
+
const res = await this.api.post(`/cli/v1/knowledge/pages/${args.id}/consolidated`, { source_path: flags.path });
|
|
26
|
+
if (!this.jsonEnabled()) {
|
|
27
|
+
this.log('Knowledge page filed:');
|
|
28
|
+
this.log((0, output_1.renderRecord)(res.data ?? {}));
|
|
29
|
+
}
|
|
30
|
+
return res;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.default = KbConsolidate;
|
|
@@ -9,6 +9,8 @@ export default class KbCreate extends BaseCommand {
|
|
|
9
9
|
'body-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
10
|
'tech-spec': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
11
|
'tech-spec-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
'in-review': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
'review-note': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
14
|
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
15
|
};
|
|
14
16
|
run(): Promise<unknown>;
|
|
@@ -10,6 +10,7 @@ class KbCreate extends base_1.BaseCommand {
|
|
|
10
10
|
'<%= config.bin %> kb create --project acme-api --title "Kamal rollback" --body "Steps…"',
|
|
11
11
|
'<%= config.bin %> kb create -p acme-api --title "ADR: queue backend" --kind decision --body-file ./adr.md',
|
|
12
12
|
'<%= config.bin %> kb create -p acme-api --title "Deploy" --body-file ./body.md --tech-spec-file ./tech.md',
|
|
13
|
+
'<%= config.bin %> kb create -p acme-api --title "Worktree trap" --body-file ./note.md --in-review --review-note "Hit it today, not obvious from the code"',
|
|
13
14
|
];
|
|
14
15
|
static flags = {
|
|
15
16
|
// Required — passed straight to the server as `project` (key or UUID), resolved there.
|
|
@@ -20,6 +21,10 @@ class KbCreate extends base_1.BaseCommand {
|
|
|
20
21
|
'body-file': core_1.Flags.string({ description: 'Read the body (markdown) from a local file' }),
|
|
21
22
|
'tech-spec': core_1.Flags.string({ description: 'Technical section, kept separate from the simple body' }),
|
|
22
23
|
'tech-spec-file': core_1.Flags.string({ description: 'Read the technical section from a local file' }),
|
|
24
|
+
// Propose instead of publish: the page waits for a human in the review queue and stays out of
|
|
25
|
+
// search, answers and related panels until it is accepted (see `kb list --status in_review`).
|
|
26
|
+
'in-review': core_1.Flags.boolean({ description: 'Create the page in review instead of publishing it' }),
|
|
27
|
+
'review-note': core_1.Flags.string({ description: 'One line explaining why the page is worth keeping (review queue)' }),
|
|
23
28
|
};
|
|
24
29
|
async run() {
|
|
25
30
|
const { flags } = await this.parse(KbCreate);
|
|
@@ -44,15 +49,20 @@ class KbCreate extends base_1.BaseCommand {
|
|
|
44
49
|
this.error(`File not found or unreadable: ${flags['tech-spec-file']}`, { exit: 2 });
|
|
45
50
|
}
|
|
46
51
|
}
|
|
52
|
+
if (flags['review-note'] !== undefined && !flags['in-review']) {
|
|
53
|
+
this.error('--review-note only applies to a page created with --in-review.', { exit: 2 });
|
|
54
|
+
}
|
|
47
55
|
const res = await this.api.post('/cli/v1/knowledge/pages', {
|
|
48
56
|
project: flags.project,
|
|
49
57
|
title: flags.title,
|
|
50
58
|
kind: flags.kind,
|
|
51
59
|
body,
|
|
52
60
|
tech_spec: techSpec,
|
|
61
|
+
in_review: flags['in-review'] ? true : undefined,
|
|
62
|
+
review_note: flags['review-note'],
|
|
53
63
|
});
|
|
54
64
|
if (!this.jsonEnabled()) {
|
|
55
|
-
this.log('Knowledge page created:');
|
|
65
|
+
this.log(flags['in-review'] ? 'Knowledge page proposed, waiting for review:' : 'Knowledge page created:');
|
|
56
66
|
this.log((0, output_1.renderRecord)(res.data ?? {}));
|
|
57
67
|
}
|
|
58
68
|
return res;
|
|
@@ -6,6 +6,8 @@ export default class KbList extends BaseCommand {
|
|
|
6
6
|
page: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
7
|
project: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
8
|
kind: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
status: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
'awaiting-consolidation': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
11
|
per: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
12
|
};
|
|
11
13
|
run(): Promise<unknown>;
|
package/dist/commands/kb/list.js
CHANGED
|
@@ -9,21 +9,31 @@ class KbList extends base_1.BaseCommand {
|
|
|
9
9
|
'<%= config.bin %> kb list',
|
|
10
10
|
'<%= config.bin %> kb list --project acme-api --kind decision',
|
|
11
11
|
'<%= config.bin %> kb list --kind note,guide --per 50 --json',
|
|
12
|
+
'<%= config.bin %> kb list --status in_review',
|
|
13
|
+
'<%= config.bin %> kb list --awaiting-consolidation',
|
|
12
14
|
];
|
|
13
15
|
static flags = {
|
|
14
16
|
// Optional (the endpoint is cross-project): omit it to span every visible project.
|
|
15
17
|
project: core_1.Flags.string({ char: 'p', description: 'Filter by project (key or UUID)' }),
|
|
16
18
|
kind: core_1.Flags.string({ description: 'Filter by kind: note|decision|guide (repeatable or comma-separated)', multiple: true }),
|
|
19
|
+
// Without it the server lists published pages only: proposals waiting for review never show up
|
|
20
|
+
// in an ordinary listing.
|
|
21
|
+
status: core_1.Flags.string({ description: 'Filter by review state', options: [...knowledge_1.KNOWLEDGE_STATUSES] }),
|
|
22
|
+
'awaiting-consolidation': core_1.Flags.boolean({
|
|
23
|
+
description: 'Only accepted pages not yet written to the versioned docs',
|
|
24
|
+
}),
|
|
17
25
|
per: core_1.Flags.integer({ description: 'Page size' }),
|
|
18
26
|
...base_1.pageFlag,
|
|
19
27
|
};
|
|
20
28
|
async run() {
|
|
21
29
|
const { flags } = await this.parse(KbList);
|
|
22
30
|
const query = (0, knowledge_1.buildPagesQuery)({
|
|
31
|
+
awaitingConsolidation: flags['awaiting-consolidation'],
|
|
23
32
|
kinds: (0, knowledge_1.normalizeKinds)(flags.kind),
|
|
24
33
|
page: flags.page,
|
|
25
34
|
per: flags.per,
|
|
26
35
|
project: flags.project,
|
|
36
|
+
status: flags.status,
|
|
27
37
|
});
|
|
28
38
|
const res = await this.api.get(`/cli/v1/knowledge/pages?${query}`);
|
|
29
39
|
if (!this.jsonEnabled()) {
|
package/dist/lib/knowledge.d.ts
CHANGED
|
@@ -7,9 +7,15 @@ export declare const KNOWLEDGE_KINDS: readonly ["note", "decision", "guide"];
|
|
|
7
7
|
export declare function normalizeKinds(values?: string[]): string[];
|
|
8
8
|
/** Collapse whitespace and truncate a cell so the list table stays single-line. */
|
|
9
9
|
export declare function truncate(value: string, max?: number): string;
|
|
10
|
+
/**
|
|
11
|
+
* Review states a page can be filtered by (Knowledge::Page#status). `all` drops the filter;
|
|
12
|
+
* omitting `--status` leaves the server default, which is `published` — so an ordinary `kb list`
|
|
13
|
+
* never shows proposals waiting for review.
|
|
14
|
+
*/
|
|
15
|
+
export declare const KNOWLEDGE_STATUSES: readonly ["published", "in_review", "rejected", "all"];
|
|
10
16
|
/**
|
|
11
17
|
* Build the shared query string for `kb list` / `kb search`. Kinds go out as the
|
|
12
|
-
* `kind[]` array param; `page` is always sent,
|
|
18
|
+
* `kind[]` array param; `page` is always sent, the rest only when set.
|
|
13
19
|
*/
|
|
14
20
|
export declare function buildPagesQuery(opts: {
|
|
15
21
|
kinds: string[];
|
|
@@ -18,6 +24,8 @@ export declare function buildPagesQuery(opts: {
|
|
|
18
24
|
project?: string;
|
|
19
25
|
q?: string;
|
|
20
26
|
related?: boolean;
|
|
27
|
+
status?: string;
|
|
28
|
+
awaitingConsolidation?: boolean;
|
|
21
29
|
}): string;
|
|
22
30
|
/** A related-page row as returned by the server (KnowledgeRelatedPageSerializer). */
|
|
23
31
|
export interface RelatedRow extends Record<string, unknown> {
|
|
@@ -40,5 +48,11 @@ export declare function renderRelated(rows: RelatedRow[], heading?: string, inde
|
|
|
40
48
|
* order as the table above it. Results without related pages are simply left out.
|
|
41
49
|
*/
|
|
42
50
|
export declare function renderRelatedGroups(pages: Array<Record<string, unknown>>, grouped: Record<string, RelatedRow[]>): string;
|
|
43
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Render the shared list/search table: TITLE, KIND, PROJECT, AUTHOR, UPDATED.
|
|
53
|
+
*
|
|
54
|
+
* STATUS shows up only when a row is not `published` — an ordinary listing already contains
|
|
55
|
+
* nothing but published pages, so the extra column would be noise; the review queue, where the
|
|
56
|
+
* state is the whole point, gets it automatically.
|
|
57
|
+
*/
|
|
44
58
|
export declare function renderPagesTable(pages: Array<Record<string, unknown>>): string;
|
package/dist/lib/knowledge.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.KNOWLEDGE_KINDS = void 0;
|
|
3
|
+
exports.KNOWLEDGE_STATUSES = exports.KNOWLEDGE_KINDS = void 0;
|
|
4
4
|
exports.normalizeKinds = normalizeKinds;
|
|
5
5
|
exports.truncate = truncate;
|
|
6
6
|
exports.buildPagesQuery = buildPagesQuery;
|
|
@@ -27,9 +27,15 @@ function truncate(value, max = 60) {
|
|
|
27
27
|
const flat = value.replace(/\s+/g, ' ').trim();
|
|
28
28
|
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Review states a page can be filtered by (Knowledge::Page#status). `all` drops the filter;
|
|
32
|
+
* omitting `--status` leaves the server default, which is `published` — so an ordinary `kb list`
|
|
33
|
+
* never shows proposals waiting for review.
|
|
34
|
+
*/
|
|
35
|
+
exports.KNOWLEDGE_STATUSES = ['published', 'in_review', 'rejected', 'all'];
|
|
30
36
|
/**
|
|
31
37
|
* Build the shared query string for `kb list` / `kb search`. Kinds go out as the
|
|
32
|
-
* `kind[]` array param; `page` is always sent,
|
|
38
|
+
* `kind[]` array param; `page` is always sent, the rest only when set.
|
|
33
39
|
*/
|
|
34
40
|
function buildPagesQuery(opts) {
|
|
35
41
|
const query = new URLSearchParams();
|
|
@@ -44,6 +50,10 @@ function buildPagesQuery(opts) {
|
|
|
44
50
|
query.set('q', opts.q);
|
|
45
51
|
if (opts.related)
|
|
46
52
|
query.set('related', '1');
|
|
53
|
+
if (opts.status)
|
|
54
|
+
query.set('status', opts.status);
|
|
55
|
+
if (opts.awaitingConsolidation)
|
|
56
|
+
query.set('awaiting_consolidation', '1');
|
|
47
57
|
return query.toString();
|
|
48
58
|
}
|
|
49
59
|
/**
|
|
@@ -72,13 +82,23 @@ function renderRelatedGroups(pages, grouped) {
|
|
|
72
82
|
.map(({ rows, title }) => renderRelated(rows, ` ${(0, output_1.sanitize)(title)}`, ' '));
|
|
73
83
|
return blocks.length === 0 ? '' : ['\n🔗 Related:', ...blocks].join('\n');
|
|
74
84
|
}
|
|
75
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* Render the shared list/search table: TITLE, KIND, PROJECT, AUTHOR, UPDATED.
|
|
87
|
+
*
|
|
88
|
+
* STATUS shows up only when a row is not `published` — an ordinary listing already contains
|
|
89
|
+
* nothing but published pages, so the extra column would be noise; the review queue, where the
|
|
90
|
+
* state is the whole point, gets it automatically.
|
|
91
|
+
*/
|
|
76
92
|
function renderPagesTable(pages) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
String(page.
|
|
83
|
-
|
|
93
|
+
const showStatus = pages.some((page) => page.status !== undefined && page.status !== 'published');
|
|
94
|
+
const headers = showStatus
|
|
95
|
+
? ['TITLE', 'KIND', 'STATUS', 'PROJECT', 'AUTHOR', 'UPDATED']
|
|
96
|
+
: ['TITLE', 'KIND', 'PROJECT', 'AUTHOR', 'UPDATED'];
|
|
97
|
+
return (0, output_1.renderTable)(headers, pages.map((page) => {
|
|
98
|
+
const row = [truncate(String(page.title ?? '')), String(page.kind ?? '')];
|
|
99
|
+
if (showStatus)
|
|
100
|
+
row.push(String(page.status ?? ''));
|
|
101
|
+
row.push(String(page.project ?? ''), String(page.author ?? ''), String(page.updated_at ?? ''));
|
|
102
|
+
return row;
|
|
103
|
+
}));
|
|
84
104
|
}
|