@bussolabs/closeyourit-cli 0.23.1 → 0.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/base.d.ts +12 -0
- package/dist/base.js +16 -0
- package/dist/commands/coverage/show.d.ts +10 -0
- package/dist/commands/coverage/show.js +39 -0
- package/dist/commands/tickets/clarifications.d.ts +21 -0
- package/dist/commands/tickets/clarifications.js +36 -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/lib/api.d.ts +12 -0
- package/dist/lib/api.js +25 -3
- package/dist/lib/ticket-clarifications.d.ts +13 -0
- package/dist/lib/ticket-clarifications.js +31 -0
- package/oclif.manifest.json +8325 -6114
- package/opencli.json +1658 -2
- package/package.json +106 -1
package/README.md
CHANGED
|
@@ -272,6 +272,9 @@ CLOSEYOURIT_TOKEN=cyi_u_… CLOSEYOURIT_API_URL=https://www.closeyour.it \
|
|
|
272
272
|
| `workload promote <action-id> --project <id\|key> [--title] [--description] [--kind] [--status] [--priority]` | Generate a ticket from the action and link it back. |
|
|
273
273
|
|
|
274
274
|
Every command accepts `--json` for machine-readable output and `--help` for usage details.
|
|
275
|
+
Every command also accepts `--yes`: the server refuses a write that goes through a dangerous
|
|
276
|
+
permission (deleting a project, importing or deleting secrets, …) with `R422-CONFIRM-001` until you
|
|
277
|
+
confirm it, and `--yes` is that confirmation. Reads never need it.
|
|
275
278
|
`--project` accepts either a project UUID or its key (matched case-insensitively).
|
|
276
279
|
|
|
277
280
|
`kb publish` needs a backend exposing the atomic Knowledge publication route; an older one fails with
|
package/dist/base.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command, type Interfaces } from '@oclif/core';
|
|
2
|
+
import type { ArgOutput, FlagOutput, Input, ParserOutput } from '@oclif/core/lib/interfaces/parser';
|
|
2
3
|
import { type CliConfig } from './lib/config';
|
|
3
4
|
import { CliApi } from './lib/api';
|
|
4
5
|
import { type TicketLookup } from './lib/ticket-lookup';
|
|
@@ -28,9 +29,20 @@ export declare function paginationError(opts: {
|
|
|
28
29
|
}): string | undefined;
|
|
29
30
|
export declare abstract class BaseCommand extends Command {
|
|
30
31
|
static enableJsonFlag: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* `--yes` on every command (CYCL-53): the server asks an explicit confirmation for writes that pass
|
|
34
|
+
* through a dangerous permission key (`R422-CONFIRM-001`, CYRA-728). Which keys are dangerous is
|
|
35
|
+
* the server's catalogue, not ours, so the flag is global instead of being sprinkled on the
|
|
36
|
+
* commands we believe are risky today. Harmless elsewhere: the parameter is simply ignored.
|
|
37
|
+
*/
|
|
38
|
+
static baseFlags: {
|
|
39
|
+
yes: Interfaces.BooleanFlag<boolean>;
|
|
40
|
+
};
|
|
31
41
|
protected cfg: CliConfig;
|
|
32
42
|
protected api: CliApi;
|
|
33
43
|
init(): Promise<void>;
|
|
44
|
+
/** Parse as oclif does, then hand `--yes` to the api client before any request leaves. */
|
|
45
|
+
protected parse<F extends FlagOutput, B extends FlagOutput, A extends ArgOutput>(options?: Input<F, B, A>, argv?: string[]): Promise<ParserOutput<F, B, A>>;
|
|
34
46
|
/**
|
|
35
47
|
* Resolve a project reference (UUID passes through; otherwise matched by key, case-insensitive).
|
|
36
48
|
* Walks every page of `/cli/v1/projects`: a key can live past the first page, so a single-page
|
package/dist/base.js
CHANGED
|
@@ -38,6 +38,15 @@ function paginationError(opts) {
|
|
|
38
38
|
}
|
|
39
39
|
class BaseCommand extends core_1.Command {
|
|
40
40
|
static enableJsonFlag = true;
|
|
41
|
+
/**
|
|
42
|
+
* `--yes` on every command (CYCL-53): the server asks an explicit confirmation for writes that pass
|
|
43
|
+
* through a dangerous permission key (`R422-CONFIRM-001`, CYRA-728). Which keys are dangerous is
|
|
44
|
+
* the server's catalogue, not ours, so the flag is global instead of being sprinkled on the
|
|
45
|
+
* commands we believe are risky today. Harmless elsewhere: the parameter is simply ignored.
|
|
46
|
+
*/
|
|
47
|
+
static baseFlags = {
|
|
48
|
+
yes: core_1.Flags.boolean({ description: 'Confirm a dangerous action the server would otherwise refuse (R422-CONFIRM-001)', default: false }),
|
|
49
|
+
};
|
|
41
50
|
cfg;
|
|
42
51
|
api;
|
|
43
52
|
async init() {
|
|
@@ -45,6 +54,13 @@ class BaseCommand extends core_1.Command {
|
|
|
45
54
|
this.cfg = (0, config_1.loadConfig)();
|
|
46
55
|
this.api = new api_1.CliApi(this.cfg);
|
|
47
56
|
}
|
|
57
|
+
/** Parse as oclif does, then hand `--yes` to the api client before any request leaves. */
|
|
58
|
+
async parse(options, argv) {
|
|
59
|
+
const parsed = await super.parse(options, argv);
|
|
60
|
+
if (this.api)
|
|
61
|
+
this.api.confirm = parsed.flags.yes === true;
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
48
64
|
/**
|
|
49
65
|
* Resolve a project reference (UUID passes through; otherwise matched by key, case-insensitive).
|
|
50
66
|
* Walks every page of `/cli/v1/projects`: a key can live past the first page, so a single-page
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class CoverageShow extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
branch: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<unknown>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const base_1 = require("../../base");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
// CYSK-26 — l'estratto di copertura pubblicato dalla CI, riletto senza rigirare la suite: è il dato
|
|
6
|
+
// su cui lo scanner del codice non usato decide se la misura è credibile prima di fidarsene.
|
|
7
|
+
class CoverageShow extends base_1.BaseCommand {
|
|
8
|
+
static description = 'Show the coverage extract the CI published for a project (last snapshot per branch, never the history)';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> coverage show --project closeyourit-rails',
|
|
11
|
+
'<%= config.bin %> coverage show -p CYRA --branch main',
|
|
12
|
+
'<%= config.bin %> coverage show -p CYRA --json',
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
...base_1.projectFlag,
|
|
16
|
+
branch: core_1.Flags.string({ description: 'Only the extract of this branch' }),
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
const { flags } = await this.parse(CoverageShow);
|
|
20
|
+
const projectId = await this.resolveProjectId(flags.project);
|
|
21
|
+
const query = flags.branch ? `?branch=${encodeURIComponent(flags.branch)}` : '';
|
|
22
|
+
const res = await this.api.get(`/cli/v1/projects/${projectId}/coverage${query}`);
|
|
23
|
+
if (!this.jsonEnabled()) {
|
|
24
|
+
const reports = res.data ?? [];
|
|
25
|
+
if (reports.length === 0) {
|
|
26
|
+
this.log('No coverage extract published yet: the CI pushes it from the default branch.');
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
for (const report of reports) {
|
|
30
|
+
this.log(`${report.branch ?? '?'} line ${report.line_covered_percent ?? '-'}% branch ${report.branch_covered_percent ?? '-'}% ` +
|
|
31
|
+
`files ${report.files_count ?? '-'} never_loaded ${report.never_loaded_count ?? '-'} ` +
|
|
32
|
+
`sha ${report.sha ?? '-'} captured ${report.captured_at ?? '-'}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return res;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.default = CoverageShow;
|
|
@@ -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,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class UsageList extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
kind: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
+
environment: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
page: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<unknown>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const base_1 = require("../../base");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
// CYSK-29 — i simboli VISTI in esecuzione (l'insieme positivo). Il verdetto «inutilizzato» lo
|
|
6
|
+
// calcola lo scanner con l'inventario statico del repo, mai questo comando.
|
|
7
|
+
class UsageList extends base_1.BaseCommand {
|
|
8
|
+
static description = 'List the usage symbols the product reported as actually executed (routes as Controller#action, jobs, custom keys)';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> usage list --project CYRA',
|
|
11
|
+
'<%= config.bin %> usage list -p CYRA --kind route --environment production',
|
|
12
|
+
'<%= config.bin %> usage list -p CYRA --json',
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
...base_1.projectFlag,
|
|
16
|
+
kind: core_1.Flags.string({ description: 'Only this kind (route, job, custom)' }),
|
|
17
|
+
environment: core_1.Flags.string({ description: 'Only this environment' }),
|
|
18
|
+
page: core_1.Flags.integer({ description: 'Page number', default: 1 }),
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { flags } = await this.parse(UsageList);
|
|
22
|
+
const projectId = await this.resolveProjectId(flags.project);
|
|
23
|
+
const query = new URLSearchParams({ page: String(flags.page) });
|
|
24
|
+
if (flags.kind)
|
|
25
|
+
query.set('kind', flags.kind);
|
|
26
|
+
if (flags.environment)
|
|
27
|
+
query.set('environment', flags.environment);
|
|
28
|
+
const res = await this.api.get(`/cli/v1/projects/${projectId}/usage?${query.toString()}`);
|
|
29
|
+
if (!this.jsonEnabled()) {
|
|
30
|
+
const rows = res.data ?? [];
|
|
31
|
+
if (rows.length === 0) {
|
|
32
|
+
this.log('No usage reported yet: the SDK flushes every ~5 minutes once deployed.');
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
for (const row of rows) {
|
|
36
|
+
this.log(`${row.kind ?? '?'} ${row.symbol ?? '?'} last_seen ${row.last_seen_at ?? '-'} hits ~${row.hits_count ?? '-'} (${row.environment ?? '-'})`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return res;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.default = UsageList;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class UsageReporters extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
kind: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<unknown>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const base_1 = require("../../base");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
// CYSK-29 — chi sta mandando usage e da quando: la risposta a «rotta mai chiamata o SDK mai
|
|
6
|
+
// deployato?». Lo scanner la legge PRIMA di giudicare qualunque kind; `truncated_last_window`
|
|
7
|
+
// squalifica il kind.
|
|
8
|
+
class UsageReporters extends base_1.BaseCommand {
|
|
9
|
+
static description = 'List the usage reporters: which SDK is sending usage of each kind, and since when';
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> usage reporters --project CYRA',
|
|
12
|
+
'<%= config.bin %> usage reporters -p CYRA --kind route',
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
...base_1.projectFlag,
|
|
16
|
+
kind: core_1.Flags.string({ description: 'Only this kind (route, job, custom)' }),
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
const { flags } = await this.parse(UsageReporters);
|
|
20
|
+
const projectId = await this.resolveProjectId(flags.project);
|
|
21
|
+
const query = flags.kind ? `?kind=${encodeURIComponent(flags.kind)}` : '';
|
|
22
|
+
const res = await this.api.get(`/cli/v1/projects/${projectId}/usage/reporters${query}`);
|
|
23
|
+
if (!this.jsonEnabled()) {
|
|
24
|
+
const rows = res.data ?? [];
|
|
25
|
+
if (rows.length === 0) {
|
|
26
|
+
this.log('No usage reporter yet: without one, "never seen" only means "SDK not deployed".');
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
for (const row of rows) {
|
|
30
|
+
this.log(`${row.kind ?? '?'} ${row.sdk_name ?? '?'}@${row.sdk_version ?? '-'} since ${row.first_reported_at ?? '-'} last ${row.last_reported_at ?? '-'} truncated ${row.truncated_last_window ? 'YES' : 'no'} (${row.environment ?? '-'})`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return res;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.default = UsageReporters;
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface UploadFile {
|
|
|
23
23
|
data: Blob;
|
|
24
24
|
}
|
|
25
25
|
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
26
|
+
/** Error code the server answers when a dangerous write arrives without the confirm gesture (CYRA-728). */
|
|
27
|
+
export declare const CONFIRMATION_REQUIRED_CODE = "R422-CONFIRM-001";
|
|
26
28
|
export declare class CliApi {
|
|
27
29
|
private readonly config;
|
|
28
30
|
/**
|
|
@@ -32,6 +34,14 @@ export declare class CliApi {
|
|
|
32
34
|
* commands (CYCL-11). The instance is per-command, so this is that command's last response.
|
|
33
35
|
*/
|
|
34
36
|
lastMeta?: Record<string, unknown>;
|
|
37
|
+
/**
|
|
38
|
+
* The user confirmed dangerous actions (`--yes`, CYCL-53). The server (CYRA-728) refuses any
|
|
39
|
+
* write through a `dangerous` permission key without the `confirm` parameter and answers
|
|
40
|
+
* `R422-CONFIRM-001`: not a permission problem, a missing gesture. `BaseCommand.parse` sets it;
|
|
41
|
+
* `request()` then adds `confirm=1` to every writing call — in the query string, so DELETE
|
|
42
|
+
* without a body is covered too. Reads never carry it: looking is not executing.
|
|
43
|
+
*/
|
|
44
|
+
confirm: boolean;
|
|
35
45
|
constructor(config: CliConfig);
|
|
36
46
|
request<T = unknown>(method: HttpMethod, path: string, opts?: RequestOptions): Promise<Envelope<T>>;
|
|
37
47
|
/**
|
|
@@ -39,6 +49,8 @@ export declare class CliApi {
|
|
|
39
49
|
* The boundary Content-Type is derived by fetch from the FormData body — never set it by hand.
|
|
40
50
|
*/
|
|
41
51
|
upload<T = unknown>(method: 'POST' | 'PUT', path: string, files: UploadFile[], opts?: RequestOptions): Promise<Envelope<T>>;
|
|
52
|
+
/** Add `confirm=1` to a writing request when the user passed `--yes`; reads are left alone. */
|
|
53
|
+
private withConfirmation;
|
|
42
54
|
private resolveUrl;
|
|
43
55
|
/** Build the shared Accept + Authorization headers; throws (before any fetch) if an authed call has no token. */
|
|
44
56
|
private authHeaders;
|
package/dist/lib/api.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CliApi = exports.ApiRequestError = void 0;
|
|
3
|
+
exports.CliApi = exports.CONFIRMATION_REQUIRED_CODE = exports.ApiRequestError = void 0;
|
|
4
4
|
const error_codes_1 = require("../errors/error-codes");
|
|
5
5
|
/** Error carrying the HTTP status and the structured code from the backend envelope (R/G prefix) or a local C-prefix fallback. */
|
|
6
6
|
class ApiRequestError extends Error {
|
|
@@ -16,6 +16,9 @@ class ApiRequestError extends Error {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
exports.ApiRequestError = ApiRequestError;
|
|
19
|
+
/** Error code the server answers when a dangerous write arrives without the confirm gesture (CYRA-728). */
|
|
20
|
+
exports.CONFIRMATION_REQUIRED_CODE = 'R422-CONFIRM-001';
|
|
21
|
+
const READ_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
19
22
|
class CliApi {
|
|
20
23
|
config;
|
|
21
24
|
/**
|
|
@@ -25,13 +28,21 @@ class CliApi {
|
|
|
25
28
|
* commands (CYCL-11). The instance is per-command, so this is that command's last response.
|
|
26
29
|
*/
|
|
27
30
|
lastMeta;
|
|
31
|
+
/**
|
|
32
|
+
* The user confirmed dangerous actions (`--yes`, CYCL-53). The server (CYRA-728) refuses any
|
|
33
|
+
* write through a `dangerous` permission key without the `confirm` parameter and answers
|
|
34
|
+
* `R422-CONFIRM-001`: not a permission problem, a missing gesture. `BaseCommand.parse` sets it;
|
|
35
|
+
* `request()` then adds `confirm=1` to every writing call — in the query string, so DELETE
|
|
36
|
+
* without a body is covered too. Reads never carry it: looking is not executing.
|
|
37
|
+
*/
|
|
38
|
+
confirm = false;
|
|
28
39
|
constructor(config) {
|
|
29
40
|
this.config = config;
|
|
30
41
|
}
|
|
31
42
|
async request(method, path, opts = {}) {
|
|
32
43
|
const headers = this.authHeaders(opts.auth !== false);
|
|
33
44
|
headers['Content-Type'] = 'application/json';
|
|
34
|
-
const url = this.resolveUrl(path);
|
|
45
|
+
const url = this.resolveUrl(this.withConfirmation(method, path));
|
|
35
46
|
let res;
|
|
36
47
|
try {
|
|
37
48
|
res = await fetch(url, {
|
|
@@ -70,6 +81,12 @@ class CliApi {
|
|
|
70
81
|
}
|
|
71
82
|
return this.handleResponse(res);
|
|
72
83
|
}
|
|
84
|
+
/** Add `confirm=1` to a writing request when the user passed `--yes`; reads are left alone. */
|
|
85
|
+
withConfirmation(method, path) {
|
|
86
|
+
if (!this.confirm || READ_METHODS.has(method))
|
|
87
|
+
return path;
|
|
88
|
+
return `${path}${path.includes('?') ? '&' : '?'}confirm=1`;
|
|
89
|
+
}
|
|
73
90
|
resolveUrl(path) {
|
|
74
91
|
return this.config.apiUrl.replace(/\/+$/, '') + path;
|
|
75
92
|
}
|
|
@@ -102,7 +119,12 @@ class CliApi {
|
|
|
102
119
|
if (!res.ok) {
|
|
103
120
|
const envelope = (json ?? {});
|
|
104
121
|
const error = envelope.error ?? {};
|
|
105
|
-
|
|
122
|
+
let message = error.message ?? res.statusText ?? `Request failed (${res.status})`;
|
|
123
|
+
// The server names the gesture it wants but not the flag that makes it: say it here, once.
|
|
124
|
+
if (error.code === exports.CONFIRMATION_REQUIRED_CODE && !message.includes('--yes')) {
|
|
125
|
+
message = `${message} Re-run with --yes to confirm this dangerous action.`;
|
|
126
|
+
}
|
|
127
|
+
throw new ApiRequestError(res.status, error.code ?? `C${res.status}-API-000`, message, error.details);
|
|
106
128
|
}
|
|
107
129
|
const envelope = (json ?? {});
|
|
108
130
|
this.lastMeta = envelope.meta;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lo stato del ciclo di chiarimenti, come lo racconta il SERVER (CYRA-628).
|
|
3
|
+
*
|
|
4
|
+
* Fino a qui quello stato viveva nel testo della discussione: un marcatore HTML che tre repository
|
|
5
|
+
* ri-parsavano per conto loro. Bastava che il sistema stesso scrivesse una riga qualsiasi — «resoconto
|
|
6
|
+
* aggiornato alla versione 2» — perché chi legge la scambiasse per una risposta e facesse ripartire un
|
|
7
|
+
* ticket a cui non aveva risposto nessuno. Intanto l'archivio continuava a dire «in attesa»: due parti
|
|
8
|
+
* dello stesso prodotto raccontavano cose diverse sullo stesso ticket, e decideva quella che sapeva meno.
|
|
9
|
+
*
|
|
10
|
+
* Il LIMITE di giri non compare qui e non compare nella risposta: il server espone i fatti — quanti
|
|
11
|
+
* giri, se è arrivata una risposta — e quando chiamare una persona resta policy di chi legge.
|
|
12
|
+
*/
|
|
13
|
+
export declare function renderClarificationState(stato: Record<string, unknown>): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderClarificationState = renderClarificationState;
|
|
4
|
+
const output_1 = require("./output");
|
|
5
|
+
/**
|
|
6
|
+
* Lo stato del ciclo di chiarimenti, come lo racconta il SERVER (CYRA-628).
|
|
7
|
+
*
|
|
8
|
+
* Fino a qui quello stato viveva nel testo della discussione: un marcatore HTML che tre repository
|
|
9
|
+
* ri-parsavano per conto loro. Bastava che il sistema stesso scrivesse una riga qualsiasi — «resoconto
|
|
10
|
+
* aggiornato alla versione 2» — perché chi legge la scambiasse per una risposta e facesse ripartire un
|
|
11
|
+
* ticket a cui non aveva risposto nessuno. Intanto l'archivio continuava a dire «in attesa»: due parti
|
|
12
|
+
* dello stesso prodotto raccontavano cose diverse sullo stesso ticket, e decideva quella che sapeva meno.
|
|
13
|
+
*
|
|
14
|
+
* Il LIMITE di giri non compare qui e non compare nella risposta: il server espone i fatti — quanti
|
|
15
|
+
* giri, se è arrivata una risposta — e quando chiamare una persona resta policy di chi legge.
|
|
16
|
+
*/
|
|
17
|
+
function renderClarificationState(stato) {
|
|
18
|
+
const rounds = Array.isArray(stato.rounds) ? stato.rounds : [];
|
|
19
|
+
const testa = `── ${(0, output_1.sanitize)(stato.state ?? '?')} · giri: ${(0, output_1.sanitize)(stato.cycles ?? 0)} · risposta: ${stato.has_reply ? 'sì' : 'no'}`;
|
|
20
|
+
if (rounds.length === 0)
|
|
21
|
+
return `${testa}\nNessuna domanda in corso.`;
|
|
22
|
+
const corpo = rounds.map((round) => {
|
|
23
|
+
const quando = round.answered_at ? `risposto ${(0, output_1.sanitize)(round.answered_at)}` : 'senza risposta';
|
|
24
|
+
return [
|
|
25
|
+
`\n· giro ${(0, output_1.sanitize)(round.cycle ?? '?')} — ${quando}`,
|
|
26
|
+
(0, output_1.sanitizeMultiline)(round.questions ?? ''),
|
|
27
|
+
round.response ? (0, output_1.sanitizeMultiline)(round.response) : '',
|
|
28
|
+
].filter(Boolean).join('\n');
|
|
29
|
+
});
|
|
30
|
+
return [testa, ...corpo].join('\n');
|
|
31
|
+
}
|