ikanban 0.4.4 → 0.4.5
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/lib/pipeline.js +16 -1
- package/package.json +1 -1
- package/scanners/ikanban-aiscan.py +24 -1
package/lib/pipeline.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// Pipelines view (secrets masked first), posts running → passed/failed, and exits
|
|
6
6
|
// with the command's own exit code. iKanban being down never fails the command.
|
|
7
7
|
// ikanban pipeline event --task IKA-1 --stage deploy --status passed [--url U] [--sha S] [--detail D]
|
|
8
|
+
// ikanban pipeline event --by-url --stage checks --status passed --url U
|
|
9
|
+
// the end of a CI run, to every task whose stage is still open on that run link
|
|
8
10
|
// ikanban pipeline log --task IKA-1 --stage build < output.txt
|
|
9
11
|
//
|
|
10
12
|
// Team: --team or IKANBAN_TEAM (identifier like IKA, or its id). Key: VIBE_API_TOKEN.
|
|
@@ -31,6 +33,9 @@ function masker(values) {
|
|
|
31
33
|
return (line) => values.reduce((s, v) => s.split(v).join("***"), line);
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
/** Options that take no value. */
|
|
37
|
+
const FLAGS = ["--by-url"];
|
|
38
|
+
|
|
34
39
|
function parse(args) {
|
|
35
40
|
const opts = { _: [] };
|
|
36
41
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -40,6 +45,7 @@ function parse(args) {
|
|
|
40
45
|
break;
|
|
41
46
|
}
|
|
42
47
|
if (!a.startsWith("--")) opts._.push(a);
|
|
48
|
+
else if (FLAGS.includes(a)) opts[a.slice(2)] = true;
|
|
43
49
|
else opts[a.slice(2)] = args[++i];
|
|
44
50
|
}
|
|
45
51
|
return opts;
|
|
@@ -80,7 +86,7 @@ function connection(opts) {
|
|
|
80
86
|
const [task, run] = t.split("@");
|
|
81
87
|
return { task, run: run || opts.run || process.env.IKANBAN_RUN || undefined };
|
|
82
88
|
});
|
|
83
|
-
if (!tasks.length) throw new Error("set --task (for example IKA-123; several: IKA-1,IKA-2; with a run: IKA-1@pr-159)");
|
|
89
|
+
if (!tasks.length && !opts["by-url"]) throw new Error("set --task (for example IKA-123; several: IKA-1,IKA-2; with a run: IKA-1@pr-159)");
|
|
84
90
|
if (!opts.stage) throw new Error("set --stage (for example build, deploy, smoke)");
|
|
85
91
|
return {
|
|
86
92
|
base: apiOrigin(opts["base-url"] || process.env.IKANBAN_BASE_URL || "https://api.i-kanban.com"),
|
|
@@ -192,8 +198,15 @@ async function run(args) {
|
|
|
192
198
|
if (sub === "event") {
|
|
193
199
|
const conn = connection(opts);
|
|
194
200
|
if (!opts.status) throw new Error("set --status (queued, running, passed, failed, skipped, held, cancelled)");
|
|
201
|
+
if (opts["by-url"] && !opts.url) throw new Error("--by-url needs --url (the run's link)");
|
|
195
202
|
const tid = await teamId(conn, conn.team);
|
|
196
203
|
try {
|
|
204
|
+
if (opts["by-url"]) {
|
|
205
|
+
const body = { stage: opts.stage, status: opts.status, url: opts.url, commit_sha: opts.sha, detail: opts.detail, source: conn.source, actor: conn.actor };
|
|
206
|
+
const done = await api(`/api/teams/${tid}/pipeline-events/by-url`, { ...conn, method: "POST", body });
|
|
207
|
+
console.log(`${opts.stage} ${opts.status}: ${(done && done.tasks ? done.tasks.length : 0)} open task(s) on that link`);
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
197
210
|
for (const t of conn.tasks) {
|
|
198
211
|
await postEvent(conn, tid, t, { stage: opts.stage, status: opts.status, url: opts.url, commit_sha: opts.sha, detail: opts.detail });
|
|
199
212
|
}
|
|
@@ -221,6 +234,8 @@ async function run(args) {
|
|
|
221
234
|
const HELP = `Usage:
|
|
222
235
|
ikanban pipeline run --task IKA-1[@pr-159][,IKA-2] --stage build [--url U] [--sha S] [--mask NAME,...] -- <command> [args...]
|
|
223
236
|
ikanban pipeline event --task IKA-1 --stage deploy --status passed [--url U] [--sha S] [--detail D]
|
|
237
|
+
ikanban pipeline event --by-url --stage checks --status passed --url U
|
|
238
|
+
(the end of a CI run, to every task whose stage is still open on that link)
|
|
224
239
|
ikanban pipeline log --task IKA-1 --stage build < output.txt
|
|
225
240
|
Every command also takes --run R (the run: a PR like pr-159, or a branch), --source S (where:
|
|
226
241
|
github, gitlab, laptop, ci) and --actor A (who). One run per task: IKA-1@pr-159.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ikanban",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "iKanban CLI: AI and security scans of your own repo, with results filed into iKanban",
|
|
5
5
|
"bin": { "ikanban": "bin/ikanban.js" },
|
|
6
6
|
"files": ["bin", "lib", "hooks", "scanners", "README.md", "LICENSE.md"],
|
|
@@ -57,7 +57,7 @@ PROMPTFOO_VERSION = "0.123.1"
|
|
|
57
57
|
# so this string is the release identity. Bump it when publishing a release —
|
|
58
58
|
# a customer pinning a version is pinning this value, and re-uploading different
|
|
59
59
|
# bytes under a version someone already fetched defeats the sha256 they pinned.
|
|
60
|
-
AISCAN_VERSION = "0.6.
|
|
60
|
+
AISCAN_VERSION = "0.6.2"
|
|
61
61
|
|
|
62
62
|
# The ten categories, their labels, and their plugins -- the forward map, copied
|
|
63
63
|
# from promptfoo's OWASP_LLM_TOP_10_MAPPING (src/redteam/constants/frameworks.ts).
|
|
@@ -663,6 +663,29 @@ class _NoRedirect(urllib.request.HTTPRedirectHandler):
|
|
|
663
663
|
_OPENER = urllib.request.build_opener(_NoRedirect)
|
|
664
664
|
|
|
665
665
|
|
|
666
|
+
def api(method: str, path: str, base_url: str, token: str, payload=None):
|
|
667
|
+
"""(ok, data | error text | None for 404). Same helper as secscan's; the Pipelines code
|
|
668
|
+
below calls it (seen on dev: without it `--task` crashed with a NameError)."""
|
|
669
|
+
req = urllib.request.Request(
|
|
670
|
+
f"{ikanban_api(base_url)}/api{path}",
|
|
671
|
+
data=json.dumps(payload).encode() if payload is not None else None,
|
|
672
|
+
headers={"Authorization": f"Bearer {token}",
|
|
673
|
+
"Content-Type": "application/json"},
|
|
674
|
+
method=method,
|
|
675
|
+
)
|
|
676
|
+
try:
|
|
677
|
+
with _OPENER.open(req, timeout=30) as r:
|
|
678
|
+
body = json.loads(r.read().decode() or "{}")
|
|
679
|
+
return True, body.get("data", body)
|
|
680
|
+
except urllib.error.HTTPError as e:
|
|
681
|
+
detail = e.read().decode()[:300]
|
|
682
|
+
if e.code in (404, 405) and not detail.strip():
|
|
683
|
+
return False, None
|
|
684
|
+
return False, f"HTTP {e.code}: {detail}"
|
|
685
|
+
except Exception as e: # noqa: BLE001 — surface any transport failure verbatim
|
|
686
|
+
return False, str(e)
|
|
687
|
+
|
|
688
|
+
|
|
666
689
|
def publish(findings, run_id, base_url, token, team_id, coverage, plugin_results):
|
|
667
690
|
payload = {
|
|
668
691
|
"scan_run_id": run_id,
|