Template
Compare commits
15
Commits
9836e94e48
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f30e95d9d0 | ||
|
|
8d352aa7d5 | ||
|
|
2c9e5e9c0e | ||
|
|
f5228a243f | ||
|
|
9cf15f1258 | ||
|
|
28b0019d2e | ||
|
|
5c44e3ef41 | ||
|
|
abb94a478e | ||
|
|
0be43403e6 | ||
|
|
16cbeb4ed8 | ||
|
|
1b899d32d3 | ||
|
|
29f33c4b03 | ||
|
|
323e8ce2d1 | ||
|
|
d5a0e4d8f6 | ||
|
|
cf1b444a45 |
@@ -0,0 +1,3 @@
|
||||
*
|
||||
!crush.json
|
||||
!.gitignore
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "^(bash|edit|write|multiedit|lsp_replace_symbol|lsp_rename)$",
|
||||
"command": "./hooks/ticket-gate.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,32 @@ enforce the mechanical rules below; this document states the policy and intent.
|
||||
|
||||
**You are an AI agent working on this project. Your first actions, in order:**
|
||||
|
||||
> **SESSION-START GATE (NON-NEGOTIABLE):** Steps 1-4 orient you. Step 5 is the
|
||||
> check-for-understanding gate (rule summary). Steps 6-8 gather state. Step 9 is
|
||||
> the scope-alignment gate. You MUST NOT begin any task work until the user
|
||||
> (a) confirms your rule summary AND (b) names the ticket to work on. This runs
|
||||
> **every session, automatically** — the user should never have to ask for it.
|
||||
|
||||
1. **Set up the environment:** `bash scripts/setup-hooks.sh` (installs git hooks — idempotent).
|
||||
2. **Read [STATUS.md](STATUS.md)** — current state, inbox, blockers, tactical notes.
|
||||
3. **Read [`.crush/memory/operational.md`](.crush/memory/operational.md)** — access details, key IDs, gotchas.
|
||||
4. **Read [questions-v1.md](questions-v1.md)** — open questions awaiting human input.
|
||||
5. **Check Redmine** — `redmine list --assigned-to-me -p <project-id>` for active work.
|
||||
6. **Check current state:** `git log --oneline -10`.
|
||||
5. **Check for understanding — GATE.** In your own words, summarize the rules
|
||||
from this file back to the user: Systems of Record, Git Workflow, Questions
|
||||
policy, Remote Access (if applicable), Conventions, and any project-specific
|
||||
rules. Then stop and wait for the user to confirm. This checkpoint
|
||||
guarantees every session starts aligned.
|
||||
6. **Check Redmine** — `redmine list --assigned-to-me -p <project-id>` for active work.
|
||||
7. **Check current state:** `git log --oneline -10`.
|
||||
8. **Run rule audit:** `bash scripts/check-rules.sh --fast`.
|
||||
9. **Scope-alignment — GATE.** Present the session handoff's priority list (or
|
||||
the Redmine queue if no handoff exists). Do NOT scan the full ticket queue
|
||||
and pick work on your own — **the user directs what gets worked on, always.**
|
||||
Note any new or urgent items from the Redmine check, then ask which ticket
|
||||
to work on tonight. **STOP and wait.** Do not set `.crush/active-ticket`,
|
||||
do not read systems, do not run diagnostics, until the user names the
|
||||
target. This gate prevents the agent from burning context on work the user
|
||||
didn't ask for.
|
||||
|
||||
## Project Overview
|
||||
|
||||
@@ -59,6 +79,10 @@ Replace this bracketed text.>
|
||||
## Systems of Record (do not duplicate)
|
||||
|
||||
- **Redmine** is the single system of record for ALL project work — tickets, schedules, Gantt, dependencies. Use the `redmine-cli`. **Do not use Gitea issues.** Reference tickets as `[#NNN]`.
|
||||
- **NEVER close a Redmine ticket without explicit user permission.** You may SUGGEST a close when the result is clearly scoped and delivered. If it's ambiguous whether the work is truly complete, don't suggest a close — leave that decision to the user. This applies to ALL tickets, no exceptions.
|
||||
- **User acceptance testing is MANDATORY before declaring work done.** The agent performs implementation and technical validation (services running, configs correct, APIs responding). The user performs UAT — visually confirming dashboards render data, alerts deliver, tools are usable. The agent MUST NOT set done-ratio to 100%, MUST NOT suggest closing, and MUST NOT move to the next ticket until the user explicitly accepts the work. "Technically wired but blank dashboard" is NOT done.
|
||||
- **NEVER access a database directly if an API exists.** APIs are the stable contract; databases change schemas without warning. If a tool has an API, use it — exhaust all API endpoints, check the docs, try alternative methods. Only fall back to direct DB access as a last resort AND with explicit user approval for that specific instance. Soon all DB access will route through a proxy under zero trust; building API-first habits now ensures that transition is clean.
|
||||
- **Ticket-first enforcement (mechanically enforced).** Before starting any work, set the active ticket: `echo '#NNN' > .crush/active-ticket`. The Crush hook (`hooks/ticket-gate.sh`) blocks modifying operations until this file exists. If no ticket exists, CREATE ONE FIRST via redmine-cli, then set it. Clear when done: `> .crush/active-ticket`.
|
||||
- **Discourse** is the single system of record for documentation. Use the `discourse-cli`. **Do not author long-form docs in gitea.**
|
||||
- **Git-tracked `.md` files are stubs** that point to the relevant Discourse topic URL. Operational files that must live next to code (`AGENTS.md`, `STATUS.md`, `questions-v*.md`) are the documented exceptions.
|
||||
- `STATUS.md` is a scratchpad for token efficiency, **not** a system of record.
|
||||
@@ -66,13 +90,13 @@ Replace this bracketed text.>
|
||||
### CLI invocation (no bin/ shortcuts — invoke the real containers)
|
||||
|
||||
```
|
||||
# redmine-cli (source: ~/projects/KNEL-AIMiddleware/redmine-cli/)
|
||||
docker run --rm --env-file ~/projects/KNEL-AIMiddleware/redmine-cli/.env \
|
||||
# redmine-cli
|
||||
docker run --rm --env-file ~/.creds/redmine.env \
|
||||
git.knownelement.com/reachableceo/redmine-cli:latest <command>
|
||||
# subcommands: whoami, projects, statuses, list, show <id>, create, update <id>, close <id>
|
||||
|
||||
# discourse-cli (source: ~/projects/KNEL-AIMiddleware/discourse-cli/)
|
||||
docker run --rm --env-file ~/projects/KNEL-AIMiddleware/discourse-cli/.env \
|
||||
# discourse-cli
|
||||
docker run --rm --env-file ~/.creds/discourse.env \
|
||||
git.knownelement.com/reachableceo/discourse-cli:latest <command>
|
||||
# subcommands: whoami, categories, cat-info, topics, show <id>, create, reply <id>, update <post_id>, delete <post_id>, search
|
||||
```
|
||||
@@ -121,8 +145,57 @@ docker run --rm --env-file ~/projects/KNEL-AIMiddleware/discourse-cli/.env \
|
||||
- Clear WORKING.md (to "all done") before responding to the user.
|
||||
- **The human decides when the work is done and when to deploy.** Never declare "done" unilaterally.
|
||||
|
||||
## Rolling HUD (session-scoped, NOT persisted)
|
||||
|
||||
The rolling HUD is a **live status display** the agent maintains throughout
|
||||
the session to help the human follow along. It is NOT a system of record —
|
||||
Redmine, Discourse, and git are the durable systems. The HUD exists purely
|
||||
for the human's situational awareness during the session.
|
||||
|
||||
- **Format:** a compact block shown at the end of each significant response
|
||||
(after completing a step, hitting a blocker, or pivoting). Example:
|
||||
|
||||
```
|
||||
┌─ SESSION HUD ────────────────────────────────────────
|
||||
│ Active: #NNN (ticket subject)
|
||||
│ Done: ✓ first completed item this session
|
||||
│ ✓ second completed item
|
||||
│ Now: what the agent is actively doing right now
|
||||
│ Next: → #NNN next queued ticket
|
||||
│ → #NNN another queued item (BLOCKED: what's needed)
|
||||
├─ USER ACTION ITEMS ──────────────────────────────────
|
||||
│ • Action only the user can take (unblocks #NNN)
|
||||
│ • Another user action (unblocks #NNN)
|
||||
└──────────────────────────────────────────────────────
|
||||
```
|
||||
|
||||
- **Placement:** may be written to `.crush/hud.md` on disk to keep context
|
||||
window smaller (re-read and update rather than hold in memory). Never
|
||||
committed to git. Wiped at session end.
|
||||
- **Sections:**
|
||||
- **Active:** current ticket number + one-line description
|
||||
- **Done:** ✓ items completed this session (append as work progresses)
|
||||
- **Now:** what the agent is actively doing
|
||||
- **Next:** the queued items (per handoff priority or user direction)
|
||||
- **User action items:** things ONLY the user can do (populate creds,
|
||||
physical work, manual deploys) with the tickets they unblock
|
||||
- **When to show it:** after each logical unit of work, at blockers, and
|
||||
when pivoting between tickets. Not every trivial response — use judgment.
|
||||
- **NOT a substitute for Redmine/Discourse/git.** The HUD is ephemeral. When
|
||||
work completes, update the durable systems (ticket notes, Discourse wiki,
|
||||
commits). The HUD just tracks the live narrative for the human.
|
||||
|
||||
## Questions
|
||||
|
||||
- **NEVER use a harness "question"/"ask user" tool** (structured prompts,
|
||||
modal forms, tabbed questions). Banned across every project, every
|
||||
harness. Not portable, not version-controlled, bypasses the git record.
|
||||
Non-negotiable. (See `BASELINE-PROMPT.md` §10.)
|
||||
- **All questions go in the git-tracked `questions-v(N).md` file.** The
|
||||
human edits the answer inline in the same file. **Version up the filename
|
||||
each time answers land** (v1 → v2 → v3...): create `questions-v2.md`
|
||||
with resolved Q&A marked, new questions appended. This preserves the
|
||||
history of each Q&A round.
|
||||
- Capture questions for the human in a git-tracked `questions-v(N).md` file.
|
||||
- The human reviews and edits it inline. Version up when a round of answers lands.
|
||||
- Questions, answers, and the reasoning behind decisions are often more important than the code. Synthesize resolved Q&A into Discourse (decisions) and Redmine (work items).
|
||||
@@ -134,6 +207,13 @@ docker run --rm --env-file ~/projects/KNEL-AIMiddleware/discourse-cli/.env \
|
||||
- **Prefer Unix utilities (awk, sed, grep, cut, tr, etc.) for file editing and text processing** over built-in edit tools when feasible. Harness edit tools can be unreliable with whitespace/indentation; Unix tools are deterministic and exact. Use them for surgical edits, bulk substitutions, and structured text extraction.
|
||||
- **Farm work out to deterministic tooling:** linters, LSPs, formatters, test runners. If an LSP is wired up, use it; otherwise pull a Docker image and lint inside it. Do not parse huge code blocks in context.
|
||||
- **Use sub-agents as subcontractors** (see BASELINE-PROMPT.md §12): scoped spec in, distilled deliverable out. Never read 10+ files sequentially; batch into 2-3 agent calls. Read the 3-4 files you'll edit yourself; delegate the rest.
|
||||
- **Command timeouts (NON-NEGOTIABLE):** Every command that touches a remote
|
||||
system MUST be wrapped with `timeout`. Hard limits: 30s for quick reads
|
||||
(status, ps, ls), 120s for standard operations, 300s for deployments/pulls.
|
||||
If a command hits the timeout, STOP and investigate root cause — never
|
||||
blindly retry. A hung command is a failed command. Detect failure fast,
|
||||
diagnose, fix, move on. This applies to ALL tools — bash, docker, CLIs,
|
||||
sub-agents.
|
||||
|
||||
## CI/CD
|
||||
|
||||
@@ -156,6 +236,25 @@ docker run --rm --env-file ~/projects/KNEL-AIMiddleware/discourse-cli/.env \
|
||||
- **Secrets:** NEVER commit secrets. Credentials come from env vars / `.env` (gitignored). Use placeholders in `.env.example`.
|
||||
- **IAC testing:** test against the corresponding `sectestbed-` VM (snapshot to base state). `preprod-` VMs for vendor-upgrade testing. Do not require AWX as a prerequisite.
|
||||
|
||||
## Remote Access (projects with remote hosts)
|
||||
|
||||
**If this project manages remote hosts** (servers, VMs, network devices),
|
||||
these rules are NON-NEGOTIABLE:
|
||||
|
||||
- **Ship a `remote.sh` chokepoint.** ALL SSH/SCP to any host MUST route
|
||||
through a single chokepoint script (`tests/remote.sh` or equivalent).
|
||||
NEVER call `ssh`/`scp` directly — the harness blocks raw ssh and the
|
||||
command scanner rejects it. One script = one place to configure
|
||||
hosts/users/keys, one place to audit.
|
||||
- **DNS names ONLY.** NEVER use IP address literals in any command, script,
|
||||
or config. ALWAYS use DNS names. If a DNS name does not resolve, fix DNS
|
||||
first — do not fall back to IP literals. This eliminates the per-session
|
||||
discovery tax of finding the right IP for each host.
|
||||
- **If using qemu-guest-agent:** visibility only (`qm guest cmd <id> ping`,
|
||||
`network-get-interfaces`). NEVER use `qm guest exec` or any wrapper as an
|
||||
execution, access, or key-delivery channel. SSH is the only approved
|
||||
remote access path.
|
||||
|
||||
## Key Commands
|
||||
|
||||
At a terminal (Mode 1), use `make` or call scripts directly:
|
||||
@@ -209,6 +308,9 @@ Bypass with `--no-verify` in genuine emergencies only.
|
||||
- Install language toolchains on the host.
|
||||
- Use `:latest` image tags or Docker's default container naming.
|
||||
- Author long-form docs in gitea — use Discourse.
|
||||
- Call `ssh`/`scp` directly — always route through the project's `remote.sh` chokepoint.
|
||||
- Use IP address literals for any host — always use DNS names.
|
||||
- Use `qm guest exec` or any guest-agent execution channel — SSH only.
|
||||
- Commit secrets.
|
||||
- Run destructive git operations without explicit instruction.
|
||||
- Declare the work "done" — the human decides that.
|
||||
|
||||
+40
-11
@@ -34,7 +34,28 @@
|
||||
- **Cross-referencing is mandatory.** Every Redmine ticket links to its Discourse doc; every Discourse doc links to its Redmine ticket(s); every commit references `[#NNN]`; every PR links to both Redmine and Discourse. Keep them in sync at all times.
|
||||
- **Code, docs, and tests must be kept in sync at all times.** When you change code, update the corresponding docs (Discourse) and tests in the same commit. Never leave them out of sync.
|
||||
|
||||
## 4. Git workflow
|
||||
## 4. Infrastructure change approval workflow
|
||||
|
||||
For infrastructure (operations) work, agents do NOT execute changes
|
||||
without explicit human approval. The workflow is:
|
||||
|
||||
1. **Prepare:** Agent creates scripts, configs, and a plan. Documents
|
||||
the exact changes, blast radius, and rollback procedure in a
|
||||
Redmine ticket.
|
||||
2. **Review:** Human reviews and approves (or rejects) via Redmine.
|
||||
3. **Execute:** Agent applies the change ONLY after approval.
|
||||
4. **Verify:** Agent verifies the change worked and documents results
|
||||
in Discourse.
|
||||
|
||||
This applies to all production infrastructure: network configs, host
|
||||
tuning, VM settings, DNS records, switch configs, etc. Read-only
|
||||
audits and probes do NOT require approval — only changes that alter
|
||||
system behavior.
|
||||
|
||||
Code projects (software development) follow normal git/PR workflow and
|
||||
do not require per-change Redmine approval.
|
||||
|
||||
## 5. Git workflow
|
||||
|
||||
- **Use the `tea` CLI for pull requests.**
|
||||
- **Work smart off master** generally. Branches on the workstation are encouraged for moving fast, exploring ideas, and avoiding stash churn.
|
||||
@@ -42,13 +63,13 @@
|
||||
- Branching strategy is open to per-project discussion.
|
||||
- **Commits and PRs must cross-link** to Redmine tickets (`[#NNN]` in subject or body) and Discourse docs (in PR body).
|
||||
|
||||
## 5. CI/CD — shift left, keep in lockstep
|
||||
## 6. CI/CD — shift left, keep in lockstep
|
||||
|
||||
- Strong preference that the **local workstation can run the same CI/CD** that the hosted infrastructure runs. Maintain them in lockstep across all projects.
|
||||
- **The further left CI/CD runs, the better.** Catch it before push, before PR, before merge.
|
||||
- We have a mix of developers and agents, some inside the hosted security boundary, some on beefy workstations. CI/CD must work for all of them.
|
||||
|
||||
## 6. Docker and Kubernetes for everything
|
||||
## 7. Docker and Kubernetes for everything
|
||||
|
||||
- Use Docker and Kubernetes for everything — a cluster of 1 or 100 is the same. Don't presume scale. Containers are containers; k8s is k8s.
|
||||
- **All development work happens in containers** — custom, off-the-shelf, or a mix. `docker pull` freely without asking.
|
||||
@@ -58,7 +79,7 @@
|
||||
- **One-off utility needed?** `docker pull` a pinned image and run it ephemerally. Do not install on the host.
|
||||
- **Broadly useful tool?** Create a Redmine ticket requesting the human add it via the system package manager. Do not install it yourself.
|
||||
|
||||
## 7. Infrastructure-as-Code testing
|
||||
## 8. Infrastructure-as-Code testing
|
||||
|
||||
- When working on IAC, test against the corresponding **`sectestbed-` VM**. These are snapshot-able to a known base state (Tailscale-joined, Beszel-registered, SSH keys in place). The base state evolves; the delta of tested code shrinks over time.
|
||||
- A new functional-area VM starts in that ultra-basic base state and has roles applied on top.
|
||||
@@ -66,7 +87,7 @@
|
||||
- Compliance mitigations may need to flow through both `sectestbed-` and `preprod-` testing, in lockstep. Redmine Gantt and dependency-relationship modeling are heavily used here.
|
||||
- **Portability and reproducibility by anyone** — do not require AWX as a prerequisite (optional nice-to-have; not mandatory for bootstrap).
|
||||
|
||||
## 8. STATUS.md — scratchpad, not system of record
|
||||
## 9. STATUS.md — scratchpad, not system of record
|
||||
|
||||
- **STATUS.md is a durable, git-tracked scratchpad for token efficiency.** It is not the system of record (Redmine is).
|
||||
- The agent fully owns STATUS.md; the human only consumes it.
|
||||
@@ -74,24 +95,32 @@
|
||||
- The harness todo tool is fine for tracking *current* work; STATUS.md is the durable cross-session record.
|
||||
- **STATUS.md has an Inbox section.** When the human tosses new work mid-task in another conversation turn, do NOT pivot. Log it in the Inbox. If it's materially different, spin up a Redmine ticket.
|
||||
|
||||
## 9. Questions file — `questions-v(N).md`
|
||||
## 10. Questions file — `questions-v(N).md`
|
||||
|
||||
- Capture questions in a git-tracked, versioned file: `questions-v1.md`, `questions-v2.md`, …
|
||||
- **NEVER use a harness "question"/"ask user" tool** (structured prompts,
|
||||
modal forms, tabbed questions, etc.). Ever. They are banned across every
|
||||
project. They are not portable across harnesses, they don't version
|
||||
history, and they bypass the git-tracked record. This is non-negotiable.
|
||||
- **All questions go in the git-tracked `questions-v(N).md` file.** Write
|
||||
the question; the human edits the answer inline in the same file. This
|
||||
preserves history, works under every harness, and keeps reasoning next
|
||||
to the answer. Version up (`questions-v2.md`, …) when a round lands.
|
||||
- Capture questions in a versioned file: `questions-v1.md`, `questions-v2.md`, …
|
||||
- The human reviews and edits it inline. Version it when a round of answers goes in.
|
||||
- Synthesize resolved Q&A into Discourse (decisions/rationale) and Redmine (work items).
|
||||
|
||||
## 10. Belt-and-suspenders protocol enforcement
|
||||
## 11. Belt-and-suspenders protocol enforcement
|
||||
|
||||
- Enforce the rules in **two layers**: prose policy in `AGENTS.md` and mechanical enforcement in git `pre-commit` / `pre-push` hooks. Harness-specific hooks (e.g. Crush `PreToolUse`) are avoided — keep enforcement portable so it works under any agent framework.
|
||||
- Never rely on memory or prose alone.
|
||||
|
||||
## 11. Gardening — keep docs from sprawling
|
||||
## 12. Gardening — keep docs from sprawling
|
||||
|
||||
- Run a routine **gardening loop.** Agents are disciplined with code but tend to sprawl `.md` files everywhere.
|
||||
- Keep docs, code, and tests in sync at all times.
|
||||
- `scripts/garden.sh` reports `.md` sprawl and files that should be migrated to Discourse.
|
||||
|
||||
## 12. Sub-agents as subcontractors
|
||||
## 13. Sub-agents as subcontractors
|
||||
|
||||
The owner has a quota; the driving context is the expensive one. The biggest
|
||||
token cost is prefix mutation, not per-call work — so keep the driving prefix
|
||||
@@ -105,7 +134,7 @@ stable and push volatility into side-channels (sub-agents, STATUS.md).
|
||||
|
||||
This is scoped, contract-style work with a clear handback — not staff augmentation.
|
||||
|
||||
## 13. TDD and linting
|
||||
## 14. TDD and linting
|
||||
|
||||
- **Red/green TDD for all code.** Write the failing test first.
|
||||
- **Linters on all code, as early as possible.** Be token-efficient — let deterministic tools find the issues.
|
||||
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
# hooks/ticket-gate.sh — enforce ticket-first work policy
|
||||
#
|
||||
# Blocks modifying operations until an active ticket is established.
|
||||
# The agent sets the active ticket via: echo '#NNN' > .crush/active-ticket
|
||||
# And clears it when done: > .crush/active-ticket
|
||||
#
|
||||
# Exempts read-only and management commands (so you can create tickets,
|
||||
# run audits, check status, etc.).
|
||||
set -euo pipefail
|
||||
|
||||
TICKET_FILE="${CRUSH_PROJECT_DIR}/.crush/active-ticket"
|
||||
TOOL="${CRUSH_TOOL_NAME:-}"
|
||||
CMD="${CRUSH_TOOL_INPUT_COMMAND:-}"
|
||||
|
||||
# Read-only tools — always allowed
|
||||
case "$TOOL" in
|
||||
view|ls|grep|glob|agent|sourcegraph|fetch|agentic_fetch|download|lsp_diagnostics|lsp_symbols|lsp_definition|lsp_references|lsp_call_hierarchy|crush_info|crush_logs|question|todos)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# For bash tool: exempt read-only and management commands
|
||||
if [ "$TOOL" = "bash" ]; then
|
||||
# Ticket/doc/dns management — always allowed
|
||||
case "$CMD" in
|
||||
*"redmine-cli"*|*"discourse-cli"*|*"dns-cli"*|*"technitium"*) exit 0 ;;
|
||||
esac
|
||||
# Read-only git
|
||||
case "$CMD" in
|
||||
*"git status"*|*"git log"*|*"git diff"*|*"git show"*|*"git branch"*) exit 0 ;;
|
||||
esac
|
||||
# Repo hygiene scripts
|
||||
case "$CMD" in
|
||||
*"check-rules"*|*"setup-hooks"*|*"shellcheck"*|*"run-tests"*) exit 0 ;;
|
||||
esac
|
||||
# Monitoring/probe commands
|
||||
case "$CMD" in
|
||||
*"tailscale status"*|*"access-matrix"*) exit 0 ;;
|
||||
esac
|
||||
# Setting/clearing the active ticket
|
||||
case "$CMD" in
|
||||
*active-ticket*) exit 0 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# For edit/write: exempt policy/hook files (these ARE the policy)
|
||||
FILE_PATH="${CRUSH_TOOL_INPUT_FILE_PATH:-}"
|
||||
case "$FILE_PATH" in
|
||||
*/AGENTS.md|*/check-rules.sh|*/crush.json|*/hooks/*)
|
||||
if [ "$TOOL" = "write" ] || [ "$TOOL" = "edit" ] || [ "$TOOL" = "multiedit" ]; then
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check for active ticket
|
||||
if [ -f "$TICKET_FILE" ] && [ -s "$TICKET_FILE" ]; then
|
||||
TICKET=$(cat "$TICKET_FILE")
|
||||
printf '{"context":"Active ticket: %s"}\n' "$TICKET"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# No active ticket — block
|
||||
cat >&2 <<'MSG'
|
||||
TICKET GATE: No active ticket set.
|
||||
|
||||
This project requires ticket-governed work (AGENTS.md Agent Authority).
|
||||
Before modifying systems or code, set the active ticket:
|
||||
|
||||
echo '#NNN' > .crush/active-ticket
|
||||
|
||||
If no ticket exists yet, create one first (redmine-cli create), then set it.
|
||||
Clear the ticket when work is complete:
|
||||
|
||||
> .crush/active-ticket
|
||||
MSG
|
||||
exit 2
|
||||
+6
-8
@@ -16,14 +16,12 @@
|
||||
|
||||
Go with option a. The tea command is setup on this workstation (and on ultix-offstage). I guess, also capture that the tea command (and docker login) are setup on my workstations, so that in the future, projects know they can use tea to setup a repo. Also, i want this to be TSYS wide, so it should go under the TSYSGroupCorporate organization. Call the repo: TSYSGroupAIOS . Make it a template repository.
|
||||
|
||||
### Q2. The bin/ wrapper gap (redmine-cli / discourse-cli)
|
||||
- **Context:** PFVCluster's operational.md and AGENTS.md reference `~/daytoday/redmine/bin/redmine` and `~/daytoday/discourse/bin/discourse` as the entrypoints. But `ls ~/daytoday/{redmine,discourse}/` shows only `.gitignore` + `AGENTS.md` (+ MIGRATION-PLAN.md for discourse) — no `bin/`, no Dockerfile. The actual CLI source lives in `~/projects/KNEL-AIMiddleware/{redmine,discourse}-cli/`.
|
||||
- **Question:** Are the `bin/` wrappers something that should exist (and were lost / never committed), or is the documentation aspirational? Should the template reference these CLIs at all, or stay tool-agnostic?
|
||||
- **Answer:** _(human)_
|
||||
- **Decision:** _(human/agent)_
|
||||
- **Synthesized to:** —
|
||||
|
||||
The clis should exist. Maybe the AGENTS.md reference the actual paths? I dont need duplicate code. I think i was using the directories as kind of "shortcuts" vs the ~/projects/... path every time. So, for this repo, reference the full path/container name/invoke notes. Does that make sense?
|
||||
### Q2. The bin/ wrapper gap (redmine-cli / discourse-cli) — RESOLVED (discourse)
|
||||
- **Context:** Projects referenced `~/daytoday/redmine/bin/redmine` and `~/daytoday/discourse/bin/discourse` as entrypoints — thin shortcut wrappers around the real CLI containers. The actual CLI source lived in `~/projects/KNEL-AIMiddleware/{redmine,discourse}-cli/`.
|
||||
- **Question:** Are the `bin/` wrappers something that should exist, or is the documentation aspirational? Should the template reference these CLIs at all, or stay tool-agnostic?
|
||||
- **Answer:** Reference the real container invocation (full path/container name/invoke notes); no duplicate code via shortcut wrappers.
|
||||
- **Decision:** No `bin/` wrappers. Invoke the real container with `docker run`. Both CLI sources have been merged into `~/projects/KNEL-AIMiddleware/tooling-cli/{discourse,redmine}/` (CLI source + Dockerfile + README + AGENTS.md). The canonical invocation is documented in AGENTS.md §CLI invocation and uses `--env-file ~/.creds/{discourse,redmine}.env` (centralized credential store) — no system-dependent paths in the rules. The old `~/daytoday/{discourse,redmine}` workspaces and the `{discourse,redmine}-cli/` subdirs were removed.
|
||||
- **Synthesized to:** `~/daytoday/meta/AGENTS.md` §CLI invocation; `tooling-cli/{discourse,redmine}/`
|
||||
|
||||
### Q3. Should the template ship the Discourse pointer-header pattern?
|
||||
- **Context:** PFVCluster migrated 36 in-repo `.md` files to 10-line pointer stubs citing `https://community.turnsys.com/t/<N>`. The template currently has `scripts/garden.sh` that *warns* about oversized non-Discourse `.md`, but doesn't enforce the pointer-header format.
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env bash
|
||||
# agent-profile.sh — switch agent identity context.
|
||||
#
|
||||
# Source this script to become a specific agent. Sets git author/committer
|
||||
# identity, registers a tea login (from Bitwarden), and exports the agent
|
||||
# name for other tools (bw-run.sh, clone-as.sh, etc.).
|
||||
#
|
||||
# Usage:
|
||||
# . agent-profile.sh vp-techops
|
||||
# . agent-profile.sh vp-secops
|
||||
# . agent-profile.sh reachableceo (back to Charles's identity)
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Bitwarden session active (BW_SESSION exported) OR BW_CLIENTID/
|
||||
# BW_CLIENTSECRET in ~/.config/bw/env
|
||||
# - Agent credentials provisioned in Bitwarden (see agent-identity-bootstrap.md)
|
||||
|
||||
# Do NOT set -euo pipefail — this script is sourced, and the caller controls
|
||||
# their own shell options. We guard each operation explicitly.
|
||||
|
||||
AGENT_PROFILE_NAME="${1:-}"
|
||||
|
||||
if [ -z "$AGENT_PROFILE_NAME" ]; then
|
||||
echo "Usage: . agent-profile.sh <agent-name>" >&2
|
||||
echo " e.g. . agent-profile.sh vp-techops" >&2
|
||||
echo " . agent-profile.sh reachableceo" >&2
|
||||
# shellcheck disable=SC2317 # reachable when executed (not sourced)
|
||||
return 1 2>/dev/null || exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Display name mapping (for git author name)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
case "$AGENT_PROFILE_NAME" in
|
||||
vp-techops) _display="VP TechOps" ;;
|
||||
vp-secops) _display="VP SecOps" ;;
|
||||
vp-techcompliance) _display="VP TechCompliance" ;;
|
||||
coo) _display="Chief Operating Officer" ;;
|
||||
svp-knel) _display="SVP KNEL" ;;
|
||||
svp-tctc) _display="SVP TCTC" ;;
|
||||
reachableceo) _display="Charles N Wyble" ;;
|
||||
*)
|
||||
_display="$AGENT_PROFILE_NAME"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Domain mapping
|
||||
_domain="turnsys.com"
|
||||
if [ "$AGENT_PROFILE_NAME" = "reachableceo" ]; then
|
||||
_email="reachableceo@turnsys.com"
|
||||
else
|
||||
_email="${AGENT_PROFILE_NAME}@${_domain}"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Export agent identity environment variables
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
export AGENT_NAME="$AGENT_PROFILE_NAME"
|
||||
export AGENT_DISPLAY="$_display"
|
||||
export AGENT_EMAIL="$_email"
|
||||
export GIT_AUTHOR_NAME="$_display"
|
||||
export GIT_COMMITTER_NAME="$_display"
|
||||
export GIT_AUTHOR_EMAIL="$_email"
|
||||
export GIT_COMMITTER_EMAIL="$_email"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ensure Bitwarden session is active
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_bw_session_setup() {
|
||||
if [ -n "${BW_SESSION:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local bw_env="${HOME}/.config/bw/env"
|
||||
if [ -f "$bw_env" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "$bw_env"
|
||||
fi
|
||||
|
||||
if [ -n "${BW_CLIENTID:-}" ] && [ -n "${BW_CLIENTSECRET:-}" ]; then
|
||||
bw login --apikey >/dev/null 2>&1 || true
|
||||
BW_SESSION=$(BW_PASSWORD="${BW_PASSWORD:-}" bw unlock --raw 2>/dev/null || true)
|
||||
if [ -n "$BW_SESSION" ]; then
|
||||
export BW_SESSION
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "agent-profile: WARNING — no BW session, tea login will not be configured" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Register tea login for this agent (if credentials exist in BW)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_tea_setup() {
|
||||
# Only set up tea for agents that have Gitea credentials in BW
|
||||
local token
|
||||
token=$(bw get password "${AGENT_NAME} Gitea" 2>/dev/null || echo "")
|
||||
if [ -z "$token" ]; then
|
||||
# Not an error — some agents may not have Gitea access yet
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if login already exists
|
||||
if tea login list --output csv 2>/dev/null | grep -q "^${AGENT_NAME},"; then
|
||||
# Already registered — just set as default
|
||||
tea login default "$AGENT_NAME" >/dev/null 2>&1 || true
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Register the login
|
||||
tea login add \
|
||||
--name "$AGENT_NAME" \
|
||||
--url "https://git.knownelement.com" \
|
||||
--token "$token" \
|
||||
--ssh-host "git.knownelement.com" \
|
||||
>/dev/null 2>&1 || true
|
||||
|
||||
tea login default "$AGENT_NAME" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
if _bw_session_setup; then
|
||||
_tea_setup
|
||||
fi
|
||||
|
||||
# Clean up helper functions from the environment
|
||||
unset -f _bw_session_setup _tea_setup 2>/dev/null || true
|
||||
unset _display _email _domain 2>/dev/null || true
|
||||
|
||||
echo "agent-profile: now operating as ${AGENT_PROFILE_NAME} (${AGENT_DISPLAY})" >&2
|
||||
Executable
+105
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
# bw-git-credential.sh — Git credential helper that sources Gitea tokens from Bitwarden.
|
||||
#
|
||||
# This eliminates the need for per-agent SSH keys or on-disk tokens for git
|
||||
# operations. Each repo has a git identity (user.email) that identifies which
|
||||
# agent is working. This helper reads that identity, fetches the matching
|
||||
# Gitea API token from Bitwarden, and provides it to git.
|
||||
#
|
||||
# Setup (once per environment, in ~/.gitconfig):
|
||||
# [credential "https://git.knownelement.com"]
|
||||
# helper = /path/to/bw-git-credential.sh
|
||||
#
|
||||
# How it works:
|
||||
# - During git push (in an existing repo): reads `git config user.email`
|
||||
# to determine the agent, fetches "<agent> Gitea" from Bitwarden.
|
||||
# - During git clone (no repo yet): reads AGENT_CLONE_AS env var, which
|
||||
# clone-as.sh sets before calling git clone.
|
||||
# - If neither is set, exits silently (lets other credential helpers run).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GITEA_HOST="git.knownelement.com"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Read git credential input from stdin
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
declare -A cred
|
||||
while IFS='=' read -r key value; do
|
||||
# Empty line terminates the credential block
|
||||
[ -z "$key" ] && break
|
||||
cred["$key"]="$value"
|
||||
done
|
||||
|
||||
host="${cred[protocol]:-}://${cred[host]:-}"
|
||||
|
||||
# Only handle our Gitea instance
|
||||
if [ "${cred[host]:-}" != "$GITEA_HOST" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Determine which agent identity to use
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
agent=""
|
||||
|
||||
# During clone: clone-as.sh sets this env var
|
||||
if [ -n "${AGENT_CLONE_AS:-}" ]; then
|
||||
agent="$AGENT_CLONE_AS"
|
||||
else
|
||||
# During push/fetch: read the repo's configured identity
|
||||
agent_email=$(git config user.email 2>/dev/null || echo "")
|
||||
if [ -n "$agent_email" ]; then
|
||||
agent=$(echo "$agent_email" | cut -d@ -f1)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$agent" ]; then
|
||||
# No agent identity — let other credential helpers handle it
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ensure Bitwarden session is active
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
if [ -z "${BW_SESSION:-}" ]; then
|
||||
# Try to establish from the env file
|
||||
bw_env="${HOME}/.config/bw/env"
|
||||
if [ -f "$bw_env" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "$bw_env"
|
||||
fi
|
||||
|
||||
if [ -n "${BW_CLIENTID:-}" ] && [ -n "${BW_CLIENTSECRET:-}" ]; then
|
||||
bw login --apikey >/dev/null 2>&1 || true
|
||||
export BW_SESSION
|
||||
BW_SESSION=$(BW_PASSWORD="${BW_PASSWORD:-}" bw unlock --raw 2>/dev/null || true)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "${BW_SESSION:-}" ]; then
|
||||
echo "bw-git-credential: no BW session — cannot fetch token for $agent" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fetch the Gitea token from Bitwarden
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
item_name="$agent Gitea"
|
||||
token=$(bw get password "$item_name" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$token" ]; then
|
||||
echo "bw-git-credential: no Gitea token in BW for: $item_name" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Output credentials for git
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "username=$agent"
|
||||
echo "password=$token"
|
||||
Executable
+157
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env bash
|
||||
# bw-run.sh — run a CLI command with credentials sourced from Bitwarden.
|
||||
#
|
||||
# Replaces the --env-file ~/.creds/<service>.env pattern. Each agent identity
|
||||
# stores its own API keys/passwords in a Bitwarden collection. This script
|
||||
# fetches the credential, writes it to a temp env file, runs the CLI, and
|
||||
# cleans up — no credential material persists on disk.
|
||||
#
|
||||
# Usage:
|
||||
# bw-run.sh <agent> <service> <image> [args...]
|
||||
#
|
||||
# Examples:
|
||||
# bw-run.sh vp-techops redmine git.knownelement.com/reachableceo/redmine-cli:latest list --assigned-to-me -p 55
|
||||
# bw-run.sh vp-techops discourse git.knownelement.com/reachableceo/discourse-cli:latest ls -c vp-techops
|
||||
# bw-run.sh reachableceo redmine git.knownelement.com/reachableceo/redmine-cli:latest whoami
|
||||
#
|
||||
# Prerequisites:
|
||||
# - BW_CLIENTID and BW_CLIENTSECRET exported (or in ~/.config/bw/env)
|
||||
# - Or BW_SESSION exported from a prior `bw unlock`
|
||||
#
|
||||
# Bitwarden item convention:
|
||||
# Collection: <agent-name> (or "shared" for cross-agent creds)
|
||||
# Item name: "<agent-name> <Service>" (e.g., "vp-techops Redmine")
|
||||
# Fields: URL, USERNAME, PASSWORD, API_KEY, and custom fields as needed
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck disable=SC1091
|
||||
source "$HERE/lib/common.sh"
|
||||
|
||||
###############################################################################
|
||||
|
||||
print_usage() {
|
||||
sed -n '2,26p' "$0"
|
||||
exit 0
|
||||
}
|
||||
|
||||
if [ $# -lt 3 ] || [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
|
||||
print_usage
|
||||
fi
|
||||
|
||||
AGENT="$1"
|
||||
SERVICE="$2"
|
||||
IMAGE="$3"
|
||||
shift 3
|
||||
|
||||
ITEM_NAME="${AGENT} ${SERVICE}"
|
||||
|
||||
###############################################################################
|
||||
# Ensure Bitwarden session is active
|
||||
###############################################################################
|
||||
|
||||
ensure_bw_session() {
|
||||
# If BW_SESSION is already set, trust it
|
||||
if [ -n "${BW_SESSION:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try machine-to-machine auth via client credentials
|
||||
if [ -n "${BW_CLIENTID:-}" ] && [ -n "${BW_CLIENTSECRET:-}" ]; then
|
||||
log_info "Authenticating to Bitwarden via API key..."
|
||||
if bw login --apikey >/dev/null 2>&1; then
|
||||
export BW_SESSION
|
||||
BW_SESSION=$(bw unlock --raw 2>/dev/null || true)
|
||||
if [ -z "$BW_SESSION" ]; then
|
||||
die "Bitwarden unlock failed after API-key login."
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Try reading from the bw env file
|
||||
local bw_env="${HOME}/.config/bw/env"
|
||||
if [ -f "$bw_env" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "$bw_env"
|
||||
if [ -n "${BW_CLIENTID:-}" ] && [ -n "${BW_CLIENTSECRET:-}" ]; then
|
||||
log_info "Authenticating to Bitwarden via env file..."
|
||||
bw login --apikey >/dev/null 2>&1 || true
|
||||
export BW_SESSION
|
||||
BW_SESSION=$(bw unlock --raw 2>/dev/null || true)
|
||||
if [ -n "$BW_SESSION" ]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
die "No Bitwarden session. Set BW_SESSION or BW_CLIENTID/BW_CLIENTSECRET."
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
|
||||
ensure_bw_session
|
||||
|
||||
log_info "Fetching credential: ${ITEM_NAME}"
|
||||
|
||||
# Fetch the Bitwarden item and extract fields into env vars
|
||||
RAW_JSON=$(bw get item "$ITEM_NAME" 2>/dev/null) || die "Bitwarden item not found: ${ITEM_NAME}"
|
||||
|
||||
# Build env file content from the JSON response
|
||||
ENV_CONTENT=$(echo "$RAW_JSON" | jq -r '
|
||||
def safename(f): f | gsub("[^A-Za-z0-9_]"; "_") | ascii_upcase;
|
||||
.login as $login |
|
||||
# Standard fields
|
||||
(
|
||||
if $login.uris[0].uri then "URL=\($login.uris[0].uri)\n" else "" end
|
||||
) +
|
||||
(
|
||||
if $login.username then "USERNAME=\($login.username)\n" else "" end
|
||||
) +
|
||||
(
|
||||
if $login.password then "PASSWORD=\($login.password)\n" else "" end
|
||||
) +
|
||||
# Map known field names to common env var patterns
|
||||
(
|
||||
.fields[]? |
|
||||
if .name == "API_KEY" or .name == "api_key" or .name == "apikey" then
|
||||
"API_KEY=\(.value)\n"
|
||||
elif .name == "token" or .name == "TOKEN" then
|
||||
"TOKEN=\(.value)\n"
|
||||
else
|
||||
"\(safename(.name))=\(.value)\n"
|
||||
end
|
||||
) +
|
||||
# Also emit REDMINE_API_KEY / DISCOURSE_API_KEY etc. from the password field
|
||||
# (many CLI tools expect the key in the password field)
|
||||
""
|
||||
') || die "Failed to parse Bitwarden item JSON."
|
||||
|
||||
if [ -z "$ENV_CONTENT" ]; then
|
||||
die "No credential fields found in Bitwarden item: ${ITEM_NAME}"
|
||||
fi
|
||||
|
||||
# Also check if the password field IS the API key (common for CLI creds)
|
||||
# and add a service-specific key env var based on the service name
|
||||
SERVICE_UPPER=$(echo "$SERVICE" | tr '[:lower:]' '[:upper:]' | tr '-' '_')
|
||||
API_KEY_VAR="${SERVICE_UPPER}_API_KEY"
|
||||
|
||||
# If the service-specific key var isn't already in ENV_CONTENT, try adding it from password
|
||||
if ! echo "$ENV_CONTENT" | grep -q "^${API_KEY_VAR}="; then
|
||||
PASSWORD_VAL=$(echo "$RAW_JSON" | jq -r '.login.password // empty')
|
||||
if [ -n "$PASSWORD_VAL" ]; then
|
||||
ENV_CONTENT="${API_KEY_VAR}=${PASSWORD_VAL}
|
||||
${ENV_CONTENT}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Write to temp file, cleaned up on exit
|
||||
ENVFILE=$(mktemp "/tmp/bw-${AGENT}-${SERVICE}-XXXXXX.env")
|
||||
trap 'rm -f "$ENVFILE"' EXIT
|
||||
chmod 600 "$ENVFILE"
|
||||
printf '%s' "$ENV_CONTENT" > "$ENVFILE"
|
||||
|
||||
log_ok "Credential sourced. Running: ${IMAGE} $*"
|
||||
|
||||
# Run the CLI with BW-sourced credentials
|
||||
docker run --rm --env-file "$ENVFILE" "$IMAGE" "$@"
|
||||
Executable
+139
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env bash
|
||||
# clone-as.sh — clone a git repository as a specific agent identity.
|
||||
#
|
||||
# Wraps `git clone` so the clone authenticates with the agent's Gitea token
|
||||
# (from Bitwarden) and the resulting repo has the correct per-repo git
|
||||
# identity (user.name + user.email) set automatically.
|
||||
#
|
||||
# Usage:
|
||||
# clone-as.sh <agent> <repo-url> [target-dir]
|
||||
#
|
||||
# Examples:
|
||||
# clone-as.sh vp-techops https://git.knownelement.com/KNEL/PFVCluster.git
|
||||
# clone-as.sh vp-techops https://git.knownelement.com/KNEL/PFVCluster.git ~/projects/pfv
|
||||
# clone-as.sh reachableceo ssh://git@git.knownelement.com:29418/KNEL/PFVCluster.git
|
||||
#
|
||||
# After cloning, subsequent `git push` uses bw-git-credential.sh (configured
|
||||
# in ~/.gitconfig) which reads the repo's user.email to fetch the token.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck disable=SC1091
|
||||
source "$HERE/lib/common.sh"
|
||||
|
||||
###############################################################################
|
||||
|
||||
if [ $# -lt 2 ]; then
|
||||
sed -n '2,22p' "$0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CLONE_AGENT="$1"
|
||||
CLONE_URL="$2"
|
||||
CLONE_DIR="${3:-}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Display name mapping
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
case "$CLONE_AGENT" in
|
||||
vp-techops) display="VP TechOps" ;;
|
||||
vp-secops) display="VP SecOps" ;;
|
||||
vp-techcompliance) display="VP TechCompliance" ;;
|
||||
coo) display="Chief Operating Officer" ;;
|
||||
svp-knel) display="SVP KNEL" ;;
|
||||
svp-tctc) display="SVP TCTC" ;;
|
||||
reachableceo) display="Charles N Wyble" ;;
|
||||
*) display="$CLONE_AGENT" ;;
|
||||
esac
|
||||
|
||||
if [ "$CLONE_AGENT" = "reachableceo" ]; then
|
||||
email="reachableceo@turnsys.com"
|
||||
else
|
||||
email="${CLONE_AGENT}@turnsys.com"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# If SSH URL, clone directly (SSH key auth, no credential helper needed)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
if [[ "$CLONE_URL" == ssh://* ]] || [[ "$CLONE_URL" == git@* ]]; then
|
||||
log_info "Cloning (SSH): $CLONE_URL as $CLONE_AGENT"
|
||||
|
||||
if [ -n "$CLONE_DIR" ]; then
|
||||
git clone "$CLONE_URL" "$CLONE_DIR"
|
||||
cd "$CLONE_DIR"
|
||||
else
|
||||
git clone "$CLONE_URL"
|
||||
# Derive repo dir name from URL
|
||||
repo_base=$(basename "$CLONE_URL" .git)
|
||||
cd "$repo_base"
|
||||
fi
|
||||
|
||||
# Set per-repo identity
|
||||
git config user.name "$display"
|
||||
git config user.email "$email"
|
||||
log_ok "Cloned to $(pwd) — identity: $display <$email>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# HTTPS URL — need credential helper for clone authentication
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Ensure Bitwarden session
|
||||
if [ -z "${BW_SESSION:-}" ]; then
|
||||
bw_env="${HOME}/.config/bw/env"
|
||||
if [ -f "$bw_env" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "$bw_env"
|
||||
fi
|
||||
if [ -n "${BW_CLIENTID:-}" ] && [ -n "${BW_CLIENTSECRET:-}" ]; then
|
||||
bw login --apikey >/dev/null 2>&1 || true
|
||||
export BW_SESSION
|
||||
BW_SESSION=$(BW_PASSWORD="${BW_PASSWORD:-}" bw unlock --raw 2>/dev/null || true)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "${BW_SESSION:-}" ]; then
|
||||
die "No BW session — cannot fetch Gitea token for $CLONE_AGENT"
|
||||
fi
|
||||
|
||||
# Fetch token for clone auth
|
||||
token=$(bw get password "$CLONE_AGENT Gitea" 2>/dev/null || echo "")
|
||||
if [ -z "$token" ]; then
|
||||
die "No Gitea token in BW for: $CLONE_AGENT Gitea"
|
||||
fi
|
||||
|
||||
log_info "Cloning (HTTPS): $CLONE_URL as $CLONE_AGENT"
|
||||
|
||||
# Set AGENT_CLONE_AS so bw-git-credential.sh knows which agent this is
|
||||
# (needed only if the credential helper is wired; we also inject directly)
|
||||
export AGENT_CLONE_AS="$CLONE_AGENT"
|
||||
|
||||
# Inject token into URL for clone auth, then strip after clone
|
||||
# (avoids storing the token in .git/config)
|
||||
url_path="${CLONE_URL#https://}"
|
||||
injected_url="https://${CLONE_AGENT}:${token}@${url_path}"
|
||||
|
||||
if [ -n "$CLONE_DIR" ]; then
|
||||
git clone "$injected_url" "$CLONE_DIR"
|
||||
cd "$CLONE_DIR"
|
||||
else
|
||||
git clone "$injected_url"
|
||||
repo_base=$(basename "$CLONE_URL" .git)
|
||||
cd "$repo_base"
|
||||
fi
|
||||
|
||||
# Strip the token from the remote URL (so it's not stored in .git/config)
|
||||
remote_url=$(git remote get-url origin 2>/dev/null || echo "")
|
||||
clean_url="https://${remote_url#https://*@}"
|
||||
git remote set-url origin "$clean_url"
|
||||
|
||||
# Set per-repo identity
|
||||
git config user.name "$display"
|
||||
git config user.email "$email"
|
||||
|
||||
log_ok "Cloned to $(pwd) — identity: $display <$email>"
|
||||
log_info "Remote URL sanitized (token stripped). Push will use bw-git-credential.sh."
|
||||
Reference in New Issue
Block a user