refactor(tooling): merge redmine-cli into tooling-cli/redmine

Consolidate the redmine-cli source into tooling-cli/redmine/
(CLI source, Dockerfile, README, AGENTS.md) with all documentation
rewritten to invoke the container via raw docker run and credentials
from the centralized ~/.creds/redmine.env store. No bin/ wrapper, no
system-dependent paths in the docs.

Removes the old redmine-cli/ subdirectory, updates the
KNELCredsManager consumer table, and updates STATUS.md + AGENTS.md
to reference the new location and registry image.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
This commit is contained in:
2026-08-10 09:53:42 -05:00
parent ecffb4e598
commit 67f8056e0b
10 changed files with 276 additions and 23 deletions
+2 -2
View File
@@ -51,8 +51,8 @@ ALL work performed on this project MUST be documented in [JOURNAL.md](JOURNAL.md
- **Custom images**: publish to the Gitea container registry at - **Custom images**: publish to the Gitea container registry at
`git.knownelement.com/reachableceo/<image>:<tag>` (Docker is pre-authenticated). `git.knownelement.com/reachableceo/<image>:<tag>` (Docker is pre-authenticated).
- **Standalone CLI tools**: containerized CLI tools (not MCP/LSP servers) live - **Standalone CLI tools**: containerized CLI tools (not MCP/LSP servers) live
in their own top-level directory (e.g. `redmine-cli/`). Invoked on-demand via under `tooling-cli/` (e.g. `tooling-cli/redmine/`). Invoked on-demand via
`docker run --env-file .env <image> <command>`, not as long-running services. `docker run --env-file ~/.creds/<service>.env <image> <command>`, not as long-running services.
## Crush Configuration ## Crush Configuration
+1 -1
View File
@@ -217,4 +217,4 @@ Standalone containerized CLI tools (not MCP/LSP servers). Invoked on-demand via
| Tool | Image | Status | Notes | | Tool | Image | Status | Notes |
|------|-------|--------|-------| |------|-------|--------|-------|
| redmine-cli | kneldevstack-aimiddleware-redmine-cli | Working | Custom CLI (python-redmine) to list/show/create/update/close Redmine issues. Verified end-to-end against projects.knownelement.com. Requires REDMINE_URL and REDMINE_API_KEY. Source in `redmine-cli/`. | | redmine-cli | git.knownelement.com/reachableceo/redmine-cli:latest | Working | Custom CLI (python-redmine) to list/show/create/update/close Redmine issues. Verified end-to-end against projects.knownelement.com. Requires REDMINE_URL and REDMINE_API_KEY. Source in `tooling-cli/redmine/`. |
+1 -1
View File
@@ -26,7 +26,7 @@ All credential consumers source from `~/.creds/`:
| Service | Wrapper / consumer | Mechanism | | Service | Wrapper / consumer | Mechanism |
|---|---|---| |---|---|---|
| Redmine | `~/daytoday/redmine/bin/redmine` | `--env-file ~/.creds/redmine.env` | | Redmine | `redmine-cli` container (`tooling-cli/redmine/`) | `docker run --env-file ~/.creds/redmine.env` |
| Redmine MCP | `mcp-redmine-wrapper.sh` | `set -a; . ~/.creds/redmine.env; set +a` | | Redmine MCP | `mcp-redmine-wrapper.sh` | `set -a; . ~/.creds/redmine.env; set +a` |
| Discourse | `discourse-cli` container (`tooling-cli/discourse/`) | `docker run --env-file ~/.creds/discourse.env` | | Discourse | `discourse-cli` container (`tooling-cli/discourse/`) | `docker run --env-file ~/.creds/discourse.env` |
| Discourse MCP | `mcp-discourse-wrapper.sh` | `set -a; . ~/.creds/discourse.env; set +a` | | Discourse MCP | `mcp-discourse-wrapper.sh` | `set -a; . ~/.creds/discourse.env; set +a` |
+8
View File
@@ -0,0 +1,8 @@
# Secrets - never commit
.env
# OS / editor cruft
.DS_Store
*.swp
*.swo
*~
+240
View File
@@ -0,0 +1,240 @@
# AGENTS.md — Redmine CLI tooling
This directory holds the `redmine-cli` Docker container — a thin Python CLI
over `python-redmine` for Redmine issue tracking. Every Crush session that
works on or with this tool should read this file first: how to invoke it,
what commands exist, what gotchas to avoid, and the patterns to follow for
common ticket operations.
## Connection & invocation
Invoke the real container with `docker run`. Credentials come from the
centralized credential store (see `tooling-cli/KNELCredsManager`):
```bash
docker run --rm --env-file ~/.creds/redmine.env \
git.knownelement.com/reachableceo/redmine-cli:latest <command>
```
There is intentionally **no `bin/` wrapper** — invoke the real container, as
the project-wide rules require. For readability in an interactive session you
may define a throwaway shell alias (do not commit one):
```bash
alias redmine='docker run --rm --env-file ~/.creds/redmine.env \
git.knownelement.com/reachableceo/redmine-cli:latest'
```
The examples below write `redmine <cmd>` for brevity; expand it to the full
`docker run` line (or set the alias) before running.
| Item | Value |
|------|-------|
| Instance | `https://projects.knownelement.com` |
| API user | Charles N (id 5, login `reachableceo`) |
| Credentials | `~/.creds/redmine.env` (`REDMINE_URL`, `REDMINE_API_KEY`) |
| CLI image | `git.knownelement.com/reachableceo/redmine-cli:latest` |
| Source | `src/redmine_cli.py` in this directory |
To use a locally built image instead of the registry image, either set
`REDMINE_CLI_IMAGE` or swap the tag for `kneldevstack-aimiddleware-redmine-cli:latest`.
## Quick start
```bash
# Connection sanity check (run this first in any session):
redmine whoami
# Look at a ticket:
redmine show 314
# Your queue:
redmine list --assigned-to-me
```
## Command reference
| Command | Description |
|---------|-------------|
| `whoami` | Authenticated user + connection test. |
| `projects` | List projects (id, identifier, name). |
| `statuses` | List issue statuses + which are "closed". |
| `list` (`ls`) | List issues. Filters below. |
| `show <id>` | Full issue detail incl. note history. |
| `create` | Create an issue. |
| `update <id>` | Update an issue (status, notes, assignee, done, ...). |
| `close <id>` | Close (first closed status, done ratio 100%). |
### `list` filters
- `-m, --assigned-to-me` — only issues assigned to you
- `-a, --assigned-to <id>` — filter by assignee user id
- `-p, --project <id|slug>` — filter by project
- `-s, --status <name|id|open|closed>` — filter by status
- `-l, --limit <n>` — max results (default 50)
- `--sort <spec>` — Redmine sort spec (default `priority:desc,updated_on:desc`)
### `create` options
- `-p, --project <id|slug>` **required**
- `-s, --subject` **required**
- `-d, --description`
- `-a, --assigned-to <id>`
- `-t, --tracker <id>`
- `--priority <id>`
- `--status <name|id>`
### `update` options
- `-s, --status <name|id>`
- `-n, --notes <text>`
- `-a, --assigned-to <id>`
- `--done-ratio <0-100>`
- `--subject`
- `--priority <id>`
## Key project / tracker / status IDs
### Most-used projects
| ID | Identifier | Name |
|----|------------|------|
| 55 | `technicaloperations` | Known Element Enterprises - Technology & Facility Services |
| 62 | `business-operations` | Known Element Enterprises - Business Services |
| 77 | `tsys-group` | TSYS Group |
Run `redmine projects` for the full list.
### Statuses
| ID | Name | Closed? |
|----|------|---------|
| 1 | New | no |
| 2 | In Progress | no |
| 3 | Resolved | yes |
| 4 | Feedback | no |
| 5 | Closed | yes |
| 6 | Rejected | no |
### Trackers
Tracker 3 = **Support** (the most common one in project 55). Inspect a
parent issue to inherit its exact tracker.
## Gotcha: creating subtasks
`redmine create` has **no `--parent` flag**. To create a subtask of an
existing issue you must use `python-redmine` directly inside the container
(via `--entrypoint python`).
Pattern (mount a script and run it in the same image):
```bash
cat > /tmp/script.py <<'PY'
import os
from redminelib import Redmine
rm = Redmine(os.environ["REDMINE_URL"].rstrip("/"), key=os.environ["REDMINE_API_KEY"])
issue = rm.issue.create(
project_id=55, # inherit from parent
tracker_id=3, # inherit from parent
priority_id=2, # inherit from parent
status_id=4, # Feedback = freshly created, awaiting work
assigned_to_id=5, # inherit from parent
parent_issue_id=314, # THE KEY FIELD
subject="Your subject",
description="Your description",
)
print(f"Created #{issue.id}")
PY
docker run --rm --env-file ~/.creds/redmine.env \
--entrypoint python \
-v /tmp/script.py:/tmp/script.py \
git.knownelement.com/reachableceo/redmine-cli:latest \
/tmp/script.py
```
**Before creating subtasks**, inspect the parent to inherit its attributes:
```bash
docker run --rm --env-file ~/.creds/redmine.env \
--entrypoint python \
git.knownelement.com/reachableceo/redmine-cli:latest -c "
import os
from redminelib import Redmine
rm = Redmine(os.environ['REDMINE_URL'].rstrip('/'), key=os.environ['REDMINE_API_KEY'])
i = rm.issue.get(PARENT_ID)
print('project_id:', i.project.id)
print('tracker_id:', i.tracker.id, i.tracker.name)
print('priority_id:', i.priority.id, i.priority.name)
print('assigned_to_id:', getattr(i.assigned_to,'id',None))
print('status_id:', i.status.id, i.status.name)
"
```
## Patterns
### Pattern: review & split a ticket
1. `redmine show <id>` — read the full ticket (description + history).
2. Identify the **distinct bodies of work**. Each should be independently
trackable and assignable.
3. Map out **dependencies** between the pieces (what blocks what).
4. Propose a split to the user as a table: proposed subject, scope, and
dependencies. Wait for approval before creating anything.
5. On approval: create subtasks (see "Gotcha: creating subtasks" above),
inheriting project/tracker/priority/assignee from the parent.
6. Update the parent's **description** to an index table of children +
recommended execution order. Add a note explaining the split.
7. Verify with `redmine show <parent_id>`.
### Pattern: ticket conventions
- **Subject prefix:** OAM-pool tickets use `OAM: <topic>`. Match the parent's
naming convention if one exists.
- **Description header:** always start with `Parent/umbrella: #<id> (<subject>).`
- **Scope section:** bullet list of concrete deliverables. Use `- [ ]` for
checklist items within a subtask.
- **Dependencies section:** list what the ticket depends on and what it blocks,
referencing ticket numbers once they exist.
- **Newly created children** go to status **Feedback (4)** so they're visible
but not yet "in progress".
- **Tables** in descriptions render in Redmine's Markdown pipeline (`| a | b |`).
### Pattern: bulk operations
For anything beyond a single `create`/`update`/`close`, write a Python script
and run it inside the container as shown in the subtask gotcha above. This
applies to: batch status changes, bulk ticket creation, inspecting parent
attributes, relationship wiring, etc.
### Pattern: before you act
- Always `redmine show <id>` before updating — confirm current status,
assignee, and existing notes so you don't clobber context.
- Never delete or close a ticket unless the user explicitly asks.
- When in doubt about project/tracker/priority, inherit from the parent or
ask the user.
## Git workflow
### Atomic commits
Each commit is **one logical change** — one feature, one fix, one doc update.
If you're tempted to write "and also..." in a commit message, that's a sign
to split it into two commits. Stage precisely (`git add <file>`, not
`git add -A`) so unrelated changes don't get bundled.
### Conventional commit messages
Use the [Conventional Commits](https://www.conventionalcommits.org/) format:
```
<type>(<optional scope>): <imperative subject>
<optional body — why, not what>
```
Types used in this repo: `feat`, `fix`, `docs`, `refactor`, `chore`, `style`.
Rules:
- Subject line **under 72 chars**, lowercase, imperative mood.
- No period at end of subject.
- Body wrapped at 72 chars, explains **why** the change exists.
### Commit cadence
- **Commit early and often.** Don't accumulate a pile of unrelated changes.
- Commit **without asking** — if you made a coherent change, commit it.
- Every task (ticket review, split, script addition, doc update) ends with
the relevant files committed.
- Run `git status` before committing to stage only what belongs together.
@@ -15,19 +15,21 @@ wrapper.
## Configuration ## Configuration
Copy the env template and fill in real values (the `.env` is gitignored): Credentials live in the **centralized credential store** at
`~/.creds/redmine.env` (see `tooling-cli/KNELCredsManager`). It holds:
```bash
cp .env.example .env
# then edit .env:
# REDMINE_URL=https://your-redmine.example.com
# REDMINE_API_KEY=abc123...
``` ```
REDMINE_URL=https://your-redmine.example.com
REDMINE_API_KEY=abc123...
```
Use `.env.example` in this directory as a template if you need to create one.
Permissions: directory `700`, the env file `600` (owner read/write only).
## Build ## Build
```bash ```bash
docker build -t kneldevstack-aimiddleware-redmine-cli . docker build -t kneldevstack-aimiddleware-redmine-cli:latest .
``` ```
A prebuilt image is also available in the Gitea registry: A prebuilt image is also available in the Gitea registry:
@@ -38,21 +40,25 @@ git.knownelement.com/reachableceo/redmine-cli:latest
## Usage ## Usage
### Via the repo wrapper (loads `.env` automatically) Invoke the container directly with `docker run`. Pass credentials from the
centralized store via `--env-file`:
```bash ```bash
bin/redmine whoami docker run --rm --env-file ~/.creds/redmine.env \
bin/redmine list --assigned-to-me git.knownelement.com/reachableceo/redmine-cli:latest whoami
bin/redmine show 123
bin/redmine close 123 --notes "work complete" docker run --rm --env-file ~/.creds/redmine.env \
git.knownelement.com/reachableceo/redmine-cli:latest list --assigned-to-me
docker run --rm --env-file ~/.creds/redmine.env \
git.knownelement.com/reachableceo/redmine-cli:latest show 123
docker run --rm --env-file ~/.creds/redmine.env \
git.knownelement.com/reachableceo/redmine-cli:latest close 123 --notes "work complete"
``` ```
### Via docker directly (from the repo root) To use a locally built image instead of the registry image, either set
`REDMINE_CLI_IMAGE` or swap the image tag for `kneldevstack-aimiddleware-redmine-cli:latest`.
```bash
docker run --rm --env-file .env kneldevstack-aimiddleware-redmine-cli whoami
docker run --rm --env-file .env kneldevstack-aimiddleware-redmine-cli list --assigned-to-me
```
## Commands ## Commands
@@ -94,4 +100,3 @@ docker run --rm --env-file .env kneldevstack-aimiddleware-redmine-cli list --ass
| ----------------- | -------- | ------------------------------------ | | ----------------- | -------- | ------------------------------------ |
| `REDMINE_URL` | yes | Base URL of the Redmine instance. | | `REDMINE_URL` | yes | Base URL of the Redmine instance. |
| `REDMINE_API_KEY` | yes | API key of the acting user. | | `REDMINE_API_KEY` | yes | API key of the acting user. |
| `REDMINE_CLI_IMAGE` | no | Override the image tag used by `bin/redmine` (default `git.knownelement.com/reachableceo/redmine-cli:latest`). |