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 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.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.1"
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,