@bussolabs/closeyourit-cli 0.22.0 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -3
- package/dist/base.d.ts +23 -0
- package/dist/base.js +64 -4
- package/dist/commands/assistant/ask.d.ts +24 -0
- package/dist/commands/assistant/ask.js +112 -0
- package/dist/commands/assistant/conversations/create.d.ts +9 -0
- package/dist/commands/assistant/conversations/create.js +25 -0
- package/dist/commands/assistant/conversations/delete.d.ts +12 -0
- package/dist/commands/assistant/conversations/delete.js +25 -0
- package/dist/commands/assistant/conversations/list.d.ts +10 -0
- package/dist/commands/assistant/conversations/list.js +30 -0
- package/dist/commands/assistant/conversations/show.d.ts +9 -0
- package/dist/commands/assistant/conversations/show.js +25 -0
- package/dist/commands/errors/assign.d.ts +14 -0
- package/dist/commands/errors/assign.js +52 -0
- package/dist/commands/errors/bulk-triage.d.ts +11 -0
- package/dist/commands/errors/bulk-triage.js +41 -0
- package/dist/commands/errors/delete.d.ts +13 -0
- package/dist/commands/errors/delete.js +28 -0
- package/dist/commands/errors/grouping-rules/create.d.ts +15 -0
- package/dist/commands/errors/grouping-rules/create.js +44 -0
- package/dist/commands/errors/grouping-rules/delete.d.ts +13 -0
- package/dist/commands/errors/grouping-rules/delete.js +27 -0
- package/dist/commands/errors/grouping-rules/list.d.ts +10 -0
- package/dist/commands/errors/grouping-rules/list.js +28 -0
- package/dist/commands/errors/grouping-rules/update.d.ts +18 -0
- package/dist/commands/errors/grouping-rules/update.js +43 -0
- package/dist/commands/errors/merge.d.ts +14 -0
- package/dist/commands/errors/merge.js +39 -0
- package/dist/commands/errors/split.d.ts +13 -0
- package/dist/commands/errors/split.js +34 -0
- package/dist/commands/invitations/create.js +9 -2
- package/dist/commands/invitations/resend.js +8 -2
- package/dist/commands/kb/attach.d.ts +12 -0
- package/dist/commands/kb/attach.js +49 -0
- package/dist/commands/kb/attachment-download.d.ts +14 -0
- package/dist/commands/kb/attachment-download.js +38 -0
- package/dist/commands/kb/attachments.d.ts +9 -0
- package/dist/commands/kb/attachments.js +36 -0
- package/dist/commands/kb/create.d.ts +4 -1
- package/dist/commands/kb/create.js +41 -5
- package/dist/commands/kb/detach.d.ts +13 -0
- package/dist/commands/kb/detach.js +26 -0
- package/dist/commands/kb/show.js +3 -0
- package/dist/commands/kb/update.d.ts +4 -0
- package/dist/commands/kb/update.js +69 -21
- package/dist/commands/metrics/bulk-triage.d.ts +11 -0
- package/dist/commands/metrics/bulk-triage.js +44 -0
- package/dist/commands/service-accounts/show.d.ts +9 -0
- package/dist/commands/service-accounts/show.js +26 -0
- package/dist/commands/service-accounts/tokens/list.d.ts +12 -0
- package/dist/commands/service-accounts/tokens/list.js +40 -0
- package/dist/commands/service-accounts/update.d.ts +31 -0
- package/dist/commands/service-accounts/update.js +105 -0
- package/dist/commands/shared/delete.d.ts +12 -0
- package/dist/commands/shared/delete.js +30 -0
- package/dist/commands/shared/list.d.ts +6 -0
- package/dist/commands/shared/list.js +45 -0
- package/dist/commands/shared/set.d.ts +14 -0
- package/dist/commands/shared/set.js +41 -0
- package/dist/commands/tickets/ask.d.ts +13 -0
- package/dist/commands/tickets/ask.js +81 -0
- package/dist/commands/tickets/dependencies/add.d.ts +15 -0
- package/dist/commands/tickets/dependencies/add.js +62 -0
- package/dist/commands/tickets/dependencies/list.d.ts +12 -0
- package/dist/commands/tickets/dependencies/list.js +37 -0
- package/dist/commands/tickets/dependencies/remove.d.ts +14 -0
- package/dist/commands/tickets/dependencies/remove.js +59 -0
- package/dist/commands/tickets/work-context.d.ts +15 -0
- package/dist/commands/tickets/work-context.js +130 -0
- package/dist/commands/vault/requests/approve.d.ts +9 -0
- package/dist/commands/vault/requests/approve.js +40 -0
- package/dist/commands/vault/requests/list.d.ts +6 -0
- package/dist/commands/vault/requests/list.js +29 -0
- package/dist/commands/vault/requests/reject.d.ts +12 -0
- package/dist/commands/vault/requests/reject.js +52 -0
- package/dist/commands/workload/create.d.ts +15 -0
- package/dist/commands/workload/create.js +45 -0
- package/dist/commands/workload/delete.d.ts +12 -0
- package/dist/commands/workload/delete.js +26 -0
- package/dist/commands/workload/list.d.ts +9 -0
- package/dist/commands/workload/list.js +29 -0
- package/dist/commands/workload/participant/add.d.ts +10 -0
- package/dist/commands/workload/participant/add.js +27 -0
- package/dist/commands/workload/participant/remove.d.ts +10 -0
- package/dist/commands/workload/participant/remove.js +23 -0
- package/dist/commands/workload/promote.d.ts +17 -0
- package/dist/commands/workload/promote.js +58 -0
- package/dist/commands/workload/show.d.ts +9 -0
- package/dist/commands/workload/show.js +24 -0
- package/dist/commands/workload/update.d.ts +17 -0
- package/dist/commands/workload/update.js +45 -0
- package/dist/errors/error-codes.d.ts +17 -0
- package/dist/errors/error-codes.js +36 -0
- package/dist/lib/api.d.ts +7 -1
- package/dist/lib/api.js +8 -0
- package/dist/lib/assistant.d.ts +29 -0
- package/dist/lib/assistant.js +76 -0
- package/dist/lib/bulk-triage.d.ts +35 -0
- package/dist/lib/bulk-triage.js +55 -0
- package/dist/lib/grouping-rules.d.ts +28 -0
- package/dist/lib/grouping-rules.js +39 -0
- package/dist/lib/knowledge.d.ts +15 -0
- package/dist/lib/knowledge.js +36 -0
- package/dist/lib/output.js +10 -0
- package/dist/lib/poll.d.ts +51 -0
- package/dist/lib/poll.js +59 -0
- package/dist/lib/service-accounts.d.ts +65 -0
- package/dist/lib/service-accounts.js +132 -0
- package/dist/lib/vault-requests.d.ts +38 -0
- package/dist/lib/vault-requests.js +125 -0
- package/dist/lib/workload.d.ts +37 -0
- package/dist/lib/workload.js +64 -0
- package/oclif.manifest.json +6290 -4001
- package/opencli.json +2164 -549
- package/package.json +27 -3
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** A message of an assistant conversation, as the backend serializes it (AssistantMessageSerializer). */
|
|
2
|
+
export interface AssistantMessage extends Record<string, unknown> {
|
|
3
|
+
id?: string;
|
|
4
|
+
role?: string;
|
|
5
|
+
/** `streaming` = still being written; `complete`/`failed` = final. */
|
|
6
|
+
status?: string;
|
|
7
|
+
content?: string | null;
|
|
8
|
+
tools_used?: unknown;
|
|
9
|
+
error_code?: string | null;
|
|
10
|
+
created_at?: string;
|
|
11
|
+
}
|
|
12
|
+
/** `{code, message}` for a failed reply: the server code when there is one, ours when there is not. */
|
|
13
|
+
export declare function assistantFailure(errorCode?: string | null): {
|
|
14
|
+
code: string;
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The `🧰 Tools used:` line, or '' when the reply used none. What the assistant read is what makes
|
|
19
|
+
* the answer checkable, so it is printed next to the answer and not hidden behind `--json`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function renderToolsUsed(tools: unknown, prefix?: string): string;
|
|
22
|
+
/** Every message of a conversation, oldest first (the order the backend serializes them in). */
|
|
23
|
+
export declare function renderMessages(messages: unknown): string;
|
|
24
|
+
/**
|
|
25
|
+
* The conversation list. ID first: it is the argument of `assistant conversations show|delete` and
|
|
26
|
+
* the value of `assistant ask --conversation`, so a table without it would force `--json` just to
|
|
27
|
+
* carry on the conversation you are looking at.
|
|
28
|
+
*/
|
|
29
|
+
export declare function renderConversationsTable(rows: Array<Record<string, unknown>>): string;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assistantFailure = assistantFailure;
|
|
4
|
+
exports.renderToolsUsed = renderToolsUsed;
|
|
5
|
+
exports.renderMessages = renderMessages;
|
|
6
|
+
exports.renderConversationsTable = renderConversationsTable;
|
|
7
|
+
const error_codes_1 = require("../errors/error-codes");
|
|
8
|
+
const output_1 = require("./output");
|
|
9
|
+
/**
|
|
10
|
+
* Why a reply failed, said in a way that tells the reader what to do next.
|
|
11
|
+
*
|
|
12
|
+
* The backend ships only the code (`Assistant::Message#error_kind` does the same reading on the web
|
|
13
|
+
* side), and the three cases ask for three different reactions: nothing will change until someone
|
|
14
|
+
* connects the AI service, or the question itself must be rewritten, or it is worth trying again in
|
|
15
|
+
* a minute. Collapsing them into one "the assistant failed" would send people to retry forever
|
|
16
|
+
* something that cannot succeed.
|
|
17
|
+
*/
|
|
18
|
+
const FAILURE_MESSAGES = {
|
|
19
|
+
// Integrations::Providers::NOT_CONNECTED_CODE
|
|
20
|
+
'R503-INTEGRATION-006': 'The AI service is not connected for your organization: connect it from the web app, then ask again.',
|
|
21
|
+
// Assistant::Message::NO_ANSWER_ERROR_CODES
|
|
22
|
+
'R502-GEMINI-004': 'The assistant did not produce an answer for this question — try rephrasing it.',
|
|
23
|
+
};
|
|
24
|
+
const FAILURE_FALLBACK = 'The assistant could not answer (temporary problem upstream); try again in a moment.';
|
|
25
|
+
/** `{code, message}` for a failed reply: the server code when there is one, ours when there is not. */
|
|
26
|
+
function assistantFailure(errorCode) {
|
|
27
|
+
const code = errorCode ?? undefined;
|
|
28
|
+
return {
|
|
29
|
+
code: code ?? error_codes_1.ErrorCodes.Ask.failed,
|
|
30
|
+
message: (code ? FAILURE_MESSAGES[code] : undefined) ?? FAILURE_FALLBACK,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The `🧰 Tools used:` line, or '' when the reply used none. What the assistant read is what makes
|
|
35
|
+
* the answer checkable, so it is printed next to the answer and not hidden behind `--json`.
|
|
36
|
+
*/
|
|
37
|
+
function renderToolsUsed(tools, prefix = '') {
|
|
38
|
+
const names = Array.isArray(tools) ? tools.map((tool) => (0, output_1.sanitize)(tool)).filter((tool) => tool.trim() !== '') : [];
|
|
39
|
+
return names.length === 0 ? '' : `${prefix}🧰 Tools used: ${names.join(', ')}`;
|
|
40
|
+
}
|
|
41
|
+
/** One message: who spoke and when, the text, the reason when it failed, the tools it used. */
|
|
42
|
+
function renderMessage(message) {
|
|
43
|
+
const when = (0, output_1.sanitize)(message.created_at ?? '');
|
|
44
|
+
const lines = [`${(0, output_1.statusDot)(message.status)} [${(0, output_1.sanitize)(message.role ?? '-')}] ${when}`.trimEnd()];
|
|
45
|
+
const content = String(message.content ?? '').trim();
|
|
46
|
+
if (content !== '')
|
|
47
|
+
lines.push((0, output_1.sanitizeMultiline)(message.content));
|
|
48
|
+
// A failed reply carries no text at all: without this line the conversation would show an empty
|
|
49
|
+
// bubble and no hint of what went wrong.
|
|
50
|
+
if (message.status === 'failed') {
|
|
51
|
+
const failure = assistantFailure(message.error_code);
|
|
52
|
+
lines.push(`⚠️ ${failure.code} — ${failure.message}`);
|
|
53
|
+
}
|
|
54
|
+
const tools = renderToolsUsed(message.tools_used);
|
|
55
|
+
if (tools !== '')
|
|
56
|
+
lines.push(tools);
|
|
57
|
+
return lines.join('\n');
|
|
58
|
+
}
|
|
59
|
+
/** Every message of a conversation, oldest first (the order the backend serializes them in). */
|
|
60
|
+
function renderMessages(messages) {
|
|
61
|
+
const rows = Array.isArray(messages) ? messages : [];
|
|
62
|
+
return rows.length === 0 ? '(no messages)' : rows.map((message) => renderMessage(message)).join('\n\n');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The conversation list. ID first: it is the argument of `assistant conversations show|delete` and
|
|
66
|
+
* the value of `assistant ask --conversation`, so a table without it would force `--json` just to
|
|
67
|
+
* carry on the conversation you are looking at.
|
|
68
|
+
*/
|
|
69
|
+
function renderConversationsTable(rows) {
|
|
70
|
+
return (0, output_1.renderTable)(['ID', 'TITLE', 'LAST MESSAGE', 'CREATED'], rows.map((row) => [
|
|
71
|
+
String(row.id ?? ''),
|
|
72
|
+
String(row.title ?? ''),
|
|
73
|
+
String(row.last_message_at ?? ''),
|
|
74
|
+
String(row.created_at ?? ''),
|
|
75
|
+
]));
|
|
76
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk triage of monitoring groups (errors, metrics) — CYCL-44.
|
|
3
|
+
*
|
|
4
|
+
* The two endpoints are the same shape (`POST .../bulk_triage` with `ids[]` + `bulk_action`) and
|
|
5
|
+
* share the same blind spot, so the guard lives here once instead of in both commands.
|
|
6
|
+
*/
|
|
7
|
+
/** The three actions the backend accepts, for errors and metrics alike (`Observability::Triage::ACTIONS`). */
|
|
8
|
+
export declare const TRIAGE_ACTIONS: readonly ["resolve", "ignore", "reopen"];
|
|
9
|
+
/**
|
|
10
|
+
* The ids to send: the order they were written in, each one once.
|
|
11
|
+
*
|
|
12
|
+
* The server matches with `where(id: ids)`, which never returns the same row twice — so a repeated
|
|
13
|
+
* `--group` would come back as one row and the check below would read it as "one went missing".
|
|
14
|
+
* Deduplicating is safe here precisely because this endpoint is not all-or-nothing: unlike merge,
|
|
15
|
+
* where the raw list is the evidence the server needs to refuse the whole operation.
|
|
16
|
+
*/
|
|
17
|
+
export declare function uniqueIds(values: string[]): string[];
|
|
18
|
+
/**
|
|
19
|
+
* Refuse a partial triage the caller cannot see.
|
|
20
|
+
*
|
|
21
|
+
* `bulk_triage` scopes the selection to the project and answers 200 on whatever is left, so an id
|
|
22
|
+
* belonging to another project (or to a page refreshed on stale data) is dropped without a word.
|
|
23
|
+
* A script would go on believing it closed thirty groups after closing twenty-eight. Everything the
|
|
24
|
+
* server did touch stays done — the triage is reversible, and undoing the good part would be worse
|
|
25
|
+
* — but the command says so and exits non-zero.
|
|
26
|
+
*/
|
|
27
|
+
export declare function assertNothingDropped(opts: {
|
|
28
|
+
action: string;
|
|
29
|
+
code: string;
|
|
30
|
+
noun: string;
|
|
31
|
+
requested: string[];
|
|
32
|
+
rows: Array<Record<string, unknown>>;
|
|
33
|
+
}): void;
|
|
34
|
+
/** `1 error group` / `3 error groups` — the count line both commands print. */
|
|
35
|
+
export declare function triagedLine(count: number, noun: string, action: string): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TRIAGE_ACTIONS = void 0;
|
|
4
|
+
exports.uniqueIds = uniqueIds;
|
|
5
|
+
exports.assertNothingDropped = assertNothingDropped;
|
|
6
|
+
exports.triagedLine = triagedLine;
|
|
7
|
+
const api_1 = require("./api");
|
|
8
|
+
/**
|
|
9
|
+
* Bulk triage of monitoring groups (errors, metrics) — CYCL-44.
|
|
10
|
+
*
|
|
11
|
+
* The two endpoints are the same shape (`POST .../bulk_triage` with `ids[]` + `bulk_action`) and
|
|
12
|
+
* share the same blind spot, so the guard lives here once instead of in both commands.
|
|
13
|
+
*/
|
|
14
|
+
/** The three actions the backend accepts, for errors and metrics alike (`Observability::Triage::ACTIONS`). */
|
|
15
|
+
exports.TRIAGE_ACTIONS = ['resolve', 'ignore', 'reopen'];
|
|
16
|
+
/**
|
|
17
|
+
* The ids to send: the order they were written in, each one once.
|
|
18
|
+
*
|
|
19
|
+
* The server matches with `where(id: ids)`, which never returns the same row twice — so a repeated
|
|
20
|
+
* `--group` would come back as one row and the check below would read it as "one went missing".
|
|
21
|
+
* Deduplicating is safe here precisely because this endpoint is not all-or-nothing: unlike merge,
|
|
22
|
+
* where the raw list is the evidence the server needs to refuse the whole operation.
|
|
23
|
+
*/
|
|
24
|
+
function uniqueIds(values) {
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
return values.filter((value) => {
|
|
27
|
+
const key = value.toLowerCase();
|
|
28
|
+
if (seen.has(key))
|
|
29
|
+
return false;
|
|
30
|
+
seen.add(key);
|
|
31
|
+
return true;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Refuse a partial triage the caller cannot see.
|
|
36
|
+
*
|
|
37
|
+
* `bulk_triage` scopes the selection to the project and answers 200 on whatever is left, so an id
|
|
38
|
+
* belonging to another project (or to a page refreshed on stale data) is dropped without a word.
|
|
39
|
+
* A script would go on believing it closed thirty groups after closing twenty-eight. Everything the
|
|
40
|
+
* server did touch stays done — the triage is reversible, and undoing the good part would be worse
|
|
41
|
+
* — but the command says so and exits non-zero.
|
|
42
|
+
*/
|
|
43
|
+
function assertNothingDropped(opts) {
|
|
44
|
+
const returned = new Set(opts.rows.map((row) => String(row.id ?? '').toLowerCase()));
|
|
45
|
+
const missing = opts.requested.filter((id) => !returned.has(id.toLowerCase()));
|
|
46
|
+
if (missing.length === 0)
|
|
47
|
+
return;
|
|
48
|
+
throw new api_1.ApiRequestError(404, opts.code, `Triaged ${opts.rows.length} of ${opts.requested.length} ${opts.noun}s (${opts.action}). ` +
|
|
49
|
+
`Not found in this project: ${missing.join(', ')} — those were left untouched, ` +
|
|
50
|
+
`the other ${opts.rows.length} were changed.`);
|
|
51
|
+
}
|
|
52
|
+
/** `1 error group` / `3 error groups` — the count line both commands print. */
|
|
53
|
+
function triagedLine(count, noun, action) {
|
|
54
|
+
return `Triaged ${count} ${noun}${count === 1 ? '' : 's'} (${action}).`;
|
|
55
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error grouping rules (CYCL-44, server side CYRA-153).
|
|
3
|
+
*
|
|
4
|
+
* At ingest time the first active rule, in `position` order, whose match succeeds rewrites the
|
|
5
|
+
* occurrence's fingerprint: everything hitting rules with the same `fingerprint_key` lands in one
|
|
6
|
+
* group. This is how a team bends the automatic grouping.
|
|
7
|
+
*/
|
|
8
|
+
/** Occurrence field a rule looks at (`Errors::GroupingRule.fields`). */
|
|
9
|
+
export declare const GROUPING_RULE_FIELDS: readonly ["exception_type", "message", "culprit", "transaction"];
|
|
10
|
+
/**
|
|
11
|
+
* How the field is compared (`Errors::GroupingRule.operators`). Always case-insensitive and never a
|
|
12
|
+
* regular expression: on the ingest hot path a user-supplied pattern would be an open door to ReDoS.
|
|
13
|
+
*/
|
|
14
|
+
export declare const GROUPING_RULE_OPERATORS: readonly ["contains", "equals", "starts_with"];
|
|
15
|
+
export interface GroupingRuleFlags {
|
|
16
|
+
active?: boolean;
|
|
17
|
+
field?: string;
|
|
18
|
+
'fingerprint-key'?: string;
|
|
19
|
+
operator?: string;
|
|
20
|
+
position?: number;
|
|
21
|
+
value?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The rule attributes to send, skipping every flag left out. `create` fills the required ones by
|
|
25
|
+
* declaring them required; `update` sends only what it was told to change, so an omitted flag keeps
|
|
26
|
+
* the value the rule already has instead of blanking it.
|
|
27
|
+
*/
|
|
28
|
+
export declare function groupingRuleBody(flags: GroupingRuleFlags): Record<string, unknown>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Custom error grouping rules (CYCL-44, server side CYRA-153).
|
|
4
|
+
*
|
|
5
|
+
* At ingest time the first active rule, in `position` order, whose match succeeds rewrites the
|
|
6
|
+
* occurrence's fingerprint: everything hitting rules with the same `fingerprint_key` lands in one
|
|
7
|
+
* group. This is how a team bends the automatic grouping.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.GROUPING_RULE_OPERATORS = exports.GROUPING_RULE_FIELDS = void 0;
|
|
11
|
+
exports.groupingRuleBody = groupingRuleBody;
|
|
12
|
+
/** Occurrence field a rule looks at (`Errors::GroupingRule.fields`). */
|
|
13
|
+
exports.GROUPING_RULE_FIELDS = ['exception_type', 'message', 'culprit', 'transaction'];
|
|
14
|
+
/**
|
|
15
|
+
* How the field is compared (`Errors::GroupingRule.operators`). Always case-insensitive and never a
|
|
16
|
+
* regular expression: on the ingest hot path a user-supplied pattern would be an open door to ReDoS.
|
|
17
|
+
*/
|
|
18
|
+
exports.GROUPING_RULE_OPERATORS = ['contains', 'equals', 'starts_with'];
|
|
19
|
+
/**
|
|
20
|
+
* The rule attributes to send, skipping every flag left out. `create` fills the required ones by
|
|
21
|
+
* declaring them required; `update` sends only what it was told to change, so an omitted flag keeps
|
|
22
|
+
* the value the rule already has instead of blanking it.
|
|
23
|
+
*/
|
|
24
|
+
function groupingRuleBody(flags) {
|
|
25
|
+
const body = {};
|
|
26
|
+
if (flags.field !== undefined)
|
|
27
|
+
body.field = flags.field;
|
|
28
|
+
if (flags.operator !== undefined)
|
|
29
|
+
body.operator = flags.operator;
|
|
30
|
+
if (flags.value !== undefined)
|
|
31
|
+
body.value = flags.value;
|
|
32
|
+
if (flags['fingerprint-key'] !== undefined)
|
|
33
|
+
body.fingerprint_key = flags['fingerprint-key'];
|
|
34
|
+
if (flags.position !== undefined)
|
|
35
|
+
body.position = flags.position;
|
|
36
|
+
if (flags.active !== undefined)
|
|
37
|
+
body.active = flags.active;
|
|
38
|
+
return body;
|
|
39
|
+
}
|
package/dist/lib/knowledge.d.ts
CHANGED
|
@@ -5,6 +5,11 @@ export declare const KNOWLEDGE_KINDS: readonly ["note", "decision", "guide"];
|
|
|
5
5
|
* `--kind note --kind decision,guide` → ['note', 'decision', 'guide'].
|
|
6
6
|
*/
|
|
7
7
|
export declare function normalizeKinds(values?: string[]): string[];
|
|
8
|
+
/**
|
|
9
|
+
* Clean a repeatable flag into a list: trimmed, empties dropped, order preserved.
|
|
10
|
+
* `--tag ""` therefore means "leave nothing" — the deliberate way to clear tags on update.
|
|
11
|
+
*/
|
|
12
|
+
export declare function cleanList(values?: string[]): string[];
|
|
8
13
|
/** Collapse whitespace and truncate a cell so the list table stays single-line. */
|
|
9
14
|
export declare function truncate(value: string, max?: number): string;
|
|
10
15
|
/**
|
|
@@ -59,6 +64,16 @@ export declare function buildBooksQuery(opts: {
|
|
|
59
64
|
project?: string;
|
|
60
65
|
q?: string;
|
|
61
66
|
}): string;
|
|
67
|
+
/**
|
|
68
|
+
* The scope lines of a page — every project it belongs to, its groups, its tags — as readable
|
|
69
|
+
* `key: a, b` rows. `renderRecord` drops arrays, so without these a page connected to five projects
|
|
70
|
+
* read as if it had one, and its tags were invisible from the terminal (CYCL-45).
|
|
71
|
+
*
|
|
72
|
+
* `projects` shows up only past the first one: with a single project the `project` line above
|
|
73
|
+
* already says it, and repeating it would be noise on the pages that have always had just one.
|
|
74
|
+
* Groups and tags appear whenever there are any.
|
|
75
|
+
*/
|
|
76
|
+
export declare function pageScopeLines(page: Record<string, unknown>): Record<string, string>;
|
|
62
77
|
/**
|
|
63
78
|
* Render the `kb book list` table. ID first: it is the argument of `kb book show` and of
|
|
64
79
|
* `kb book add-page`, so a table without it would force `--json` just to act on what you are
|
package/dist/lib/knowledge.js
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.KNOWLEDGE_STATUSES = exports.KNOWLEDGE_KINDS = void 0;
|
|
4
4
|
exports.normalizeKinds = normalizeKinds;
|
|
5
|
+
exports.cleanList = cleanList;
|
|
5
6
|
exports.truncate = truncate;
|
|
6
7
|
exports.buildPagesQuery = buildPagesQuery;
|
|
7
8
|
exports.renderRelated = renderRelated;
|
|
8
9
|
exports.renderRelatedGroups = renderRelatedGroups;
|
|
9
10
|
exports.buildBooksQuery = buildBooksQuery;
|
|
11
|
+
exports.pageScopeLines = pageScopeLines;
|
|
10
12
|
exports.renderBooksTable = renderBooksTable;
|
|
11
13
|
exports.renderBook = renderBook;
|
|
12
14
|
exports.renderPagesTable = renderPagesTable;
|
|
@@ -25,6 +27,15 @@ function normalizeKinds(values) {
|
|
|
25
27
|
.map((value) => value.trim())
|
|
26
28
|
.filter((value) => value.length > 0);
|
|
27
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Clean a repeatable flag into a list: trimmed, empties dropped, order preserved.
|
|
32
|
+
* `--tag ""` therefore means "leave nothing" — the deliberate way to clear tags on update.
|
|
33
|
+
*/
|
|
34
|
+
function cleanList(values) {
|
|
35
|
+
if (!values)
|
|
36
|
+
return [];
|
|
37
|
+
return values.map((value) => value.trim()).filter((value) => value.length > 0);
|
|
38
|
+
}
|
|
28
39
|
/** Collapse whitespace and truncate a cell so the list table stays single-line. */
|
|
29
40
|
function truncate(value, max = 60) {
|
|
30
41
|
const flat = value.replace(/\s+/g, ' ').trim();
|
|
@@ -107,6 +118,31 @@ function joinList(value) {
|
|
|
107
118
|
return undefined;
|
|
108
119
|
return value.map((entry) => String(entry)).join(', ');
|
|
109
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* The scope lines of a page — every project it belongs to, its groups, its tags — as readable
|
|
123
|
+
* `key: a, b` rows. `renderRecord` drops arrays, so without these a page connected to five projects
|
|
124
|
+
* read as if it had one, and its tags were invisible from the terminal (CYCL-45).
|
|
125
|
+
*
|
|
126
|
+
* `projects` shows up only past the first one: with a single project the `project` line above
|
|
127
|
+
* already says it, and repeating it would be noise on the pages that have always had just one.
|
|
128
|
+
* Groups and tags appear whenever there are any.
|
|
129
|
+
*/
|
|
130
|
+
function pageScopeLines(page) {
|
|
131
|
+
const lines = {};
|
|
132
|
+
const projects = Array.isArray(page.projects) ? page.projects : [];
|
|
133
|
+
if (projects.length > 1) {
|
|
134
|
+
const keys = projects.map((project) => String(project.key ?? '')).filter(Boolean);
|
|
135
|
+
if (keys.length > 0)
|
|
136
|
+
lines.projects = keys.join(', ');
|
|
137
|
+
}
|
|
138
|
+
const groups = joinList(page.groups);
|
|
139
|
+
if (groups)
|
|
140
|
+
lines.groups = groups;
|
|
141
|
+
const tags = joinList(page.tags);
|
|
142
|
+
if (tags)
|
|
143
|
+
lines.tags = tags;
|
|
144
|
+
return lines;
|
|
145
|
+
}
|
|
110
146
|
/**
|
|
111
147
|
* Render the `kb book list` table. ID first: it is the argument of `kb book show` and of
|
|
112
148
|
* `kb book add-page`, so a table without it would force `--json` just to act on what you are
|
package/dist/lib/output.js
CHANGED
|
@@ -148,10 +148,20 @@ const STATUS_TONE = {
|
|
|
148
148
|
high: 'down',
|
|
149
149
|
moderate: 'warn',
|
|
150
150
|
low: 'neutral',
|
|
151
|
+
// workload action (carico di lavoro non-dev)
|
|
152
|
+
planned: 'neutral',
|
|
153
|
+
in_progress: 'warn',
|
|
154
|
+
done: 'ok',
|
|
155
|
+
cancelled: 'neutral',
|
|
151
156
|
// runtime support state
|
|
152
157
|
ending_soon: 'warn',
|
|
153
158
|
eol: 'down',
|
|
154
159
|
supported: 'ok',
|
|
160
|
+
// assistant reply + async AI request (CYCL-43): `streaming`/`pending` = still being written,
|
|
161
|
+
// `complete`/`done` = there is an answer, `failed` = there will not be one.
|
|
162
|
+
complete: 'ok',
|
|
163
|
+
failed: 'down',
|
|
164
|
+
streaming: 'warn',
|
|
155
165
|
};
|
|
156
166
|
/**
|
|
157
167
|
* Badge colour (Tailwind family name) → tone. Ticket status/priority labels are org-customisable,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wait for something the server is preparing elsewhere, asking again every so often (CYCL-43).
|
|
3
|
+
*
|
|
4
|
+
* Both AI flows the CLI exposes answer later than they reply: `assistant ask` gets a message that
|
|
5
|
+
* is still being written, `tickets ask` an AI request that is still pending. The waiting is the
|
|
6
|
+
* same in both, and it is the part that goes wrong in interesting ways — so it lives here once.
|
|
7
|
+
*
|
|
8
|
+
* Two guarantees the callers depend on: the pause grows between attempts (a slow answer must not
|
|
9
|
+
* turn into hundreds of requests) and the wait always ends (`timeoutMs`), because a command that
|
|
10
|
+
* hangs forever on a terminal is worse than one that gives up and says where the answer will be.
|
|
11
|
+
*
|
|
12
|
+
* `timeoutMs` bounds the pauses and the number of attempts, not a single request: one `fetch` that
|
|
13
|
+
* never returns still hangs, because `CliApi` sends without an abort signal — a gap that belongs to
|
|
14
|
+
* the HTTP layer and would have to be closed there, for every command at once.
|
|
15
|
+
*/
|
|
16
|
+
export interface PollOptions<T> {
|
|
17
|
+
/** What the caller already holds (the POST response): already settled → no request at all. */
|
|
18
|
+
initial?: T;
|
|
19
|
+
/** Hard ceiling on the whole wait. */
|
|
20
|
+
timeoutMs: number;
|
|
21
|
+
/** First pause; each following one is `factor` times longer, capped at `maxDelayMs`. */
|
|
22
|
+
firstDelayMs?: number;
|
|
23
|
+
maxDelayMs?: number;
|
|
24
|
+
factor?: number;
|
|
25
|
+
/** Body of the timeout error: says how to get the answer that is still coming. */
|
|
26
|
+
timeoutMessage: string;
|
|
27
|
+
/** Injectable for tests; default sleeps for real. */
|
|
28
|
+
sleep?: (ms: number) => Promise<void>;
|
|
29
|
+
/** Injectable for tests; default is the wall clock. */
|
|
30
|
+
now?: () => number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Default `--timeout` of the commands that wait for an AI answer. Three minutes is long enough for
|
|
34
|
+
* an assistant that reads several things before replying, and short enough that a terminal left
|
|
35
|
+
* open never looks frozen.
|
|
36
|
+
*/
|
|
37
|
+
export declare const DEFAULT_TIMEOUT_SECONDS = 180;
|
|
38
|
+
/**
|
|
39
|
+
* Ask `fetch` again until `settled` says the value is final, then return it.
|
|
40
|
+
*
|
|
41
|
+
* Throws an `ApiRequestError` (status 408, `C408-ASK-001`) carrying `timeoutMessage` when the
|
|
42
|
+
* budget runs out — the shape `BaseCommand.catch` already prints as `code: message` on stderr and
|
|
43
|
+
* as an `{error:{code,message}}` envelope in `--json`, so a script can tell "still working" from
|
|
44
|
+
* "the server refused".
|
|
45
|
+
*/
|
|
46
|
+
export declare function pollUntil<T>(fetch: () => Promise<T>, settled: (value: T) => boolean, opts: PollOptions<T>): Promise<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Message for a `--timeout` that cannot wait for anything, or undefined when it is usable.
|
|
49
|
+
* Same shape as `paginationError`: the command line is refused before any call goes out.
|
|
50
|
+
*/
|
|
51
|
+
export declare function timeoutError(seconds: number): string | undefined;
|
package/dist/lib/poll.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_TIMEOUT_SECONDS = void 0;
|
|
4
|
+
exports.pollUntil = pollUntil;
|
|
5
|
+
exports.timeoutError = timeoutError;
|
|
6
|
+
const promises_1 = require("node:timers/promises");
|
|
7
|
+
const api_1 = require("./api");
|
|
8
|
+
const error_codes_1 = require("../errors/error-codes");
|
|
9
|
+
const FIRST_DELAY_MS = 1000;
|
|
10
|
+
const MAX_DELAY_MS = 5000;
|
|
11
|
+
const FACTOR = 1.5;
|
|
12
|
+
/**
|
|
13
|
+
* Default `--timeout` of the commands that wait for an AI answer. Three minutes is long enough for
|
|
14
|
+
* an assistant that reads several things before replying, and short enough that a terminal left
|
|
15
|
+
* open never looks frozen.
|
|
16
|
+
*/
|
|
17
|
+
exports.DEFAULT_TIMEOUT_SECONDS = 180;
|
|
18
|
+
/**
|
|
19
|
+
* Ask `fetch` again until `settled` says the value is final, then return it.
|
|
20
|
+
*
|
|
21
|
+
* Throws an `ApiRequestError` (status 408, `C408-ASK-001`) carrying `timeoutMessage` when the
|
|
22
|
+
* budget runs out — the shape `BaseCommand.catch` already prints as `code: message` on stderr and
|
|
23
|
+
* as an `{error:{code,message}}` envelope in `--json`, so a script can tell "still working" from
|
|
24
|
+
* "the server refused".
|
|
25
|
+
*/
|
|
26
|
+
async function pollUntil(fetch, settled, opts) {
|
|
27
|
+
if (opts.initial !== undefined && settled(opts.initial))
|
|
28
|
+
return opts.initial;
|
|
29
|
+
const now = opts.now ?? Date.now;
|
|
30
|
+
const sleep = opts.sleep ?? ((ms) => (0, promises_1.setTimeout)(ms));
|
|
31
|
+
const maxDelay = opts.maxDelayMs ?? MAX_DELAY_MS;
|
|
32
|
+
const factor = opts.factor ?? FACTOR;
|
|
33
|
+
const deadline = now() + opts.timeoutMs;
|
|
34
|
+
let wait = Math.min(opts.firstDelayMs ?? FIRST_DELAY_MS, maxDelay);
|
|
35
|
+
for (;;) {
|
|
36
|
+
const remaining = deadline - now();
|
|
37
|
+
if (remaining <= 0) {
|
|
38
|
+
throw new api_1.ApiRequestError(408, error_codes_1.ErrorCodes.Ask.timeout, opts.timeoutMessage);
|
|
39
|
+
}
|
|
40
|
+
// Never sleep past the deadline: the last attempt lands exactly on it, so the full budget is
|
|
41
|
+
// used instead of being cut short by a pause that would have overshot it.
|
|
42
|
+
// eslint-disable-next-line no-await-in-loop
|
|
43
|
+
await sleep(Math.min(wait, remaining));
|
|
44
|
+
// eslint-disable-next-line no-await-in-loop
|
|
45
|
+
const value = await fetch();
|
|
46
|
+
if (settled(value))
|
|
47
|
+
return value;
|
|
48
|
+
wait = Math.min(Math.round(wait * factor), maxDelay);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Message for a `--timeout` that cannot wait for anything, or undefined when it is usable.
|
|
53
|
+
* Same shape as `paginationError`: the command line is refused before any call goes out.
|
|
54
|
+
*/
|
|
55
|
+
function timeoutError(seconds) {
|
|
56
|
+
if (seconds < 1)
|
|
57
|
+
return `Invalid --timeout ${seconds}: wait at least 1 second.`;
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading and correcting a service account without recreating it (CYCL-46).
|
|
3
|
+
*
|
|
4
|
+
* One rule runs through everything here: on the secret boundary of a service account, an EMPTY list
|
|
5
|
+
* of environment codes does not mean "no access", it means "every environment". So the empty list is
|
|
6
|
+
* never a neutral value to fall into by accident — it is a decision, and it has to be printed with
|
|
7
|
+
* words that cannot be misread.
|
|
8
|
+
*/
|
|
9
|
+
/** The boundary in words: the codes, or the fact that there is no boundary at all. */
|
|
10
|
+
export declare function environmentsLine(codes: unknown): string;
|
|
11
|
+
/**
|
|
12
|
+
* Environment codes the way the server stores them: trimmed, lowercase, each one once, blanks
|
|
13
|
+
* dropped — the same normalisation `Connections::SetSecretAccess#sanitize` applies before saving.
|
|
14
|
+
* Sending them already in that shape is what makes "what I asked for" and "what came back"
|
|
15
|
+
* comparable at all: otherwise ` Staging ` would look dropped every single time.
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeEnvironmentCodes(values: unknown[]): string[];
|
|
18
|
+
/**
|
|
19
|
+
* The fields of a service account worth printing, as an explicit allow-list — the same discipline as
|
|
20
|
+
* `changeRequestSummary`: `renderRecord` prints every scalar it is handed, so handing it the raw
|
|
21
|
+
* payload would print whatever the server adds next, up to and including a field carrying a token.
|
|
22
|
+
* `secret_environment_codes` is left out on purpose: it is an array (renderRecord would swallow it)
|
|
23
|
+
* and it deserves the sentence `environmentsLine` builds, not a bare key.
|
|
24
|
+
*/
|
|
25
|
+
export declare function accountSummary(account: Record<string, unknown>): Record<string, unknown>;
|
|
26
|
+
/**
|
|
27
|
+
* A copy of a token row without the secret, on both the human and the `--json` path.
|
|
28
|
+
*
|
|
29
|
+
* `UserApiTokenSerializer` never ships it — the secret exists only in the response of `tokens
|
|
30
|
+
* create`, reveal-once, and in the database there is just a prefix. This strips it anyway, for the
|
|
31
|
+
* same reason `withoutValue` does it on the vault queue: the CLI decides what leaves the terminal,
|
|
32
|
+
* so a payload that one day carried the secret (a serializer edit, a proxy, a hand-rolled backend)
|
|
33
|
+
* cannot turn a plain listing into a second reveal.
|
|
34
|
+
*/
|
|
35
|
+
export declare function withoutSecret<T>(row: T): T;
|
|
36
|
+
/** `active` while the token works, `revoked` once it has been withdrawn — what the reader is after. */
|
|
37
|
+
export declare function tokenStatus(token: Record<string, unknown>): string;
|
|
38
|
+
/**
|
|
39
|
+
* The requested codes an organization does not declare — checked BEFORE writing.
|
|
40
|
+
*
|
|
41
|
+
* `SetSecretAccess` intersects what it receives with the codes the organization really has and
|
|
42
|
+
* saves what is left, answering 200 either way. On a typo that intersection is empty, and an empty
|
|
43
|
+
* allow-list is not "no environments", it is EVERY environment: `--secret-environment stagin` would
|
|
44
|
+
* hand the account the production secrets it was being restricted away from. Catching it here means
|
|
45
|
+
* the mistake costs an error message instead of an open door.
|
|
46
|
+
*
|
|
47
|
+
* `known` empty is treated as "could not read them" by the caller, not as "nothing is valid": a
|
|
48
|
+
* lookup that came back empty (no permission, a serializer without `code`) must not block a command
|
|
49
|
+
* the caller is entitled to run.
|
|
50
|
+
*/
|
|
51
|
+
export declare function unknownEnvironmentCodes(requested: string[], known: string[]): string[];
|
|
52
|
+
/**
|
|
53
|
+
* Refuse an environment boundary the server did not actually put in place.
|
|
54
|
+
*
|
|
55
|
+
* The pre-flight check above catches the typo before it is written, but it cannot be the only
|
|
56
|
+
* guard: reading the environments needs `environments.view`, a permission managing members does not
|
|
57
|
+
* imply, and an environment can be removed between the two calls. So the request and the response
|
|
58
|
+
* are compared here too, and any difference — a code dropped OR a code appearing that nobody asked
|
|
59
|
+
* for, which would mean a wider boundary than requested — is an error, not a footnote.
|
|
60
|
+
*
|
|
61
|
+
* Whatever the server did save stays saved (the boundary is one call away from being fixed, and
|
|
62
|
+
* rolling it back blindly could lock out an agent mid-run), but the command says so and exits
|
|
63
|
+
* non-zero — the same shape as `assertNothingDropped` on a partial bulk triage.
|
|
64
|
+
*/
|
|
65
|
+
export declare function assertBoundaryApplied(requested: string[], applied: string[]): void;
|