@bussolabs/closeyourit-cli 0.22.0 → 0.24.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/coverage/show.d.ts +10 -0
- package/dist/commands/coverage/show.js +39 -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/clarifications.d.ts +21 -0
- package/dist/commands/tickets/clarifications.js +36 -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/usage/list.d.ts +12 -0
- package/dist/commands/usage/list.js +43 -0
- package/dist/commands/usage/reporters.d.ts +10 -0
- package/dist/commands/usage/reporters.js +37 -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/ticket-clarifications.d.ts +13 -0
- package/dist/lib/ticket-clarifications.js +31 -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 +7495 -4994
- package/opencli.json +7424 -5644
- package/package.json +27 -3
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class SharedDelete extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
name: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
confirm: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<unknown>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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 SharedDelete extends base_1.BaseCommand {
|
|
6
|
+
static args = {
|
|
7
|
+
name: core_1.Args.string({ description: 'Shared secret name, or its id', required: true }),
|
|
8
|
+
};
|
|
9
|
+
// The backend has no per-environment destroy: like the web matrix, it deletes the whole row.
|
|
10
|
+
// Say so in the description, in the --confirm refusal and in the success line, so nobody reads
|
|
11
|
+
// this as "removes the production cell" (CYCL-41).
|
|
12
|
+
static description = 'Delete an organization shared secret from EVERY environment at once (irreversible). There is no per-environment deletion.';
|
|
13
|
+
static examples = ['<%= config.bin %> shared delete API_KEY --confirm'];
|
|
14
|
+
static flags = {
|
|
15
|
+
confirm: core_1.Flags.boolean({ description: 'Required: confirm the deletion across every environment' }),
|
|
16
|
+
};
|
|
17
|
+
async run() {
|
|
18
|
+
const { args, flags } = await this.parse(SharedDelete);
|
|
19
|
+
if (!flags.confirm) {
|
|
20
|
+
this.error(`Refusing to delete without --confirm: this removes ${args.name} from every environment at once, not just one.`, { exit: 2 });
|
|
21
|
+
}
|
|
22
|
+
const ref = base_1.UUID_RE.test(args.name) ? args.name : args.name.toUpperCase();
|
|
23
|
+
await this.api.delete(`/cli/v1/shared_secrets/${encodeURIComponent(ref)}`);
|
|
24
|
+
if (!this.jsonEnabled()) {
|
|
25
|
+
this.log(`Shared secret ${ref} deleted from every environment.`);
|
|
26
|
+
}
|
|
27
|
+
return { deleted: ref, environments: 'all' };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.default = SharedDelete;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const base_1 = require("../../base");
|
|
4
|
+
const output_1 = require("../../lib/output");
|
|
5
|
+
const paginate_1 = require("../../lib/paginate");
|
|
6
|
+
/**
|
|
7
|
+
* Environment codes carried by a shared-variable row, or `[]` when the payload does not say.
|
|
8
|
+
*
|
|
9
|
+
* A shared variable holds one value per environment, but `SharedSecretVariableSerializer` today
|
|
10
|
+
* ships only `{id, name, description, created_at, updated_at}` — the per-environment matrix is not
|
|
11
|
+
* in the list payload. Read it defensively rather than print an always-empty column: the day the
|
|
12
|
+
* backend adds `environments`, `shared list` shows it without a CLI change (CYCL-41).
|
|
13
|
+
*/
|
|
14
|
+
function environmentCodes(row) {
|
|
15
|
+
if (!Array.isArray(row.environments))
|
|
16
|
+
return [];
|
|
17
|
+
return row.environments
|
|
18
|
+
.map((env) => (env !== null && typeof env === 'object' ? env.code : env))
|
|
19
|
+
.map((code) => String(code ?? ''))
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
}
|
|
22
|
+
class SharedList extends base_1.BaseCommand {
|
|
23
|
+
static description = 'List the organization shared secret names (one row per variable, every page). Never prints values.';
|
|
24
|
+
static examples = ['<%= config.bin %> shared list', '<%= config.bin %> shared list --json'];
|
|
25
|
+
async run() {
|
|
26
|
+
await this.parse(SharedList);
|
|
27
|
+
const data = await (0, paginate_1.fetchAllPages)(this.api, '/cli/v1/shared_secrets');
|
|
28
|
+
if (!this.jsonEnabled()) {
|
|
29
|
+
// ENVIRONMENTS shows up only when at least one row carries it, like the STATUS column of
|
|
30
|
+
// `kb list`: a column of blanks would read as "this variable is on no environment".
|
|
31
|
+
const showEnvironments = data.some((secret) => environmentCodes(secret).length > 0);
|
|
32
|
+
const headers = showEnvironments ? ['NAME', 'ENVIRONMENTS', 'DESCRIPTION', 'UPDATED'] : ['NAME', 'DESCRIPTION', 'UPDATED'];
|
|
33
|
+
const rows = data.map((secret) => {
|
|
34
|
+
const row = [String(secret.name ?? '')];
|
|
35
|
+
if (showEnvironments)
|
|
36
|
+
row.push(environmentCodes(secret).join(', '));
|
|
37
|
+
row.push(String(secret.description ?? ''), String(secret.updated_at ?? ''));
|
|
38
|
+
return row;
|
|
39
|
+
});
|
|
40
|
+
this.log((0, output_1.renderTable)(headers, rows));
|
|
41
|
+
}
|
|
42
|
+
return { data, meta: (0, paginate_1.aggregatedMeta)(data.length) };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.default = SharedList;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class SharedSet extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
assignment: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
value: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
description: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
environment: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<unknown>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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 SharedSet extends base_1.BaseCommand {
|
|
6
|
+
static args = {
|
|
7
|
+
assignment: core_1.Args.string({ description: 'NAME=VALUE, or just NAME with --value', required: true }),
|
|
8
|
+
};
|
|
9
|
+
static description = 'Set (create or update) an organization shared secret for one environment. Projects delegated on that environment read the new value.';
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> shared set -e production SENTRY_DSN=https://key@sentry.io/42',
|
|
12
|
+
'<%= config.bin %> shared set -e staging API_KEY --value s3cr3t --description "Stripe key"',
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
// No --project on purpose: a shared variable belongs to the organization and reaches projects
|
|
16
|
+
// through delegation, so scoping it to one project at write time would be a lie.
|
|
17
|
+
...base_1.environmentFlag,
|
|
18
|
+
value: core_1.Flags.string({ description: 'Secret value (use when the arg is just NAME)' }),
|
|
19
|
+
description: core_1.Flags.string({ description: 'Optional description' }),
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const { args, flags } = await this.parse(SharedSet);
|
|
23
|
+
const eq = args.assignment.indexOf('=');
|
|
24
|
+
const name = eq === -1 ? args.assignment : args.assignment.slice(0, eq);
|
|
25
|
+
const value = eq === -1 ? flags.value : args.assignment.slice(eq + 1);
|
|
26
|
+
if (value === undefined) {
|
|
27
|
+
this.error('Provide a value: use NAME=VALUE or pass --value', { exit: 1 });
|
|
28
|
+
}
|
|
29
|
+
const res = await this.api.post('/cli/v1/shared_secrets', {
|
|
30
|
+
environment: flags.environment,
|
|
31
|
+
name,
|
|
32
|
+
value,
|
|
33
|
+
description: flags.description,
|
|
34
|
+
});
|
|
35
|
+
if (!this.jsonEnabled()) {
|
|
36
|
+
this.log(`Shared secret ${String(name).toUpperCase()} saved (${flags.environment}).`);
|
|
37
|
+
}
|
|
38
|
+
return res;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.default = SharedSet;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class TicketsAsk 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
|
+
static flags: {
|
|
10
|
+
timeout: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<unknown>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
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 error_codes_1 = require("../../errors/error-codes");
|
|
6
|
+
const api_1 = require("../../lib/api");
|
|
7
|
+
const output_1 = require("../../lib/output");
|
|
8
|
+
const poll_1 = require("../../lib/poll");
|
|
9
|
+
class TicketsAsk extends base_1.BaseCommand {
|
|
10
|
+
// Variadic question: every positional word is joined into a single natural-language question.
|
|
11
|
+
static strict = false;
|
|
12
|
+
static description = 'Ask a natural-language question about the tickets you can see; the AI answers and cites the tickets it used (may take a few seconds)';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> tickets ask what is left before the next release',
|
|
15
|
+
'<%= config.bin %> tickets ask "has anyone reported this crash before?" --json',
|
|
16
|
+
];
|
|
17
|
+
// Non-required: with strict = false the arg validation is off, so we guard the empty case ourselves.
|
|
18
|
+
static args = {
|
|
19
|
+
question: core_1.Args.string({ description: 'The question (all positional words are joined)' }),
|
|
20
|
+
};
|
|
21
|
+
static flags = {
|
|
22
|
+
timeout: core_1.Flags.integer({
|
|
23
|
+
description: 'Seconds to wait for the answer before giving up',
|
|
24
|
+
default: poll_1.DEFAULT_TIMEOUT_SECONDS,
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
async run() {
|
|
28
|
+
const { argv, flags } = await this.parse(TicketsAsk);
|
|
29
|
+
const question = argv.join(' ').trim();
|
|
30
|
+
if (question === '') {
|
|
31
|
+
this.error('Provide a question (e.g. "tickets ask what is left before the next release").', { exit: 2 });
|
|
32
|
+
}
|
|
33
|
+
const invalidTimeout = (0, poll_1.timeoutError)(flags.timeout);
|
|
34
|
+
if (invalidTimeout)
|
|
35
|
+
this.error(invalidTimeout, { exit: 2 });
|
|
36
|
+
if (!this.jsonEnabled())
|
|
37
|
+
this.log('Asking your tickets (AI), this may take a few seconds…');
|
|
38
|
+
// The question is answered elsewhere: this call only queues it and hands back the id to follow.
|
|
39
|
+
const queued = await this.api.post('/cli/v1/tickets/ask', { question });
|
|
40
|
+
const requestId = queued.data?.request_id;
|
|
41
|
+
if (typeof requestId !== 'string' || requestId === '') {
|
|
42
|
+
throw new api_1.ApiRequestError(502, error_codes_1.ErrorCodes.Ask.failed, 'The server accepted the question without a request id: there is nothing to follow.');
|
|
43
|
+
}
|
|
44
|
+
const final = await (0, poll_1.pollUntil)(async () => (await this.api.get(`/cli/v1/ai/requests/${encodeURIComponent(requestId)}`)).data ?? {}, (state) => state.status !== 'pending', {
|
|
45
|
+
timeoutMs: flags.timeout * 1000,
|
|
46
|
+
// Not "ask again": the question is still being worked on as request `${requestId}`, and
|
|
47
|
+
// repeating it would queue a second one instead of picking that answer up. Waiting longer
|
|
48
|
+
// is the way through.
|
|
49
|
+
timeoutMessage: `No answer within ${flags.timeout}s: request ${requestId} is still being worked on. ` +
|
|
50
|
+
'Ask again with a longer --timeout (asking now would queue a second question, not collect this one).',
|
|
51
|
+
});
|
|
52
|
+
if (final.status === 'failed') {
|
|
53
|
+
throw new api_1.ApiRequestError(502, final.error?.code ?? error_codes_1.ErrorCodes.Ask.failed, final.error?.message ?? 'The question could not be answered (temporary problem upstream); try again in a moment.');
|
|
54
|
+
}
|
|
55
|
+
// Anything that is not `pending` ended the wait, but only `done` carries a result: a missing or
|
|
56
|
+
// unknown status would otherwise print as a successful empty answer.
|
|
57
|
+
if (final.status !== 'done') {
|
|
58
|
+
throw new api_1.ApiRequestError(502, error_codes_1.ErrorCodes.Ask.failed, `The server reported a request state this CLI does not know ("${final.status ?? 'none'}"): treat the answer as missing.`);
|
|
59
|
+
}
|
|
60
|
+
const result = final.result ?? {};
|
|
61
|
+
if (!this.jsonEnabled()) {
|
|
62
|
+
if (result.insufficient) {
|
|
63
|
+
this.log('The tickets you can see do not have enough to answer this confidently.');
|
|
64
|
+
}
|
|
65
|
+
const answer = String(result.answer ?? '').trim();
|
|
66
|
+
if (answer !== '')
|
|
67
|
+
this.log((0, output_1.sanitizeMultiline)(result.answer));
|
|
68
|
+
// The tickets the answer stands on: an answer that names its sources can be checked, one that
|
|
69
|
+
// does not asks to be believed.
|
|
70
|
+
const tickets = Array.isArray(result.tickets) ? result.tickets : [];
|
|
71
|
+
if (tickets.length > 0) {
|
|
72
|
+
this.log('\n🎫 Tickets:');
|
|
73
|
+
for (const ticket of tickets) {
|
|
74
|
+
this.log(` ${(0, output_1.sanitize)(ticket.code)} · ${(0, output_1.sanitize)(ticket.title)} · ${(0, output_1.sanitize)(ticket.status_label)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { data: { request_id: requestId, status: final.status, result } };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.default = TicketsAsk;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
/**
|
|
3
|
+
* Lo stato del ciclo di chiarimenti di un ticket, chiesto al sistema (CYRA-628).
|
|
4
|
+
*
|
|
5
|
+
* È il canale che sostituisce il ri-parsing della discussione: la skill di triage e l'automator
|
|
6
|
+
* leggevano un marcatore HTML nel testo dei commenti e provavano a indovinare se una domanda fosse
|
|
7
|
+
* pendente e a che giro. Una riga scritta dal sistema stesso bastava a farlo ripartire.
|
|
8
|
+
*
|
|
9
|
+
* Sola lettura: le domande le scrive il server quando il triage le consegna, non un chiamante esterno.
|
|
10
|
+
*/
|
|
11
|
+
export default class TicketsClarifications extends BaseCommand {
|
|
12
|
+
static args: {
|
|
13
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
14
|
+
};
|
|
15
|
+
static description: string;
|
|
16
|
+
static examples: string[];
|
|
17
|
+
static flags: {
|
|
18
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
|
+
};
|
|
20
|
+
run(): Promise<unknown>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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 ticket_clarifications_1 = require("../../lib/ticket-clarifications");
|
|
6
|
+
/**
|
|
7
|
+
* Lo stato del ciclo di chiarimenti di un ticket, chiesto al sistema (CYRA-628).
|
|
8
|
+
*
|
|
9
|
+
* È il canale che sostituisce il ri-parsing della discussione: la skill di triage e l'automator
|
|
10
|
+
* leggevano un marcatore HTML nel testo dei commenti e provavano a indovinare se una domanda fosse
|
|
11
|
+
* pendente e a che giro. Una riga scritta dal sistema stesso bastava a farlo ripartire.
|
|
12
|
+
*
|
|
13
|
+
* Sola lettura: le domande le scrive il server quando il triage le consegna, non un chiamante esterno.
|
|
14
|
+
*/
|
|
15
|
+
class TicketsClarifications extends base_1.BaseCommand {
|
|
16
|
+
static args = {
|
|
17
|
+
id: core_1.Args.string({ description: 'Ticket id or code (e.g. DRFL-3)', required: true }),
|
|
18
|
+
};
|
|
19
|
+
static description = 'Show the clarification cycle state of a ticket (rounds, reply, escalation)';
|
|
20
|
+
static examples = [
|
|
21
|
+
'<%= config.bin %> tickets clarifications <ticket-id> --project acme-api',
|
|
22
|
+
'<%= config.bin %> tickets clarifications DRFL-3 -p acme-api --json',
|
|
23
|
+
];
|
|
24
|
+
static flags = { ...base_1.projectFlag };
|
|
25
|
+
async run() {
|
|
26
|
+
const { args, flags } = await this.parse(TicketsClarifications);
|
|
27
|
+
const projectId = await this.resolveProjectId(flags.project);
|
|
28
|
+
const path = `/cli/v1/projects/${encodeURIComponent(projectId)}/tickets/${encodeURIComponent(args.id)}/clarifications`;
|
|
29
|
+
const res = await this.api.get(path);
|
|
30
|
+
if (!this.jsonEnabled()) {
|
|
31
|
+
this.log((0, ticket_clarifications_1.renderClarificationState)(res.data ?? {}));
|
|
32
|
+
}
|
|
33
|
+
return res;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.default = TicketsClarifications;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base';
|
|
2
|
+
export default class TicketsDependenciesAdd extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
blocker: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static examples: string[];
|
|
9
|
+
static flags: {
|
|
10
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<unknown>;
|
|
13
|
+
/** Il blocker si nomina col suo codice; l'elenco lo espone su `code`, salvo che sia oscurato. */
|
|
14
|
+
private dependencyExists;
|
|
15
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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 api_1 = require("../../../lib/api");
|
|
6
|
+
/** Il server risponde così quando l'arco che si sta creando non è ammesso — doppione compreso. */
|
|
7
|
+
const NOT_ALLOWED = 'R422-TICKET-017';
|
|
8
|
+
class TicketsDependenciesAdd extends base_1.BaseCommand {
|
|
9
|
+
static args = {
|
|
10
|
+
id: core_1.Args.string({ description: 'Ticket id or code (e.g. CYRA-132)', required: true }),
|
|
11
|
+
blocker: core_1.Args.string({ description: 'Prerequisite ticket id or code — may live in another project', required: true }),
|
|
12
|
+
};
|
|
13
|
+
static description = 'Add a prerequisite to a ticket: the blocker has to be finished before this ticket can start';
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> tickets dependencies add CYRA-132 CYAU-83 --project closeyourit-rails',
|
|
16
|
+
'while read -r a b; do <%= config.bin %> tickets dependencies add "$a" "$b" -p closeyourit-rails; done < ordine.txt',
|
|
17
|
+
];
|
|
18
|
+
static flags = { ...base_1.projectFlag };
|
|
19
|
+
async run() {
|
|
20
|
+
const { args, flags } = await this.parse(TicketsDependenciesAdd);
|
|
21
|
+
const projectId = await this.resolveProjectId(flags.project);
|
|
22
|
+
const base = `/cli/v1/projects/${encodeURIComponent(projectId)}/tickets/${encodeURIComponent(args.id)}/dependencies`;
|
|
23
|
+
// Gemello della guardia in `remove`: con l'argomento vuoto la rilettura combacerebbe con una riga
|
|
24
|
+
// oscurata (`code: null` vale "") e questo comando direbbe «già presente» uscendo 0 — un errore
|
|
25
|
+
// vero che passa per lavoro fatto, proprio dentro il file che si rilancia.
|
|
26
|
+
if (args.blocker.trim() === '') {
|
|
27
|
+
this.error('Il prerequisito va nominato: passa il suo codice, anche di un altro prodotto (es. CYAU-83).', { exit: 2 });
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const res = await this.api.post(base, { blocker: args.blocker });
|
|
31
|
+
if (!this.jsonEnabled())
|
|
32
|
+
this.log(`prerequisito aggiunto: ${args.id} ← ${args.blocker}`);
|
|
33
|
+
return res.data;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
// Rilanciare il file intero deve essere sicuro: un legame già scritto non è un errore, è il
|
|
37
|
+
// lavoro già fatto. Ma `R422-TICKET-017` non vuol dire solo «c'è già» — è la risposta a ogni
|
|
38
|
+
// arco non ammesso, e trattarla come «va bene» in blocco farebbe passare per riuscite anche le
|
|
39
|
+
// righe rifiutate per un altro motivo. Quindi si rilegge l'elenco: se l'arco c'è davvero si esce
|
|
40
|
+
// 0, altrimenti l'errore propaga.
|
|
41
|
+
//
|
|
42
|
+
// Il ciclo (R422-TICKET-013) e il prerequisito inesistente (R404-TICKET-015) propagano sempre:
|
|
43
|
+
// lì la riga è sbagliata e va vista, non ingoiata.
|
|
44
|
+
if (!(error instanceof api_1.ApiRequestError) || error.code !== NOT_ALLOWED)
|
|
45
|
+
throw error;
|
|
46
|
+
const gia = await this.dependencyExists(base, args.blocker);
|
|
47
|
+
if (!gia)
|
|
48
|
+
throw error;
|
|
49
|
+
if (!this.jsonEnabled())
|
|
50
|
+
this.log(`prerequisito già presente: ${args.id} ← ${args.blocker}`);
|
|
51
|
+
return { ticket: args.id, blocker: args.blocker, alreadyPresent: true };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Il blocker si nomina col suo codice; l'elenco lo espone su `code`, salvo che sia oscurato. */
|
|
55
|
+
async dependencyExists(base, blocker) {
|
|
56
|
+
const res = await this.api.get(base);
|
|
57
|
+
const cercato = blocker.toLowerCase();
|
|
58
|
+
return (res.data ?? []).some((row) => (typeof row.code === 'string' && row.code.toLowerCase() === cercato) ||
|
|
59
|
+
(typeof row.blocker_id === 'string' && row.blocker_id === blocker));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.default = TicketsDependenciesAdd;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base';
|
|
2
|
+
export default class TicketsDependenciesList extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<unknown>;
|
|
12
|
+
}
|
|
@@ -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 output_1 = require("../../../lib/output");
|
|
6
|
+
class TicketsDependenciesList extends base_1.BaseCommand {
|
|
7
|
+
static args = {
|
|
8
|
+
id: core_1.Args.string({ description: 'Ticket id or code (e.g. CYRA-132)', required: true }),
|
|
9
|
+
};
|
|
10
|
+
static description = 'List the prerequisites of a ticket: what has to be finished before this one can start';
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> tickets dependencies list CYRA-132 --project closeyourit-rails',
|
|
13
|
+
'<%= config.bin %> tickets dependencies list CYRA-132 -p closeyourit-rails --json',
|
|
14
|
+
];
|
|
15
|
+
static flags = { ...base_1.projectFlag };
|
|
16
|
+
async run() {
|
|
17
|
+
const { args, flags } = await this.parse(TicketsDependenciesList);
|
|
18
|
+
const projectId = await this.resolveProjectId(flags.project);
|
|
19
|
+
const path = `/cli/v1/projects/${encodeURIComponent(projectId)}/tickets/${encodeURIComponent(args.id)}/dependencies`;
|
|
20
|
+
const res = await this.api.get(path);
|
|
21
|
+
const rows = res.data ?? [];
|
|
22
|
+
if (!this.jsonEnabled()) {
|
|
23
|
+
// Un prerequisito in un prodotto che il tuo accesso non vede resta VISIBILE come fatto — esiste,
|
|
24
|
+
// ed è concluso o no — ma senza codice né titolo. È la stessa regola della pagina web: nascondere
|
|
25
|
+
// anche l'esistenza direbbe «questo ticket non è bloccato», che è falso e porta a partire.
|
|
26
|
+
this.log((0, output_1.renderTable)(['ID', 'BLOCKER', 'TITLE', 'STATUS', 'DONE'], rows.map((row) => [
|
|
27
|
+
(0, output_1.sanitize)(row.id ?? ''),
|
|
28
|
+
row.hidden === true ? '(nascosto)' : (0, output_1.sanitize)(row.code ?? ''),
|
|
29
|
+
row.hidden === true ? '(fuori dal tuo accesso)' : (0, output_1.sanitize)(row.title ?? ''),
|
|
30
|
+
(0, output_1.sanitize)(row.status?.label ?? row.status?.code ?? ''),
|
|
31
|
+
row.done === true ? 'sì' : 'no',
|
|
32
|
+
])));
|
|
33
|
+
}
|
|
34
|
+
return rows;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.default = TicketsDependenciesList;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base';
|
|
2
|
+
export default class TicketsDependenciesRemove extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
blocker: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static examples: string[];
|
|
9
|
+
static flags: {
|
|
10
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<unknown>;
|
|
13
|
+
private resolveDependencyId;
|
|
14
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
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 TicketsDependenciesRemove extends base_1.BaseCommand {
|
|
6
|
+
static args = {
|
|
7
|
+
id: core_1.Args.string({ description: 'Ticket id or code (e.g. CYRA-132)', required: true }),
|
|
8
|
+
blocker: core_1.Args.string({
|
|
9
|
+
description: 'Prerequisite to remove: its ticket code (e.g. CYAU-83) or the dependency id',
|
|
10
|
+
required: true,
|
|
11
|
+
}),
|
|
12
|
+
};
|
|
13
|
+
static description = 'Remove a prerequisite from a ticket';
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> tickets dependencies remove CYRA-132 CYAU-83 --project closeyourit-rails',
|
|
16
|
+
];
|
|
17
|
+
static flags = { ...base_1.projectFlag };
|
|
18
|
+
async run() {
|
|
19
|
+
const { args, flags } = await this.parse(TicketsDependenciesRemove);
|
|
20
|
+
const projectId = await this.resolveProjectId(flags.project);
|
|
21
|
+
const base = `/cli/v1/projects/${encodeURIComponent(projectId)}/tickets/${encodeURIComponent(args.id)}/dependencies`;
|
|
22
|
+
// Il server vuole l'UUID della dipendenza, che nessun comando ti ha mai mostrato: si accetta anche
|
|
23
|
+
// il CODICE del prerequisito — quello che hai scritto per aggiungerlo — e lo si risolve leggendo
|
|
24
|
+
// l'elenco. Nessun --confirm: l'arco si ricrea identico con una riga, e la riprovabilità è lo scopo
|
|
25
|
+
// di tutto questo lavoro.
|
|
26
|
+
// Un prerequisito NASCOSTO ha `code: null`, che confrontato come stringa vale "". Un argomento
|
|
27
|
+
// vuoto diventa quindi "" e combacerebbe con la prima riga oscurata: `remove <ticket> ""`
|
|
28
|
+
// cancellerebbe un prerequisito che chi lancia non ha nemmeno il diritto di vedere, in silenzio.
|
|
29
|
+
if (args.blocker.trim() === '') {
|
|
30
|
+
this.error('Il prerequisito da togliere va nominato: passa il suo codice (es. CYAU-83) o l\'id della dipendenza.', { exit: 2 });
|
|
31
|
+
}
|
|
32
|
+
const dependencyId = await this.resolveDependencyId(base, args.blocker);
|
|
33
|
+
if (!dependencyId) {
|
|
34
|
+
// Nessuna DELETE alla cieca: senza aver trovato l'arco, mandarla vorrebbe dire chiedere al server
|
|
35
|
+
// di cancellare un id che non abbiamo, e leggere il suo 404 come se fosse la nostra risposta.
|
|
36
|
+
this.error(`Nessun prerequisito «${args.blocker}» su ${args.id}: niente da togliere.`, { exit: 2 });
|
|
37
|
+
}
|
|
38
|
+
await this.api.delete(`${base}/${encodeURIComponent(dependencyId)}`);
|
|
39
|
+
if (!this.jsonEnabled())
|
|
40
|
+
this.log(`prerequisito rimosso: ${args.id} ← ${args.blocker}`);
|
|
41
|
+
return { ticket: args.id, blocker: args.blocker, removed: dependencyId };
|
|
42
|
+
}
|
|
43
|
+
async resolveDependencyId(base, blocker) {
|
|
44
|
+
const res = await this.api.get(base);
|
|
45
|
+
const righe = res.data ?? [];
|
|
46
|
+
const cercato = blocker.toLowerCase();
|
|
47
|
+
// Prima per codice del prerequisito, poi per id della dipendenza: chi ha già l'id (lo stampa
|
|
48
|
+
// `dependencies list`) non deve essere costretto a passare dal codice, che per un prerequisito
|
|
49
|
+
// oscurato non esiste nemmeno.
|
|
50
|
+
// `code` ASSENTE non combacia mai, con nessun argomento: è la riga oscurata, e l'unico modo di
|
|
51
|
+
// toglierla è nominarne l'id — quello che `dependencies list` stampa anche per lei.
|
|
52
|
+
const perCodice = righe.find((row) => typeof row.code === 'string' && row.code.toLowerCase() === cercato);
|
|
53
|
+
if (perCodice?.id)
|
|
54
|
+
return String(perCodice.id);
|
|
55
|
+
const perId = righe.find((row) => String(row.id ?? '') === blocker || String(row.blocker_id ?? '') === blocker);
|
|
56
|
+
return perId?.id ? String(perId.id) : null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.default = TicketsDependenciesRemove;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class TicketsWorkContext extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
full: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<unknown>;
|
|
13
|
+
/** Le due liste congelate: stessa tabella di `guidance show`, così il confronto con l'oggi è immediato. */
|
|
14
|
+
private renderPayload;
|
|
15
|
+
}
|