@bussolabs/closeyourit-cli 0.0.13 → 0.0.15
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 +7 -0
- package/dist/commands/ideas/create.d.ts +3 -1
- package/dist/commands/ideas/create.js +10 -3
- package/dist/commands/ideas/show.js +12 -5
- package/dist/commands/kb/ask.d.ts +10 -0
- package/dist/commands/kb/ask.js +45 -0
- package/dist/commands/kb/create.d.ts +13 -0
- package/dist/commands/kb/create.js +48 -0
- package/dist/commands/kb/delete.d.ts +12 -0
- package/dist/commands/kb/delete.js +25 -0
- package/dist/commands/kb/list.d.ts +12 -0
- package/dist/commands/kb/list.js +35 -0
- package/dist/commands/kb/search.d.ts +16 -0
- package/dist/commands/kb/search.js +48 -0
- package/dist/commands/kb/show.d.ts +9 -0
- package/dist/commands/kb/show.js +40 -0
- package/dist/commands/kb/update.d.ts +15 -0
- package/dist/commands/kb/update.js +53 -0
- package/dist/lib/knowledge.d.ts +22 -0
- package/dist/lib/knowledge.js +54 -0
- package/oclif.manifest.json +1659 -1252
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -64,6 +64,13 @@ organization are stored locally.
|
|
|
64
64
|
| `metrics list --project <id\|key> [--kind] [--page]` | List metric groups. |
|
|
65
65
|
| `metrics show <id> --project <id\|key>` | Show one metric group. |
|
|
66
66
|
| `logs list [--project <id\|key>] [--level] [--trace-id] [--environment] [--page]` | List structured log entries. Cross-app: omit `--project` for the full visible stream. |
|
|
67
|
+
| `kb list [--project <id\|key>] [--kind] [--per] [--page]` | List knowledge pages. Cross-project: omit `--project` for every visible project. |
|
|
68
|
+
| `kb search <query…> [--project <id\|key>] [--kind] [--per] [--page]` | Search knowledge pages (semantic when available, title ILIKE fallback). |
|
|
69
|
+
| `kb show <id>` | Show one knowledge page: metadata header + raw markdown body. |
|
|
70
|
+
| `kb create --project <id\|key> --title <t> [--kind note\|decision\|guide] (--body <md> \| --body-file <path>)` | Create a knowledge page. |
|
|
71
|
+
| `kb update <id> [--title] [--kind] [--body \| --body-file]` | Update a page (unpassed fields keep their current value). |
|
|
72
|
+
| `kb delete <id> --confirm` | Delete a knowledge page (irreversible). |
|
|
73
|
+
| `kb ask <question…>` | Ask a question; the AI answers from the knowledge base and cites the pages it used. |
|
|
67
74
|
| `servers list [--status] [--page]` | List the monitored servers (fleet snapshot: status, cpu/mem/disk, last seen). |
|
|
68
75
|
| `servers show <id>` | Show a monitored server (latest snapshot + machine details). |
|
|
69
76
|
| `servers rename <id> --name <name>` | Rename a server (display name only). |
|
|
@@ -4,7 +4,9 @@ export default class IdeasCreate extends BaseCommand {
|
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
6
|
title: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
-
|
|
7
|
+
problem: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
solution: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
stakeholders: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
10
|
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
11
|
};
|
|
10
12
|
run(): Promise<unknown>;
|
|
@@ -6,17 +6,24 @@ const output_1 = require("../../lib/output");
|
|
|
6
6
|
class IdeasCreate extends base_1.BaseCommand {
|
|
7
7
|
static description = 'Propose an idea for a project';
|
|
8
8
|
static examples = [
|
|
9
|
-
'<%= config.bin %> ideas create --project acme-api --title "Dark mode" --
|
|
9
|
+
'<%= config.bin %> ideas create --project acme-api --title "Dark mode" --problem "The dashboard blinds users at night" --solution "System dark theme" --stakeholders "Mobile team" --stakeholders "Enterprise customers"',
|
|
10
10
|
];
|
|
11
11
|
static flags = {
|
|
12
12
|
...base_1.projectFlag,
|
|
13
13
|
title: core_1.Flags.string({ description: 'Idea title', required: true }),
|
|
14
|
-
|
|
14
|
+
problem: core_1.Flags.string({ description: 'Problem or need the idea solves', required: true }),
|
|
15
|
+
solution: core_1.Flags.string({ description: 'Proposed solution (optional)' }),
|
|
16
|
+
stakeholders: core_1.Flags.string({ description: 'Who it helps — repeatable (optional)', multiple: true }),
|
|
15
17
|
};
|
|
16
18
|
async run() {
|
|
17
19
|
const { flags } = await this.parse(IdeasCreate);
|
|
18
20
|
const projectId = await this.resolveProjectId(flags.project);
|
|
19
|
-
const res = await this.api.post(`/cli/v1/projects/${projectId}/ideas`, {
|
|
21
|
+
const res = await this.api.post(`/cli/v1/projects/${projectId}/ideas`, {
|
|
22
|
+
title: flags.title,
|
|
23
|
+
problem: flags.problem,
|
|
24
|
+
solution: flags.solution,
|
|
25
|
+
stakeholders: flags.stakeholders ?? [],
|
|
26
|
+
});
|
|
20
27
|
if (!this.jsonEnabled()) {
|
|
21
28
|
this.log('Idea created:');
|
|
22
29
|
this.log((0, output_1.renderRecord)(res.data ?? {}));
|
|
@@ -7,7 +7,7 @@ class IdeasShow extends base_1.BaseCommand {
|
|
|
7
7
|
static args = {
|
|
8
8
|
id: core_1.Args.string({ description: 'Idea id', required: true }),
|
|
9
9
|
};
|
|
10
|
-
static description = 'Show an idea (fields +
|
|
10
|
+
static description = 'Show an idea (fields + problem, solution, stakeholders)';
|
|
11
11
|
static examples = ['<%= config.bin %> ideas show <idea-id> --project acme-api'];
|
|
12
12
|
static flags = { ...base_1.projectFlag };
|
|
13
13
|
async run() {
|
|
@@ -15,11 +15,18 @@ class IdeasShow extends base_1.BaseCommand {
|
|
|
15
15
|
const projectId = await this.resolveProjectId(flags.project);
|
|
16
16
|
const res = await this.api.get(`/cli/v1/projects/${projectId}/ideas/${args.id}`);
|
|
17
17
|
if (!this.jsonEnabled()) {
|
|
18
|
-
const {
|
|
18
|
+
const { problem, solution, stakeholders, ...fields } = res.data ?? {};
|
|
19
19
|
this.log((0, output_1.renderRecord)(fields));
|
|
20
|
-
if (
|
|
21
|
-
this.log(
|
|
22
|
-
|
|
20
|
+
if (Array.isArray(stakeholders) && stakeholders.length > 0) {
|
|
21
|
+
this.log(`\nStakeholders: ${stakeholders.join(', ')}`);
|
|
22
|
+
}
|
|
23
|
+
if (problem) {
|
|
24
|
+
this.log('\nProblem:');
|
|
25
|
+
this.log((0, output_1.sanitizeMultiline)(String(problem)));
|
|
26
|
+
}
|
|
27
|
+
if (solution) {
|
|
28
|
+
this.log('\nSolution:');
|
|
29
|
+
this.log((0, output_1.sanitizeMultiline)(String(solution)));
|
|
23
30
|
}
|
|
24
31
|
}
|
|
25
32
|
return res;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class KbAsk extends BaseCommand {
|
|
3
|
+
static strict: boolean;
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static args: {
|
|
7
|
+
question: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<unknown>;
|
|
10
|
+
}
|
|
@@ -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
|
+
class KbAsk extends base_1.BaseCommand {
|
|
7
|
+
// Variadic question: every positional word is joined into a single natural-language question.
|
|
8
|
+
static strict = false;
|
|
9
|
+
static description = 'Ask a natural-language question; the server AI answers from the knowledge base and cites the pages it used (may take a few seconds)';
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> kb ask how do we roll back a kamal deploy',
|
|
12
|
+
'<%= config.bin %> kb ask "what is our retry/backoff policy?" --json',
|
|
13
|
+
];
|
|
14
|
+
// Non-required: with strict = false the arg validation is off, so we guard the empty case ourselves.
|
|
15
|
+
static args = {
|
|
16
|
+
question: core_1.Args.string({ description: 'The question (all positional words are joined)' }),
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
const { argv } = await this.parse(KbAsk);
|
|
20
|
+
const question = argv.join(' ').trim();
|
|
21
|
+
if (question === '')
|
|
22
|
+
this.error('Provide a question (e.g. "kb ask how do we roll back a deploy").', { exit: 2 });
|
|
23
|
+
if (!this.jsonEnabled())
|
|
24
|
+
this.log('Asking the knowledge base (AI), this may take a few seconds…');
|
|
25
|
+
const res = await this.api.post('/cli/v1/knowledge/ask', { question });
|
|
26
|
+
const data = res.data ?? {};
|
|
27
|
+
if (!this.jsonEnabled()) {
|
|
28
|
+
if (data.insufficient) {
|
|
29
|
+
this.log('The knowledge base does not have enough to answer this confidently.');
|
|
30
|
+
}
|
|
31
|
+
const answer = String(data.answer ?? '').trim();
|
|
32
|
+
if (answer !== '')
|
|
33
|
+
this.log((0, output_1.sanitizeMultiline)(data.answer));
|
|
34
|
+
const pages = Array.isArray(data.pages) ? data.pages : [];
|
|
35
|
+
if (pages.length > 0) {
|
|
36
|
+
this.log('\nSources:');
|
|
37
|
+
for (const page of pages) {
|
|
38
|
+
this.log(` [${(0, output_1.sanitize)(page.kind)}] ${(0, output_1.sanitize)(page.title)} · ${(0, output_1.sanitize)(page.id)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return res;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.default = KbAsk;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class KbCreate extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
title: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
+
kind: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
body: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
'body-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<unknown>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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 output_1 = require("../../lib/output");
|
|
7
|
+
class KbCreate extends base_1.BaseCommand {
|
|
8
|
+
static description = 'Create a knowledge page in a project';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> kb create --project acme-api --title "Kamal rollback" --body "Steps…"',
|
|
11
|
+
'<%= config.bin %> kb create -p acme-api --title "ADR: queue backend" --kind decision --body-file ./adr.md',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
// Required — passed straight to the server as `project` (key or UUID), resolved there.
|
|
15
|
+
...base_1.projectFlag,
|
|
16
|
+
title: core_1.Flags.string({ description: 'Page title', required: true }),
|
|
17
|
+
kind: core_1.Flags.string({ description: 'Page kind: note|decision|guide', default: 'note' }),
|
|
18
|
+
body: core_1.Flags.string({ description: 'Page body (markdown)' }),
|
|
19
|
+
'body-file': core_1.Flags.string({ description: 'Read the body (markdown) from a local file' }),
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const { flags } = await this.parse(KbCreate);
|
|
23
|
+
let body = flags.body;
|
|
24
|
+
if (body === undefined && flags['body-file'] !== undefined) {
|
|
25
|
+
try {
|
|
26
|
+
body = await (0, promises_1.readFile)(flags['body-file'], 'utf8');
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
this.error(`File not found or unreadable: ${flags['body-file']}`, { exit: 2 });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (body === undefined) {
|
|
33
|
+
this.error('Provide the page body via --body or --body-file.', { exit: 2 });
|
|
34
|
+
}
|
|
35
|
+
const res = await this.api.post('/cli/v1/knowledge/pages', {
|
|
36
|
+
project: flags.project,
|
|
37
|
+
title: flags.title,
|
|
38
|
+
kind: flags.kind,
|
|
39
|
+
body,
|
|
40
|
+
});
|
|
41
|
+
if (!this.jsonEnabled()) {
|
|
42
|
+
this.log('Knowledge page created:');
|
|
43
|
+
this.log((0, output_1.renderRecord)(res.data ?? {}));
|
|
44
|
+
}
|
|
45
|
+
return res;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.default = KbCreate;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class KbDelete 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
|
+
confirm: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<unknown>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_1 = require("../../base");
|
|
5
|
+
class KbDelete extends base_1.BaseCommand {
|
|
6
|
+
static description = 'Delete a knowledge page (irreversible)';
|
|
7
|
+
static examples = ['<%= config.bin %> kb delete <page-id> --confirm'];
|
|
8
|
+
static args = {
|
|
9
|
+
id: core_1.Args.string({ description: 'Knowledge page id', required: true }),
|
|
10
|
+
};
|
|
11
|
+
static flags = {
|
|
12
|
+
confirm: core_1.Flags.boolean({ description: 'Required: confirm the irreversible deletion' }),
|
|
13
|
+
};
|
|
14
|
+
async run() {
|
|
15
|
+
const { args, flags } = await this.parse(KbDelete);
|
|
16
|
+
if (!flags.confirm) {
|
|
17
|
+
this.error('Refusing to delete without --confirm (the action is irreversible).', { exit: 2 });
|
|
18
|
+
}
|
|
19
|
+
await this.api.delete(`/cli/v1/knowledge/pages/${encodeURIComponent(args.id)}`);
|
|
20
|
+
if (!this.jsonEnabled())
|
|
21
|
+
this.log(`Deleted knowledge page ${args.id}`);
|
|
22
|
+
return { deleted: args.id };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.default = KbDelete;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class KbList 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
|
+
kind: 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,35 @@
|
|
|
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 KbList extends base_1.BaseCommand {
|
|
7
|
+
static description = 'List knowledge pages across the projects you can see';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> kb list',
|
|
10
|
+
'<%= config.bin %> kb list --project acme-api --kind decision',
|
|
11
|
+
'<%= config.bin %> kb list --kind note,guide --per 50 --json',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
// Optional (the endpoint is cross-project): omit it to span every visible project.
|
|
15
|
+
project: core_1.Flags.string({ char: 'p', description: 'Filter by project (key or UUID)' }),
|
|
16
|
+
kind: core_1.Flags.string({ description: 'Filter by kind: note|decision|guide (repeatable or comma-separated)', multiple: true }),
|
|
17
|
+
per: core_1.Flags.integer({ description: 'Page size' }),
|
|
18
|
+
...base_1.pageFlag,
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { flags } = await this.parse(KbList);
|
|
22
|
+
const query = (0, knowledge_1.buildPagesQuery)({
|
|
23
|
+
kinds: (0, knowledge_1.normalizeKinds)(flags.kind),
|
|
24
|
+
page: flags.page,
|
|
25
|
+
per: flags.per,
|
|
26
|
+
project: flags.project,
|
|
27
|
+
});
|
|
28
|
+
const res = await this.api.get(`/cli/v1/knowledge/pages?${query}`);
|
|
29
|
+
if (!this.jsonEnabled()) {
|
|
30
|
+
this.log((0, knowledge_1.renderPagesTable)(res.data ?? []));
|
|
31
|
+
}
|
|
32
|
+
return res;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.default = KbList;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class KbSearch extends BaseCommand {
|
|
3
|
+
static strict: boolean;
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static args: {
|
|
7
|
+
query: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
page: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
kind: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
|
+
per: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<unknown>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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 KbSearch extends base_1.BaseCommand {
|
|
7
|
+
// Variadic query: every positional word is joined into a single search string.
|
|
8
|
+
static strict = false;
|
|
9
|
+
static description = 'Search knowledge pages (semantic when available, ILIKE fallback on the title otherwise)';
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> kb search kamal deploy rollback',
|
|
12
|
+
'<%= config.bin %> kb search "connection pool timeout" --project acme-api',
|
|
13
|
+
'<%= config.bin %> kb search retry backoff --kind decision --json',
|
|
14
|
+
];
|
|
15
|
+
// Non-required: with strict = false the arg validation is off, so we guard the empty case ourselves.
|
|
16
|
+
static args = {
|
|
17
|
+
query: core_1.Args.string({ description: 'Search query (all positional words are joined)' }),
|
|
18
|
+
};
|
|
19
|
+
static flags = {
|
|
20
|
+
project: core_1.Flags.string({ char: 'p', description: 'Filter by project (key or UUID)' }),
|
|
21
|
+
kind: core_1.Flags.string({ description: 'Filter by kind: note|decision|guide (repeatable or comma-separated)', multiple: true }),
|
|
22
|
+
per: core_1.Flags.integer({ description: 'Page size' }),
|
|
23
|
+
...base_1.pageFlag,
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
const { argv, flags } = await this.parse(KbSearch);
|
|
27
|
+
const q = argv.join(' ').trim();
|
|
28
|
+
if (q === '')
|
|
29
|
+
this.error('Provide a search query (e.g. "kb search kamal rollback").', { exit: 2 });
|
|
30
|
+
const query = (0, knowledge_1.buildPagesQuery)({
|
|
31
|
+
kinds: (0, knowledge_1.normalizeKinds)(flags.kind),
|
|
32
|
+
page: flags.page,
|
|
33
|
+
per: flags.per,
|
|
34
|
+
project: flags.project,
|
|
35
|
+
q,
|
|
36
|
+
});
|
|
37
|
+
const res = await this.api.get(`/cli/v1/knowledge/pages?${query}`);
|
|
38
|
+
if (!this.jsonEnabled()) {
|
|
39
|
+
// meta.search tells us whether the server ran a semantic query or fell back to a title ILIKE.
|
|
40
|
+
if (res.meta?.search === 'like') {
|
|
41
|
+
this.log('(semantic search unavailable — showing title matches)');
|
|
42
|
+
}
|
|
43
|
+
this.log((0, knowledge_1.renderPagesTable)(res.data ?? []));
|
|
44
|
+
}
|
|
45
|
+
return res;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.default = KbSearch;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class KbShow 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,40 @@
|
|
|
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
|
+
class KbShow extends base_1.BaseCommand {
|
|
7
|
+
static description = 'Show a single knowledge page: metadata header then the raw markdown body';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> kb show <page-id>',
|
|
10
|
+
'<%= config.bin %> kb show <page-id> --json',
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
id: core_1.Args.string({ description: 'Knowledge page id', required: true }),
|
|
14
|
+
};
|
|
15
|
+
async run() {
|
|
16
|
+
const { args } = await this.parse(KbShow);
|
|
17
|
+
const res = await this.api.get(`/cli/v1/knowledge/pages/${encodeURIComponent(args.id)}`);
|
|
18
|
+
const page = res.data ?? {};
|
|
19
|
+
if (!this.jsonEnabled()) {
|
|
20
|
+
// Header first (scalar fields only), then the body on its own so the markdown stays intact.
|
|
21
|
+
this.log((0, output_1.renderRecord)({
|
|
22
|
+
id: page.id,
|
|
23
|
+
title: page.title,
|
|
24
|
+
kind: page.kind,
|
|
25
|
+
project: page.project,
|
|
26
|
+
project_name: page.project_name,
|
|
27
|
+
author: page.author,
|
|
28
|
+
created_at: page.created_at,
|
|
29
|
+
updated_at: page.updated_at,
|
|
30
|
+
}));
|
|
31
|
+
const body = page.body;
|
|
32
|
+
if (body !== null && body !== undefined && String(body).trim() !== '') {
|
|
33
|
+
this.log('');
|
|
34
|
+
this.log((0, output_1.sanitizeMultiline)(body));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return res;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.default = KbShow;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class KbUpdate 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
|
+
title: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
kind: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
body: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
'body-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<unknown>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
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 output_1 = require("../../lib/output");
|
|
7
|
+
class KbUpdate extends base_1.BaseCommand {
|
|
8
|
+
static description = 'Update a knowledge page (only the fields you pass; the others keep their current value)';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> kb update <page-id> --title "New title"',
|
|
11
|
+
'<%= config.bin %> kb update <page-id> --kind decision --body-file ./adr.md',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
id: core_1.Args.string({ description: 'Knowledge page id', required: true }),
|
|
15
|
+
};
|
|
16
|
+
static flags = {
|
|
17
|
+
title: core_1.Flags.string({ description: 'New title' }),
|
|
18
|
+
kind: core_1.Flags.string({ description: 'New kind: note|decision|guide' }),
|
|
19
|
+
body: core_1.Flags.string({ description: 'New body (markdown)' }),
|
|
20
|
+
'body-file': core_1.Flags.string({ description: 'Read the new body (markdown) from a local file' }),
|
|
21
|
+
};
|
|
22
|
+
async run() {
|
|
23
|
+
const { args, flags } = await this.parse(KbUpdate);
|
|
24
|
+
let body = flags.body;
|
|
25
|
+
if (body === undefined && flags['body-file'] !== undefined) {
|
|
26
|
+
try {
|
|
27
|
+
body = await (0, promises_1.readFile)(flags['body-file'], 'utf8');
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
this.error(`File not found or unreadable: ${flags['body-file']}`, { exit: 2 });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (flags.title === undefined && flags.kind === undefined && body === undefined) {
|
|
34
|
+
this.error('Provide at least one of --title, --kind, --body or --body-file.', { exit: 2 });
|
|
35
|
+
}
|
|
36
|
+
// The backend expects the full title/body/kind on update → read the current page and merge,
|
|
37
|
+
// so a partial edit (e.g. only --title) doesn't blank out the other fields.
|
|
38
|
+
const base = `/cli/v1/knowledge/pages/${encodeURIComponent(args.id)}`;
|
|
39
|
+
const current = await this.api.get(base);
|
|
40
|
+
const page = current.data ?? {};
|
|
41
|
+
const res = await this.api.put(base, {
|
|
42
|
+
title: flags.title ?? page.title,
|
|
43
|
+
kind: flags.kind ?? page.kind,
|
|
44
|
+
body: body ?? page.body,
|
|
45
|
+
});
|
|
46
|
+
if (!this.jsonEnabled()) {
|
|
47
|
+
this.log('Knowledge page updated:');
|
|
48
|
+
this.log((0, output_1.renderRecord)(res.data ?? {}));
|
|
49
|
+
}
|
|
50
|
+
return res;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.default = KbUpdate;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Knowledge page kinds accepted by the backend (Knowledge::Page). */
|
|
2
|
+
export declare const KNOWLEDGE_KINDS: readonly ["note", "decision", "guide"];
|
|
3
|
+
/**
|
|
4
|
+
* Flatten a repeatable + comma-separated `--kind` flag into a clean list.
|
|
5
|
+
* `--kind note --kind decision,guide` → ['note', 'decision', 'guide'].
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalizeKinds(values?: string[]): string[];
|
|
8
|
+
/** Collapse whitespace and truncate a cell so the list table stays single-line. */
|
|
9
|
+
export declare function truncate(value: string, max?: number): string;
|
|
10
|
+
/**
|
|
11
|
+
* Build the shared query string for `kb list` / `kb search`. Kinds go out as the
|
|
12
|
+
* `kind[]` array param; `page` is always sent, `per`/`project`/`q` only when set.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildPagesQuery(opts: {
|
|
15
|
+
kinds: string[];
|
|
16
|
+
page: number;
|
|
17
|
+
per?: number;
|
|
18
|
+
project?: string;
|
|
19
|
+
q?: string;
|
|
20
|
+
}): string;
|
|
21
|
+
/** Render the shared list/search table: TITLE, KIND, PROJECT, AUTHOR, UPDATED. */
|
|
22
|
+
export declare function renderPagesTable(pages: Array<Record<string, unknown>>): string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KNOWLEDGE_KINDS = void 0;
|
|
4
|
+
exports.normalizeKinds = normalizeKinds;
|
|
5
|
+
exports.truncate = truncate;
|
|
6
|
+
exports.buildPagesQuery = buildPagesQuery;
|
|
7
|
+
exports.renderPagesTable = renderPagesTable;
|
|
8
|
+
const output_1 = require("./output");
|
|
9
|
+
/** Knowledge page kinds accepted by the backend (Knowledge::Page). */
|
|
10
|
+
exports.KNOWLEDGE_KINDS = ['note', 'decision', 'guide'];
|
|
11
|
+
/**
|
|
12
|
+
* Flatten a repeatable + comma-separated `--kind` flag into a clean list.
|
|
13
|
+
* `--kind note --kind decision,guide` → ['note', 'decision', 'guide'].
|
|
14
|
+
*/
|
|
15
|
+
function normalizeKinds(values) {
|
|
16
|
+
if (!values)
|
|
17
|
+
return [];
|
|
18
|
+
return values
|
|
19
|
+
.flatMap((value) => value.split(','))
|
|
20
|
+
.map((value) => value.trim())
|
|
21
|
+
.filter((value) => value.length > 0);
|
|
22
|
+
}
|
|
23
|
+
/** Collapse whitespace and truncate a cell so the list table stays single-line. */
|
|
24
|
+
function truncate(value, max = 60) {
|
|
25
|
+
const flat = value.replace(/\s+/g, ' ').trim();
|
|
26
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build the shared query string for `kb list` / `kb search`. Kinds go out as the
|
|
30
|
+
* `kind[]` array param; `page` is always sent, `per`/`project`/`q` only when set.
|
|
31
|
+
*/
|
|
32
|
+
function buildPagesQuery(opts) {
|
|
33
|
+
const query = new URLSearchParams();
|
|
34
|
+
query.set('page', String(opts.page));
|
|
35
|
+
if (opts.per !== undefined)
|
|
36
|
+
query.set('per', String(opts.per));
|
|
37
|
+
if (opts.project)
|
|
38
|
+
query.set('project', opts.project);
|
|
39
|
+
for (const kind of opts.kinds)
|
|
40
|
+
query.append('kind[]', kind);
|
|
41
|
+
if (opts.q)
|
|
42
|
+
query.set('q', opts.q);
|
|
43
|
+
return query.toString();
|
|
44
|
+
}
|
|
45
|
+
/** Render the shared list/search table: TITLE, KIND, PROJECT, AUTHOR, UPDATED. */
|
|
46
|
+
function renderPagesTable(pages) {
|
|
47
|
+
return (0, output_1.renderTable)(['TITLE', 'KIND', 'PROJECT', 'AUTHOR', 'UPDATED'], pages.map((page) => [
|
|
48
|
+
truncate(String(page.title ?? '')),
|
|
49
|
+
String(page.kind ?? ''),
|
|
50
|
+
String(page.project ?? ''),
|
|
51
|
+
String(page.author ?? ''),
|
|
52
|
+
String(page.updated_at ?? ''),
|
|
53
|
+
]));
|
|
54
|
+
}
|