Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"plugins": [
{
"name": "kbagent",
"version": "0.81.0",
"version": "0.82.0",
"source": "./plugins/kbagent",
"description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, call MCP tools, manage dev branches, and debug SQL in workspaces",
"category": "development"
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/release-kbagent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,13 @@ jobs:
env:
VERSION: ${{ needs.version.outputs.VERSION }}
steps:
# NOTE for whoever gets this job working: `wingetcreate update` only bumps
# the version and installer URL -- it does NOT write locale fields. The
# FIRST successful submission must therefore set `License: Apache-2.0`
# itself, because every later run inherits whatever that manifest said.
# Do not copy the existing `Keboola.KeboolaCLI` manifest: that is the
# legacy Go CLI from keboola/keboola-as-code, a different product under a
# different (MIT) licence. `Keboola.KeboolaCLI2` has never been submitted.
- name: Submit to winget-pkgs
shell: bash
env:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,4 +330,4 @@ make hooks # install pre-commit hook

## License

MIT
[Apache License 2.0](LICENSE)
2 changes: 1 addition & 1 deletion build/package/homebrew/keboola-cli2.rb.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class KeboolaCli2 < Formula
desc "AI-friendly CLI for managing Keboola projects (kbagent)"
homepage "https://github.com/keboola/cli"
version "{VERSION}"
license "MIT"
license "Apache-2.0"

on_macos do
# Apple Silicon only (single macOS build env). Gate on arch so Intel Macs get a
Expand Down
2 changes: 1 addition & 1 deletion build/package/nfpm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ description: |
Self-contained native binary; no Python runtime required.
vendor: "Keboola"
homepage: "https://github.com/keboola/cli"
license: "MIT"
license: "Apache-2.0"

contents:
- src: ${BIN_PATH}
Expand Down
4 changes: 2 additions & 2 deletions plugins/kbagent/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "kbagent",
"version": "0.81.0",
"version": "0.82.0",
"description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, call MCP tools, manage dev branches, and debug SQL in workspaces",
"author": {
"name": "Keboola",
"email": "support@keboola.com"
},
"homepage": "https://github.com/keboola/cli",
"repository": "https://github.com/keboola/cli",
"license": "MIT"
"license": "Apache-2.0"
}
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[project]
name = "keboola-cli"
version = "0.81.0"
version = "0.82.0"
description = "AI-friendly CLI for managing Keboola projects"
readme = "README.md"
requires-python = ">=3.12"
license = "MIT"
license = "Apache-2.0"
authors = [
{ name = "Keboola", email = "dev@keboola.com" },
]
Expand Down
12 changes: 12 additions & 0 deletions src/keboola_agent_cli/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@

# Ordered newest-first. Each value is a list of brief one-line descriptions.
CHANGELOG: dict[str, list[str]] = {
"0.82.0": [
"Fix: kbagent is licensed under **Apache 2.0**, and the packaging metadata now says "
"so. #544 added an Apache 2.0 `LICENSE` file, but `pyproject.toml`, the deb/rpm "
"package, the Homebrew formula, the Claude Code plugin manifest and the README all "
"still declared MIT -- so v0.79.0 through v0.81.0 shipped a wheel telling PyPI one "
"licence while the LICENSE file in the same distribution said another, and the "
"Chocolatey package pointed its `licenseUrl` at the Apache text under an MIT "
"declaration. Nothing caught it because each file was individually valid. The wheel "
"now carries `License-Expression: Apache-2.0`, and a test pins all five declaration "
"sites to the LICENSE file so they cannot drift apart again. No code change; if you "
"vendored kbagent under the belief it was MIT, re-check against Apache 2.0.",
],
"0.81.0": [
"New (#390): `kbagent doctor` gains an `mcp_tool_tasks` check that finds scheduled "
"agent tasks still using the deprecated `--type mcp_tool` action, which is removed "
Expand Down
95 changes: 95 additions & 0 deletions tests/test_license_consistency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""The declared licence must match the LICENSE file, everywhere it is declared.

kbagent declares its licence in five places that nothing kept in sync: the
Python distribution metadata, the deb/rpm package, the Homebrew formula, the
Claude Code plugin manifest and the README. #544 added an Apache 2.0 `LICENSE`
file while all five still said MIT, and v0.79.0 shipped that contradiction --
the wheel told PyPI one licence while the file in the same repo said another.
No CI check noticed, because each file is individually valid.
"""
Comment thread
padak marked this conversation as resolved.

from __future__ import annotations

import json
import re
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).parent.parent
LICENSE_PATH = REPO_ROOT / "LICENSE"

#: SPDX identifier the repository is licensed under. Change this ONLY together
#: with the LICENSE file itself -- every assertion below hangs off it.
EXPECTED_SPDX = "Apache-2.0"

#: How each packaging file spells the same licence.
_DECLARATIONS: tuple[tuple[str, str], ...] = (
("pyproject.toml", rf'^license = "{re.escape(EXPECTED_SPDX)}"$'),
("build/package/nfpm.yaml", rf'^license: "{re.escape(EXPECTED_SPDX)}"$'),
(
"build/package/homebrew/keboola-cli2.rb.tmpl",
rf'^\s*license "{re.escape(EXPECTED_SPDX)}"$',
),
)


def test_license_file_is_the_expected_licence() -> None:
"""Anchor: the other assertions are only meaningful against the real file."""
text = LICENSE_PATH.read_text(encoding="utf-8")
assert "Apache License" in text
assert "Version 2.0" in text


@pytest.mark.parametrize(("relative_path", "pattern"), _DECLARATIONS)
def test_packaging_file_declares_the_same_licence(relative_path: str, pattern: str) -> None:
text = (REPO_ROOT / relative_path).read_text(encoding="utf-8")
assert re.search(pattern, text, re.MULTILINE), (
f"{relative_path} does not declare {EXPECTED_SPDX}; it must match the LICENSE file"
)


def test_plugin_manifest_declares_the_same_licence() -> None:
manifest = json.loads(
(REPO_ROOT / "plugins/kbagent/.claude-plugin/plugin.json").read_text(encoding="utf-8")
)
assert manifest["license"] == EXPECTED_SPDX


def test_readme_does_not_still_claim_mit() -> None:
"""The README is what a human reads before the metadata."""
readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
section = readme.split("## License", 1)
assert len(section) == 2, "README has no License section"
body = section[1][:200]
assert "MIT" not in body
assert "Apache" in body


def test_chocolatey_points_its_license_url_at_the_repo_license() -> None:
"""The nuspec has no SPDX field; its licenceUrl is the whole declaration.

A URL to the LICENSE file self-updates with the repository, which is why
this channel never went stale -- but only as long as it points THERE and
not at a hard-coded licence page.
"""
nuspec = (REPO_ROOT / "build/package/chocolatey/keboola-cli2.nuspec").read_text(
encoding="utf-8"
)
assert "<licenseUrl>https://github.com/keboola/cli/blob/main/LICENSE</licenseUrl>" in nuspec


def test_no_packaging_file_still_says_mit() -> None:
"""Catch a sixth declaration site added later without updating this test."""
offenders = []
for relative_path in (
"pyproject.toml",
"build/package/nfpm.yaml",
"build/package/homebrew/keboola-cli2.rb.tmpl",
"plugins/kbagent/.claude-plugin/plugin.json",
"README.md",
):
text = (REPO_ROOT / relative_path).read_text(encoding="utf-8")
if re.search(r"\bMIT\b", text):
offenders.append(relative_path)
assert not offenders, f"still declaring MIT: {offenders}"
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.