kantban-cli 0.1.15 → 0.1.16
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/dist/chunk-4IUZAIFL.js +102 -0
- package/dist/chunk-4IUZAIFL.js.map +1 -0
- package/dist/chunk-CQP4B53A.js +140 -0
- package/dist/chunk-CQP4B53A.js.map +1 -0
- package/dist/chunk-FF77FM7X.js +1159 -0
- package/dist/chunk-FF77FM7X.js.map +1 -0
- package/dist/chunk-MN4H5NSU.js +3149 -0
- package/dist/chunk-MN4H5NSU.js.map +1 -0
- package/dist/{cron-AZPDPON3.js → cron-FJVZR2JW.js} +10 -18
- package/dist/cron-FJVZR2JW.js.map +1 -0
- package/dist/index.js +6 -138
- package/dist/index.js.map +1 -1
- package/dist/lib/gate-proxy-server.d.ts +1 -0
- package/dist/lib/gate-proxy-server.js +462 -0
- package/dist/lib/gate-proxy-server.js.map +1 -0
- package/dist/{pipeline-UNO4PIW4.js → pipeline-6SDPVNFK.js} +494 -366
- package/dist/pipeline-6SDPVNFK.js.map +1 -0
- package/package.json +3 -1
- package/dist/chunk-KGS3M2MY.js +0 -4067
- package/dist/chunk-KGS3M2MY.js.map +0 -1
- package/dist/cron-AZPDPON3.js.map +0 -1
- package/dist/pipeline-UNO4PIW4.js.map +0 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseTimeout
|
|
3
|
+
} from "./chunk-MN4H5NSU.js";
|
|
4
|
+
|
|
5
|
+
// src/lib/gate-runner.ts
|
|
6
|
+
import { execFile } from "child_process";
|
|
7
|
+
async function runGate(gate, options = {}) {
|
|
8
|
+
const timeoutMs = gate.timeout ? parseTimeout(gate.timeout) : options.timeoutMs ?? 6e4;
|
|
9
|
+
const start = Date.now();
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
const child = execFile("sh", ["-c", gate.run], {
|
|
12
|
+
timeout: timeoutMs,
|
|
13
|
+
cwd: options.cwd,
|
|
14
|
+
env: { ...process.env, ...options.env },
|
|
15
|
+
maxBuffer: options.maxBuffer ?? 1024 * 1024
|
|
16
|
+
// 1MB output cap
|
|
17
|
+
}, (error, stdout, stderr) => {
|
|
18
|
+
const duration_ms = Date.now() - start;
|
|
19
|
+
const output = (stdout + stderr).trim();
|
|
20
|
+
const timed_out = error?.killed === true;
|
|
21
|
+
const buffer_exceeded = error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
|
|
22
|
+
if (error) {
|
|
23
|
+
let annotation = "";
|
|
24
|
+
if (timed_out) annotation = `
|
|
25
|
+
[TIMED OUT after ${timeoutMs}ms]`;
|
|
26
|
+
else if (buffer_exceeded) annotation = `
|
|
27
|
+
[OUTPUT TRUNCATED \u2014 buffer limit exceeded]`;
|
|
28
|
+
resolve({
|
|
29
|
+
name: gate.name,
|
|
30
|
+
passed: false,
|
|
31
|
+
required: gate.required ?? true,
|
|
32
|
+
duration_ms,
|
|
33
|
+
output: output + annotation,
|
|
34
|
+
stderr: stderr.trim(),
|
|
35
|
+
exit_code: child.exitCode ?? (typeof error.code === "number" ? error.code : 1),
|
|
36
|
+
timed_out: timed_out || buffer_exceeded
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
resolve({
|
|
41
|
+
name: gate.name,
|
|
42
|
+
passed: true,
|
|
43
|
+
required: gate.required ?? true,
|
|
44
|
+
duration_ms,
|
|
45
|
+
output,
|
|
46
|
+
stderr: stderr.trim(),
|
|
47
|
+
exit_code: 0,
|
|
48
|
+
timed_out: false
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function formatGateErrors(results) {
|
|
54
|
+
const failures = results.filter((r) => !r.passed);
|
|
55
|
+
if (failures.length === 0) return "All gates passed.";
|
|
56
|
+
return failures.map((r) => {
|
|
57
|
+
const lines = [`Gate "${r.name}" (${r.required ? "required" : "advisory"}): FAILED`];
|
|
58
|
+
lines.push(` Exit code: ${r.exit_code}`);
|
|
59
|
+
if (r.timed_out) lines.push(" Timed out: yes");
|
|
60
|
+
if (r.output) {
|
|
61
|
+
const stderr = r.output.split("\n").slice(-20).join("\n");
|
|
62
|
+
lines.push(` Output:
|
|
63
|
+
${stderr.replace(/\n/g, "\n ")}`);
|
|
64
|
+
}
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}).join("\n\n");
|
|
67
|
+
}
|
|
68
|
+
async function runGates(gates, options = {}) {
|
|
69
|
+
const results = [];
|
|
70
|
+
const totalStart = Date.now();
|
|
71
|
+
for (const gate of gates) {
|
|
72
|
+
if (options.totalTimeoutMs) {
|
|
73
|
+
const elapsed = Date.now() - totalStart;
|
|
74
|
+
if (elapsed >= options.totalTimeoutMs) {
|
|
75
|
+
results.push({
|
|
76
|
+
name: gate.name,
|
|
77
|
+
passed: false,
|
|
78
|
+
required: gate.required ?? true,
|
|
79
|
+
duration_ms: 0,
|
|
80
|
+
output: "[SKIPPED \u2014 total timeout exceeded]",
|
|
81
|
+
stderr: "",
|
|
82
|
+
exit_code: -1,
|
|
83
|
+
timed_out: true
|
|
84
|
+
});
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const remainingMs = options.totalTimeoutMs - elapsed;
|
|
88
|
+
const gateTimeout = gate.timeout ? parseTimeout(gate.timeout) : options.timeoutMs ?? 6e4;
|
|
89
|
+
const effectiveTimeout = Math.min(gateTimeout, remainingMs);
|
|
90
|
+
results.push(await runGate(gate, { ...options, timeoutMs: effectiveTimeout }));
|
|
91
|
+
} else {
|
|
92
|
+
results.push(await runGate(gate, options));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return results;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export {
|
|
99
|
+
formatGateErrors,
|
|
100
|
+
runGates
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=chunk-4IUZAIFL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/gate-runner.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { parseTimeout } from './gate-config.js';\nimport type { GateDefinition, GateResult } from '@kantban/types';\n\nexport interface RunOptions {\n timeoutMs?: number;\n totalTimeoutMs?: number;\n cwd?: string;\n env?: Record<string, string>;\n maxBuffer?: number;\n}\n\nexport async function runGate(\n gate: GateDefinition,\n options: RunOptions = {},\n): Promise<GateResult> {\n const timeoutMs = gate.timeout ? parseTimeout(gate.timeout) : (options.timeoutMs ?? 60_000);\n const start = Date.now();\n\n return new Promise<GateResult>((resolve) => {\n // SECURITY: gate.run is an operator-authored shell command from pipeline.gates.yaml.\n // It is executed with full process privileges via `sh -c`. This is intentional —\n // gates are trusted operator code, not user input. If gate config sources are ever\n // extended to accept user-supplied values, sanitization must be added here.\n const child = execFile('sh', ['-c', gate.run], {\n timeout: timeoutMs,\n cwd: options.cwd,\n env: { ...process.env, ...options.env },\n maxBuffer: options.maxBuffer ?? 1024 * 1024, // 1MB output cap\n }, (error, stdout, stderr) => {\n const duration_ms = Date.now() - start;\n const output = (stdout + stderr).trim();\n const timed_out = error?.killed === true;\n const buffer_exceeded = (error as NodeJS.ErrnoException)?.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER';\n\n if (error) {\n let annotation = '';\n if (timed_out) annotation = `\\n[TIMED OUT after ${timeoutMs}ms]`;\n else if (buffer_exceeded) annotation = `\\n[OUTPUT TRUNCATED — buffer limit exceeded]`;\n\n resolve({\n name: gate.name,\n passed: false,\n required: gate.required ?? true,\n duration_ms,\n output: output + annotation,\n stderr: stderr.trim(),\n exit_code: child.exitCode ?? (typeof error.code === 'number' ? error.code : 1),\n timed_out: timed_out || buffer_exceeded,\n });\n return;\n }\n\n resolve({\n name: gate.name,\n passed: true,\n required: gate.required ?? true,\n duration_ms,\n output,\n stderr: stderr.trim(),\n exit_code: 0,\n timed_out: false,\n });\n });\n });\n}\n\n/** Format gate failures into a structured error string for agent/CLI display. */\nexport function formatGateErrors(results: GateResult[]): string {\n const failures = results.filter((r) => !r.passed);\n if (failures.length === 0) return 'All gates passed.';\n\n return failures\n .map((r) => {\n const lines = [`Gate \"${r.name}\" (${r.required ? 'required' : 'advisory'}): FAILED`];\n lines.push(` Exit code: ${r.exit_code}`);\n if (r.timed_out) lines.push(' Timed out: yes');\n if (r.output) {\n const stderr = r.output.split('\\n').slice(-20).join('\\n');\n lines.push(` Output:\\n ${stderr.replace(/\\n/g, '\\n ')}`);\n }\n return lines.join('\\n');\n })\n .join('\\n\\n');\n}\n\nexport async function runGates(\n gates: GateDefinition[],\n options: RunOptions = {},\n): Promise<GateResult[]> {\n const results: GateResult[] = [];\n const totalStart = Date.now();\n\n for (const gate of gates) {\n // Check total timeout\n if (options.totalTimeoutMs) {\n const elapsed = Date.now() - totalStart;\n if (elapsed >= options.totalTimeoutMs) {\n // Remaining gates get timed-out results\n results.push({\n name: gate.name,\n passed: false,\n required: gate.required ?? true,\n duration_ms: 0,\n output: '[SKIPPED — total timeout exceeded]',\n stderr: '',\n exit_code: -1,\n timed_out: true,\n });\n continue;\n }\n // Reduce per-gate timeout by elapsed time\n const remainingMs = options.totalTimeoutMs - elapsed;\n const gateTimeout = gate.timeout ? parseTimeout(gate.timeout) : (options.timeoutMs ?? 60_000);\n const effectiveTimeout = Math.min(gateTimeout, remainingMs);\n results.push(await runGate(gate, { ...options, timeoutMs: effectiveTimeout }));\n } else {\n results.push(await runGate(gate, options));\n }\n }\n\n return results;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
3
|
+
var MAX_RETRIES = 2;
|
|
4
|
+
var RETRY_BASE_MS = 500;
|
|
5
|
+
function isRetryableStatus(status) {
|
|
6
|
+
return status >= 500 || status === 429;
|
|
7
|
+
}
|
|
8
|
+
async function fetchWithRetry(url, init, retries = MAX_RETRIES, _sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
9
|
+
let lastError;
|
|
10
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(url, { ...init, signal: controller.signal });
|
|
15
|
+
clearTimeout(timer);
|
|
16
|
+
if (res.ok || !isRetryableStatus(res.status)) {
|
|
17
|
+
return res;
|
|
18
|
+
}
|
|
19
|
+
lastError = new Error(`API error ${res.status}: ${await res.text()}`);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
lastError = err;
|
|
23
|
+
}
|
|
24
|
+
if (attempt < retries) {
|
|
25
|
+
const delay = RETRY_BASE_MS * 2 ** attempt * (1 + Math.random() * 0.5);
|
|
26
|
+
await _sleep(delay);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
throw lastError;
|
|
30
|
+
}
|
|
31
|
+
var KantBanCLIClient = class {
|
|
32
|
+
constructor(apiUrl, apiToken) {
|
|
33
|
+
this.apiUrl = apiUrl;
|
|
34
|
+
this.apiToken = apiToken;
|
|
35
|
+
}
|
|
36
|
+
get baseUrl() {
|
|
37
|
+
return this.apiUrl;
|
|
38
|
+
}
|
|
39
|
+
get token() {
|
|
40
|
+
return this.apiToken;
|
|
41
|
+
}
|
|
42
|
+
async get(path, params) {
|
|
43
|
+
const url = new URL(path, this.apiUrl);
|
|
44
|
+
if (params) {
|
|
45
|
+
for (const [k, v] of Object.entries(params)) {
|
|
46
|
+
if (v !== void 0) url.searchParams.set(k, String(v));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const res = await fetchWithRetry(url.toString(), {
|
|
50
|
+
headers: {
|
|
51
|
+
"Authorization": `Bearer ${this.apiToken}`,
|
|
52
|
+
"X-KantBan-Via": "cli"
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);
|
|
56
|
+
const json = await res.json();
|
|
57
|
+
if (!json.success) throw new Error(`API responded with success: false`);
|
|
58
|
+
return json.data;
|
|
59
|
+
}
|
|
60
|
+
async post(path, body) {
|
|
61
|
+
const url = new URL(path, this.apiUrl);
|
|
62
|
+
const headers = {
|
|
63
|
+
"Authorization": `Bearer ${this.apiToken}`,
|
|
64
|
+
"X-KantBan-Via": "cli"
|
|
65
|
+
};
|
|
66
|
+
const init = { method: "POST", headers };
|
|
67
|
+
if (body) {
|
|
68
|
+
headers["Content-Type"] = "application/json";
|
|
69
|
+
init.body = JSON.stringify(body);
|
|
70
|
+
}
|
|
71
|
+
const res = await fetchWithRetry(url.toString(), init);
|
|
72
|
+
if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);
|
|
73
|
+
const json = await res.json();
|
|
74
|
+
if (!json.success) throw new Error(`API responded with success: false`);
|
|
75
|
+
return json.data;
|
|
76
|
+
}
|
|
77
|
+
async claimTicket(projectId, ticketId) {
|
|
78
|
+
await this.post(`/projects/${projectId}/tickets/${ticketId}/start`, {});
|
|
79
|
+
}
|
|
80
|
+
async getFingerprint(projectId, ticketId) {
|
|
81
|
+
return this.get(`/projects/${projectId}/tickets/${ticketId}/fingerprint`);
|
|
82
|
+
}
|
|
83
|
+
async put(path, body) {
|
|
84
|
+
const url = new URL(path, this.apiUrl);
|
|
85
|
+
const headers = {
|
|
86
|
+
"Authorization": `Bearer ${this.apiToken}`,
|
|
87
|
+
"X-KantBan-Via": "cli"
|
|
88
|
+
};
|
|
89
|
+
const init = { method: "PUT", headers };
|
|
90
|
+
if (body) {
|
|
91
|
+
headers["Content-Type"] = "application/json";
|
|
92
|
+
init.body = JSON.stringify(body);
|
|
93
|
+
}
|
|
94
|
+
const res = await fetchWithRetry(url.toString(), init);
|
|
95
|
+
if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);
|
|
96
|
+
const json = await res.json();
|
|
97
|
+
if (!json.success) throw new Error(`API responded with success: false`);
|
|
98
|
+
return json.data;
|
|
99
|
+
}
|
|
100
|
+
async patch(path, body) {
|
|
101
|
+
const url = new URL(path, this.apiUrl);
|
|
102
|
+
const headers = {
|
|
103
|
+
"Authorization": `Bearer ${this.apiToken}`,
|
|
104
|
+
"X-KantBan-Via": "cli"
|
|
105
|
+
};
|
|
106
|
+
const init = { method: "PATCH", headers };
|
|
107
|
+
if (body) {
|
|
108
|
+
headers["Content-Type"] = "application/json";
|
|
109
|
+
init.body = JSON.stringify(body);
|
|
110
|
+
}
|
|
111
|
+
const res = await fetchWithRetry(url.toString(), init);
|
|
112
|
+
if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);
|
|
113
|
+
const json = await res.json();
|
|
114
|
+
if (!json.success) throw new Error(`API responded with success: false`);
|
|
115
|
+
return json.data;
|
|
116
|
+
}
|
|
117
|
+
async delete(path) {
|
|
118
|
+
const url = new URL(path, this.apiUrl);
|
|
119
|
+
const res = await fetchWithRetry(url.toString(), {
|
|
120
|
+
method: "DELETE",
|
|
121
|
+
headers: {
|
|
122
|
+
"Authorization": `Bearer ${this.apiToken}`,
|
|
123
|
+
"X-KantBan-Via": "cli"
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);
|
|
127
|
+
const json = await res.json();
|
|
128
|
+
if (!json.success) throw new Error(`API responded with success: false`);
|
|
129
|
+
return json.data;
|
|
130
|
+
}
|
|
131
|
+
async getBoardProject(boardId) {
|
|
132
|
+
return this.get(`/boards/${boardId}/project`);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export {
|
|
137
|
+
fetchWithRetry,
|
|
138
|
+
KantBanCLIClient
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=chunk-CQP4B53A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["// ── retry / timeout constants ─────────────────────────────────────────────\n\nexport const REQUEST_TIMEOUT_MS = 30_000;\nexport const MAX_RETRIES = 2;\nexport const RETRY_BASE_MS = 500;\n\n/** Returns true for statuses that are worth retrying (server errors + rate limit). */\nexport function isRetryableStatus(status: number): boolean {\n return status >= 500 || status === 429;\n}\n\n/**\n * Wraps `fetch` with:\n * - Per-request AbortController timeout (REQUEST_TIMEOUT_MS)\n * - Exponential backoff with jitter on retryable failures\n * - Up to MAX_RETRIES retries (so MAX_RETRIES + 1 total attempts)\n *\n * @param _sleep — optional override for the backoff sleep (used in tests to avoid real delays)\n */\nexport async function fetchWithRetry(\n url: string,\n init: RequestInit,\n retries = MAX_RETRIES,\n _sleep: (ms: number) => Promise<void> = (ms) =>\n new Promise((resolve) => setTimeout(resolve, ms)),\n): Promise<Response> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\n try {\n const res = await fetch(url, { ...init, signal: controller.signal });\n clearTimeout(timer);\n\n // Non-retryable: 2xx (ok) or any non-retryable error status\n if (res.ok || !isRetryableStatus(res.status)) {\n return res;\n }\n\n // Retryable status — treat as a transient failure\n lastError = new Error(`API error ${res.status}: ${await res.text()}`);\n } catch (err) {\n clearTimeout(timer);\n lastError = err;\n // Only retry on network/abort errors, not on logic errors\n }\n\n if (attempt < retries) {\n // Exponential backoff with up to 50% random jitter\n const delay = RETRY_BASE_MS * 2 ** attempt * (1 + Math.random() * 0.5);\n await _sleep(delay);\n }\n }\n\n throw lastError;\n}\n\n// ── client ────────────────────────────────────────────────────────────────\n\nexport class KantBanCLIClient {\n constructor(\n private apiUrl: string,\n private apiToken: string,\n ) {}\n\n get baseUrl(): string {\n return this.apiUrl;\n }\n\n get token(): string {\n return this.apiToken;\n }\n\n async get<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T> {\n const url = new URL(path, this.apiUrl);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined) url.searchParams.set(k, String(v));\n }\n }\n const res = await fetchWithRetry(url.toString(), {\n headers: {\n 'Authorization': `Bearer ${this.apiToken}`,\n 'X-KantBan-Via': 'cli',\n },\n });\n if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);\n const json = (await res.json()) as { success: boolean; data: T };\n if (!json.success) throw new Error(`API responded with success: false`);\n return json.data;\n }\n\n async post<T>(path: string, body?: Record<string, unknown>): Promise<T> {\n const url = new URL(path, this.apiUrl);\n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${this.apiToken}`,\n 'X-KantBan-Via': 'cli',\n };\n const init: RequestInit = { method: 'POST', headers };\n if (body) {\n headers['Content-Type'] = 'application/json';\n init.body = JSON.stringify(body);\n }\n const res = await fetchWithRetry(url.toString(), init);\n if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);\n const json = (await res.json()) as { success: boolean; data: T };\n if (!json.success) throw new Error(`API responded with success: false`);\n return json.data;\n }\n\n async claimTicket(projectId: string, ticketId: string): Promise<void> {\n await this.post(`/projects/${projectId}/tickets/${ticketId}/start`, {});\n }\n\n async getFingerprint(\n projectId: string,\n ticketId: string,\n ): Promise<{\n column_id: string | null;\n updated_at: string;\n comment_count: number;\n signal_count: number;\n field_value_count: number;\n }> {\n return this.get(`/projects/${projectId}/tickets/${ticketId}/fingerprint`);\n }\n\n async put<T>(path: string, body?: Record<string, unknown>): Promise<T> {\n const url = new URL(path, this.apiUrl);\n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${this.apiToken}`,\n 'X-KantBan-Via': 'cli',\n };\n const init: RequestInit = { method: 'PUT', headers };\n if (body) {\n headers['Content-Type'] = 'application/json';\n init.body = JSON.stringify(body);\n }\n const res = await fetchWithRetry(url.toString(), init);\n if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);\n const json = (await res.json()) as { success: boolean; data: T };\n if (!json.success) throw new Error(`API responded with success: false`);\n return json.data;\n }\n\n async patch<T>(path: string, body?: Record<string, unknown>): Promise<T> {\n const url = new URL(path, this.apiUrl);\n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${this.apiToken}`,\n 'X-KantBan-Via': 'cli',\n };\n const init: RequestInit = { method: 'PATCH', headers };\n if (body) {\n headers['Content-Type'] = 'application/json';\n init.body = JSON.stringify(body);\n }\n const res = await fetchWithRetry(url.toString(), init);\n if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);\n const json = (await res.json()) as { success: boolean; data: T };\n if (!json.success) throw new Error(`API responded with success: false`);\n return json.data;\n }\n\n async delete<T = unknown>(path: string): Promise<T> {\n const url = new URL(path, this.apiUrl);\n const res = await fetchWithRetry(url.toString(), {\n method: 'DELETE',\n headers: {\n 'Authorization': `Bearer ${this.apiToken}`,\n 'X-KantBan-Via': 'cli',\n },\n });\n if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);\n const json = (await res.json()) as { success: boolean; data: T };\n if (!json.success) throw new Error(`API responded with success: false`);\n return json.data;\n }\n\n async getBoardProject(boardId: string): Promise<{ project_id: string }> {\n return this.get(`/boards/${boardId}/project`);\n }\n}\n"],"mappings":"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","names":[]}
|