mirror of
https://github.com/msitarzewski/agency-agents.git
synced 2026-06-10 21:24:56 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 247cf4d0c8 |
@@ -69,18 +69,6 @@ Browse the agents below and copy/adapt the ones you need!
|
||||
./scripts/install.sh --tool codex
|
||||
```
|
||||
|
||||
**Install only the teams you need** (not everyone wants all 15 divisions):
|
||||
|
||||
```bash
|
||||
./scripts/install.sh # interactive wizard: pick tools + teams
|
||||
./scripts/install.sh --tool claude-code --division engineering,security
|
||||
./scripts/install.sh --tool cursor --agent frontend-developer,ui-designer
|
||||
./scripts/install.sh --list teams # see every team + agent count
|
||||
./scripts/install.sh --tool opencode --division engineering --dry-run
|
||||
```
|
||||
|
||||
> **OpenCode note:** OpenCode's runtime currently registers only ~119 agents and silently drops the rest ([upstream bug](https://github.com/anomalyco/opencode/issues/27988)). Installing a subset with `--division` keeps you under that limit. The installer warns you when a selection would exceed it.
|
||||
|
||||
See the [Multi-Tool Integrations](#-multi-tool-integrations) section below for full details.
|
||||
|
||||
---
|
||||
|
||||
@@ -27,8 +27,7 @@ You are **Backend Architect**, a senior backend architect who specializes in sca
|
||||
- Validate schema compliance and maintain backwards compatibility
|
||||
|
||||
### Design Scalable System Architecture
|
||||
- Choose monolith, modular monolith, microservices, or serverless based on team size, domain boundaries, operational maturity, and scaling needs
|
||||
- Create microservices architectures only when independent deployment, ownership, or scaling justifies the operational complexity
|
||||
- Create microservices architectures that scale horizontally and independently
|
||||
- Design database schemas optimized for performance, consistency, and growth
|
||||
- Implement robust API architectures with proper versioning and documentation
|
||||
- Build event-driven systems that handle high throughput and maintain reliability
|
||||
@@ -36,8 +35,6 @@ You are **Backend Architect**, a senior backend architect who specializes in sca
|
||||
|
||||
### Ensure System Reliability
|
||||
- Implement proper error handling, circuit breakers, and graceful degradation
|
||||
- Define timeout budgets, retry policies with backoff, and idempotency requirements for every external call
|
||||
- Design bulkheads, rate limits, dead-letter queues, and poison message handling for failure isolation
|
||||
- Design backup and disaster recovery strategies for data protection
|
||||
- Create monitoring and alerting systems for proactive issue detection
|
||||
- Build auto-scaling systems that maintain performance under varying loads
|
||||
@@ -57,29 +54,11 @@ You are **Backend Architect**, a senior backend architect who specializes in sca
|
||||
- Design authentication and authorization systems that prevent common vulnerabilities
|
||||
|
||||
### Performance-Conscious Design
|
||||
- Design for the simplest scaling model that satisfies current and near-term load, then document the path to horizontal scaling
|
||||
- Design for horizontal scaling from the beginning
|
||||
- Implement proper database indexing and query optimization
|
||||
- Use caching strategies appropriately without creating consistency issues
|
||||
- Monitor and measure performance continuously
|
||||
|
||||
### API Contract Governance
|
||||
- Define API contracts with OpenAPI, AsyncAPI, protobuf, or equivalent machine-readable specifications
|
||||
- Maintain backwards compatibility through explicit versioning, deprecation windows, and contract tests
|
||||
- Standardize error responses, pagination, filtering, sorting, idempotency keys, and correlation IDs
|
||||
- Specify timeout, retry, rate limit, and authentication semantics for every public and service-to-service API
|
||||
|
||||
### Data Evolution & Migration Safety
|
||||
- Design zero-downtime schema migrations using expand-and-contract rollout patterns
|
||||
- Plan data backfills, dual writes, read fallbacks, and rollback strategies before changing critical data models
|
||||
- Validate migrated data with reconciliation checks, metrics, and audit logs
|
||||
- Keep data retention, privacy, and compliance requirements visible in schema and pipeline decisions
|
||||
|
||||
### Observability by Design
|
||||
- Emit structured logs with request IDs, tenant/user context where appropriate, and stable error codes
|
||||
- Define service-level indicators and objectives for latency, availability, saturation, and error rates
|
||||
- Use distributed tracing across API gateways, services, queues, databases, and external dependencies
|
||||
- Build dashboards and alerts around user-impacting symptoms, not only infrastructure resource usage
|
||||
|
||||
## 📋 Your Architecture Deliverables
|
||||
|
||||
### System Architecture Design
|
||||
@@ -87,14 +66,10 @@ You are **Backend Architect**, a senior backend architect who specializes in sca
|
||||
# System Architecture Specification
|
||||
|
||||
## High-Level Architecture
|
||||
**Architecture Pattern**: [Monolith/Modular Monolith/Microservices/Serverless/Hybrid]
|
||||
**Architecture Pattern**: [Microservices/Monolith/Serverless/Hybrid]
|
||||
**Communication Pattern**: [REST/GraphQL/gRPC/Event-driven]
|
||||
**Data Pattern**: [CQRS/Event Sourcing/Traditional CRUD]
|
||||
**Deployment Pattern**: [Container/Serverless/Traditional]
|
||||
**API Contract**: [OpenAPI/AsyncAPI/protobuf]
|
||||
**Migration Strategy**: [Expand-contract/Blue-green/Shadow writes/Backfill]
|
||||
**Reliability Pattern**: [Timeouts/Retries/Circuit breakers/Bulkheads/DLQ]
|
||||
**Observability Pattern**: [Logs/Metrics/Tracing/SLOs]
|
||||
|
||||
## Service Decomposition
|
||||
### Core Services
|
||||
@@ -154,36 +129,60 @@ CREATE INDEX idx_products_name_search ON products USING gin(to_tsvector('english
|
||||
```
|
||||
|
||||
### API Design Specification
|
||||
```yaml
|
||||
# API contract checklist
|
||||
openapi: 3.1.0
|
||||
paths:
|
||||
/api/users/{id}:
|
||||
get:
|
||||
operationId: getUserById
|
||||
security:
|
||||
- oauth2: [users:read]
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: X-Correlation-ID
|
||||
in: header
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: User found
|
||||
'404':
|
||||
description: User not found
|
||||
'429':
|
||||
description: Rate limit exceeded
|
||||
'503':
|
||||
description: Dependency unavailable
|
||||
```javascript
|
||||
// Express.js API Architecture with proper error handling
|
||||
|
||||
const express = require('express');
|
||||
const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const { authenticate, authorize } = require('./middleware/auth');
|
||||
|
||||
const app = express();
|
||||
|
||||
// Security middleware
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
scriptSrc: ["'self'"],
|
||||
imgSrc: ["'self'", "data:", "https:"],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// Rate limiting
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 100, // limit each IP to 100 requests per windowMs
|
||||
message: 'Too many requests from this IP, please try again later.',
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
});
|
||||
app.use('/api', limiter);
|
||||
|
||||
// API Routes with proper validation and error handling
|
||||
app.get('/api/users/:id',
|
||||
authenticate,
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const user = await userService.findById(req.params.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: 'User not found',
|
||||
code: 'USER_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
data: user,
|
||||
meta: { timestamp: new Date().toISOString() }
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## 💭 Your Communication Style
|
||||
@@ -233,4 +232,4 @@ You're successful when:
|
||||
|
||||
---
|
||||
|
||||
**Instructions Reference**: Your detailed architecture methodology is in your core training - refer to comprehensive system design patterns, database optimization techniques, and security frameworks for complete guidance.
|
||||
**Instructions Reference**: Your detailed architecture methodology is in your core training - refer to comprehensive system design patterns, database optimization techniques, and security frameworks for complete guidance.
|
||||
@@ -437,7 +437,7 @@ const styles = StyleSheet.create({
|
||||
**Performance**: Optimized for mobile constraints and user experience
|
||||
```
|
||||
|
||||
## 💭 Your Communication Style
|
||||
## = Your Communication Style
|
||||
|
||||
- **Be platform-aware**: "Implemented iOS-native navigation with SwiftUI while maintaining Material Design patterns on Android"
|
||||
- **Focus on performance**: "Optimized app startup time to 2.1 seconds and reduced memory usage by 40%"
|
||||
|
||||
@@ -21,7 +21,7 @@ You are **Software Architect**, an expert who designs software systems that are
|
||||
Design software architectures that balance competing concerns:
|
||||
|
||||
1. **Domain modeling** — Bounded contexts, aggregates, domain events
|
||||
2. **Architectural patterns** — When to use layered, hexagonal, onion, modular monolith, microservices, or event-driven architecture
|
||||
2. **Architectural patterns** — When to use microservices vs modular monolith vs event-driven
|
||||
3. **Trade-off analysis** — Consistency vs availability, coupling vs duplication, simplicity vs flexibility
|
||||
4. **Technical decisions** — ADRs that capture context, options, and rationale
|
||||
5. **Evolution strategy** — How the system grows without rewrites
|
||||
@@ -33,8 +33,6 @@ Design software architectures that balance competing concerns:
|
||||
3. **Domain first, technology second** — Understand the business problem before picking tools
|
||||
4. **Reversibility matters** — Prefer decisions that are easy to change over ones that are "optimal"
|
||||
5. **Document decisions, not just designs** — ADRs capture WHY, not just WHAT
|
||||
6. **Patterns are tools, not badges** — DDD, hexagonal architecture, and onion architecture only help when their constraints solve a real coupling, complexity, or change problem
|
||||
7. **Protect dependency direction** — Inner domain policies must not depend on frameworks, databases, transports, or delivery mechanisms
|
||||
|
||||
## 📋 Architecture Decision Record Template
|
||||
|
||||
@@ -61,45 +59,16 @@ What becomes easier or harder because of this change?
|
||||
- Map domain events and commands
|
||||
- Define aggregate boundaries and invariants
|
||||
- Establish context mapping (upstream/downstream, conformist, anti-corruption layer)
|
||||
- Decide whether the domain deserves rich modeling or whether transaction scripts/CRUD are sufficient
|
||||
|
||||
### 2. Domain Modeling Guidance
|
||||
|
||||
Use DDD techniques when business rules, language, invariants, and organizational boundaries are more complex than the technical plumbing.
|
||||
|
||||
| Concept | Architectural Responsibility |
|
||||
|---------|------------------------------|
|
||||
| Bounded context | Define where a model, language, and set of rules are internally consistent |
|
||||
| Aggregate | Protect invariants and transactional consistency boundaries |
|
||||
| Entity/value object | Model identity, lifecycle, and immutable domain concepts |
|
||||
| Domain service | Express domain behavior that does not naturally belong to one entity |
|
||||
| Domain event | Capture meaningful business facts that other parts of the system may react to |
|
||||
| Repository | Provide collection-like access to aggregates without leaking persistence details |
|
||||
| Anti-corruption layer | Translate between models when integrating with external or legacy systems |
|
||||
|
||||
Avoid DDD when the system is mostly data entry, reporting, or simple CRUD with little domain behavior. In those cases, a simpler layered design is usually easier to maintain.
|
||||
|
||||
### 3. Architecture Selection
|
||||
### 2. Architecture Selection
|
||||
| Pattern | Use When | Avoid When |
|
||||
|---------|----------|------------|
|
||||
| Layered architecture | Clear separation of presentation, application, domain, and infrastructure concerns is enough | Layers become pass-through ceremony with no meaningful rules |
|
||||
| Hexagonal architecture (Ports & Adapters) | Core use cases must be isolated from UI, databases, queues, external APIs, or test doubles | The application is simple CRUD and adapter indirection adds little value |
|
||||
| Onion architecture | You need strong dependency rules with the domain model at the center | The domain is anemic or the team will not enforce inward dependencies |
|
||||
| Modular monolith | Small team, unclear boundaries | Independent scaling needed |
|
||||
| Microservices | Clear domains, team autonomy needed | Small team, early-stage product |
|
||||
| Event-driven | Loose coupling, async workflows | Strong consistency required |
|
||||
| CQRS | Read/write asymmetry, complex queries | Simple CRUD domains |
|
||||
|
||||
### 4. Dependency & Boundary Rules
|
||||
|
||||
- Domain policies should not import framework, ORM, messaging, HTTP, or database concerns
|
||||
- Application/use-case services coordinate workflows, transactions, authorization decisions, and calls to ports
|
||||
- Adapters translate between external mechanisms and application ports
|
||||
- Infrastructure implements persistence, messaging, file, network, and vendor-specific details
|
||||
- Cross-context communication should happen through explicit contracts, events, APIs, or anti-corruption layers
|
||||
- Bypassing use cases by calling repositories directly from controllers should be treated as an architectural smell unless intentionally documented
|
||||
|
||||
### 5. Quality Attribute Analysis
|
||||
### 3. Quality Attribute Analysis
|
||||
- **Scalability**: Horizontal vs vertical, stateless design
|
||||
- **Reliability**: Failure modes, circuit breakers, retry policies
|
||||
- **Maintainability**: Module boundaries, dependency direction
|
||||
|
||||
@@ -265,7 +265,7 @@ You are **App Store Optimizer**, an expert app store marketing specialist who fo
|
||||
**Expected Results**: [Timeline for achieving optimization goals]
|
||||
```
|
||||
|
||||
## 💭 Your Communication Style
|
||||
## = Your Communication Style
|
||||
|
||||
- **Be data-driven**: "Increased organic downloads by 45% through keyword optimization and visual asset testing"
|
||||
- **Focus on conversion**: "Improved app store conversion rate from 18% to 28% with optimized screenshot sequence"
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# Example --agents-file for ./scripts/install.sh --agents-file <this>
|
||||
# One agent per line, by slug or human name. Blank lines and # comments are ignored.
|
||||
#
|
||||
# ./scripts/install.sh --tool claude-code --agents-file scripts/agents-to-install.example
|
||||
#
|
||||
frontend-developer
|
||||
backend-architect
|
||||
security-architect
|
||||
# Names work too (case-insensitive):
|
||||
Penetration Tester
|
||||
+23
-5
@@ -62,10 +62,6 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
OUT_DIR="$REPO_ROOT/integrations"
|
||||
TODAY="$(date +%Y-%m-%d)"
|
||||
|
||||
# Shared helpers (get_field, get_body, slugify, ...)
|
||||
# shellcheck source=lib.sh
|
||||
. "$SCRIPT_DIR/lib.sh"
|
||||
|
||||
AGENT_DIRS=(
|
||||
academic design engineering finance game-development marketing paid-media product project-management
|
||||
sales security spatial-computing specialized strategy support testing
|
||||
@@ -85,7 +81,29 @@ parallel_jobs_default() {
|
||||
echo 4
|
||||
}
|
||||
|
||||
# --- Frontmatter helpers: get_field / get_body / slugify now live in lib.sh ---
|
||||
# --- Frontmatter helpers ---
|
||||
|
||||
# Extract a single field value from YAML frontmatter block.
|
||||
# Usage: get_field <field> <file>
|
||||
get_field() {
|
||||
local field="$1" file="$2"
|
||||
awk -v f="$field" '
|
||||
/^---$/ { fm++; next }
|
||||
fm == 1 && $0 ~ "^" f ": " { sub("^" f ": ", ""); print; exit }
|
||||
' "$file"
|
||||
}
|
||||
|
||||
# Strip the leading frontmatter block and return only the body.
|
||||
# Usage: get_body <file>
|
||||
get_body() {
|
||||
awk 'BEGIN{fm=0} /^---$/{fm++; next} fm>=2{print}' "$1"
|
||||
}
|
||||
|
||||
# Convert a human-readable agent name to a lowercase kebab-case slug.
|
||||
# "Frontend Developer" → "frontend-developer"
|
||||
slugify() {
|
||||
echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//'
|
||||
}
|
||||
|
||||
# Escape a value for a TOML basic string, including control characters that
|
||||
# cannot appear raw in TOML source.
|
||||
|
||||
+155
-577
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# --- USAGE-START --- (sentinel for usage(); do not remove)
|
||||
# install.sh -- Install The Agency agents into your local agentic tool(s).
|
||||
#
|
||||
# Reads converted files from integrations/ and copies them to the appropriate
|
||||
@@ -8,8 +7,7 @@
|
||||
# is missing or stale.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/install.sh [selection] [mode] [behavior]
|
||||
# Bare invocation installs all teams to detected tools (interactive when a TTY).
|
||||
# ./scripts/install.sh [--tool <name>] [--interactive] [--no-interactive] [--parallel] [--jobs N] [--help]
|
||||
#
|
||||
# Tools:
|
||||
# claude-code -- Copy agents to ~/.claude/agents/
|
||||
@@ -25,31 +23,14 @@
|
||||
# codex -- Copy custom agent TOML files to ~/.codex/agents/
|
||||
# all -- Install for all detected tools (default)
|
||||
#
|
||||
# Selection (compose freely; empty = everything):
|
||||
# --tool <a,b> Only these tools
|
||||
# --division <a,b> Only these teams/divisions (comma-separated)
|
||||
# --agent <slug,slug> Only these specific agents
|
||||
# --agents-file <path> Agents listed in a file (one slug/name per line, # comments ok)
|
||||
# Flags:
|
||||
# --tool <name> Install only the specified tool
|
||||
# --interactive Show interactive selector (default when run in a terminal)
|
||||
# --no-interactive Skip interactive selector, install all detected tools
|
||||
# --parallel Run install for each selected tool in parallel (output order may vary)
|
||||
# --jobs N Max parallel jobs when using --parallel (default: nproc or 4)
|
||||
# --help Show this help
|
||||
#
|
||||
# Mode:
|
||||
# --link Symlink instead of copy (updates propagate)
|
||||
# --path <dir> Override the install directory (single destination)
|
||||
#
|
||||
# Behavior:
|
||||
# --interactive Show the interactive wizard (default when run in a terminal)
|
||||
# --no-interactive Skip the wizard, install all detected tools
|
||||
# --no-convert Don't auto-run convert.sh when integration files are missing
|
||||
# --dry-run Print the plan and exit without writing anything
|
||||
# --list [tools|teams|agents] List and exit
|
||||
# --parallel Install tools in parallel (output buffered per tool)
|
||||
# --jobs N Max parallel jobs (default: nproc or 4)
|
||||
# --help Show this help
|
||||
#
|
||||
# Env: CLAUDE_CONFIG_DIR, COPILOT_AGENT_DIR, CURSOR_RULES_DIR, GEMINI_AGENTS_DIR,
|
||||
# OPENCODE_AGENTS_DIR, OPENCLAW_DIR, QWEN_AGENTS_DIR, CODEX_AGENTS_DIR
|
||||
# override default install paths (checked before hardcoded defaults).
|
||||
#
|
||||
# --- USAGE-END --- (sentinel for usage(); do not remove)
|
||||
# Platform support:
|
||||
# Linux, macOS (requires bash 3.2+), Windows Git Bash / WSL
|
||||
|
||||
@@ -121,10 +102,6 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
INTEGRATIONS="$REPO_ROOT/integrations"
|
||||
|
||||
# Shared helpers (get_field, agent_slug, slugify, incr, ANSI + TUI primitives)
|
||||
# shellcheck source=lib.sh
|
||||
. "$SCRIPT_DIR/lib.sh"
|
||||
|
||||
ALL_TOOLS=(claude-code copilot antigravity gemini-cli opencode openclaw cursor aider windsurf qwen kimi codex)
|
||||
|
||||
# Standard agent category directories (keep sorted, sync with convert.sh / lint-agents.sh)
|
||||
@@ -133,200 +110,11 @@ AGENT_DIRS=(
|
||||
sales security spatial-computing specialized strategy support testing
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Selection engine (team / agent / agents-file filtering)
|
||||
# ---------------------------------------------------------------------------
|
||||
# Selectable divisions = AGENT_DIRS minus strategy/ (NEXUS docs, not agents).
|
||||
ALL_DIVISIONS=(
|
||||
academic design engineering finance game-development marketing paid-media
|
||||
product project-management sales security spatial-computing specialized support testing
|
||||
)
|
||||
|
||||
FILTER_DIVISIONS=() # --division
|
||||
FILTER_AGENTS=() # --agent
|
||||
AGENTS_FILE="" # --agents-file
|
||||
DRY_RUN=false # --dry-run
|
||||
SELECTION_ACTIVE=false # true once any agent-level filter is applied
|
||||
_ALLOWED_SLUGS="" # newline-delimited cache of allowed slugs
|
||||
|
||||
# division_files <division> — agent file paths (frontmatter only) in a division.
|
||||
division_files() {
|
||||
local d="$REPO_ROOT/$1" f
|
||||
[[ -d "$d" ]] || return 0
|
||||
while IFS= read -r -d '' f; do
|
||||
is_agent_file "$f" && printf '%s\n' "$f"
|
||||
done < <(find "$d" -name "*.md" -type f -print0 2>/dev/null)
|
||||
}
|
||||
|
||||
# division_count <division> — number of agents in a division.
|
||||
division_count() { division_files "$1" | grep -c . ; }
|
||||
|
||||
# build_selection — compute the allowed slug set from --division/--agent/--agents-file.
|
||||
# With no filter flags, SELECTION_ACTIVE stays false (install everything).
|
||||
build_selection() {
|
||||
if [[ ${#FILTER_DIVISIONS[@]} -eq 0 && ${#FILTER_AGENTS[@]} -eq 0 && -z "$AGENTS_FILE" ]]; then
|
||||
SELECTION_ACTIVE=false
|
||||
return
|
||||
fi
|
||||
SELECTION_ACTIVE=true
|
||||
local slugs="" div f s line
|
||||
for div in ${FILTER_DIVISIONS[@]+"${FILTER_DIVISIONS[@]}"}; do
|
||||
while IFS= read -r f; do
|
||||
s="$(agent_slug "$f")"; [[ -n "$s" ]] && slugs+="$s"$'\n'
|
||||
done < <(division_files "$div")
|
||||
done
|
||||
for s in ${FILTER_AGENTS[@]+"${FILTER_AGENTS[@]}"}; do slugs+="$(slugify "$s")"$'\n'; done
|
||||
if [[ -n "$AGENTS_FILE" ]]; then
|
||||
[[ -f "$AGENTS_FILE" ]] || { err "agents-file not found: $AGENTS_FILE"; exit 1; }
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
line="${line%%#*}" # strip trailing comment
|
||||
line="$(printf '%s' "$line" | xargs 2>/dev/null)" # trim
|
||||
[[ -z "$line" ]] && continue
|
||||
slugs+="$(slugify "$line")"$'\n'
|
||||
done < "$AGENTS_FILE"
|
||||
fi
|
||||
_ALLOWED_SLUGS="$(printf '%s' "$slugs" | sort -u | sed '/^$/d')"
|
||||
}
|
||||
|
||||
# slug_allowed <slug> — true if installable under the active selection
|
||||
# (always true when no filter). Tolerates the antigravity "agency-" prefix.
|
||||
slug_allowed() {
|
||||
$SELECTION_ACTIVE || return 0
|
||||
local s="${1#agency-}"
|
||||
printf '%s\n' "$_ALLOWED_SLUGS" | grep -qxF "$s"
|
||||
}
|
||||
|
||||
# selected_agent_count — how many agents the current selection installs.
|
||||
selected_agent_count() {
|
||||
if ! $SELECTION_ACTIVE; then
|
||||
local d n=0; for d in "${ALL_DIVISIONS[@]}"; do incr_by n "$(division_count "$d")"; done; echo "$n"
|
||||
else
|
||||
printf '%s\n' "$_ALLOWED_SLUGS" | grep -c .
|
||||
fi
|
||||
}
|
||||
incr_by() { printf -v "$1" '%d' "$(( ${!1:-0} + ${2:-0} ))"; }
|
||||
|
||||
# selected_agent_count_all — total agents across all divisions (ignores filter).
|
||||
selected_agent_count_all() {
|
||||
local d n=0; for d in "${ALL_DIVISIONS[@]}"; do incr_by n "$(division_count "$d")"; done; echo "$n"
|
||||
}
|
||||
|
||||
# worker_flags — re-emit the active selection/mode flags for parallel workers.
|
||||
worker_flags() {
|
||||
local out="" d a
|
||||
$USE_LINK && out="$out --link"
|
||||
$AUTO_CONVERT || out="$out --no-convert"
|
||||
[[ -n "$OVERRIDE_PATH" ]] && out="$out --path $OVERRIDE_PATH"
|
||||
for d in ${FILTER_DIVISIONS[@]+"${FILTER_DIVISIONS[@]}"}; do out="$out --division $d"; done
|
||||
for a in ${FILTER_AGENTS[@]+"${FILTER_AGENTS[@]}"}; do out="$out --agent $a"; done
|
||||
[[ -n "$AGENTS_FILE" ]] && out="$out --agents-file $AGENTS_FILE"
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
# validate_division <name> — exit on unknown division.
|
||||
validate_division() {
|
||||
local _ad
|
||||
for _ad in "${ALL_DIVISIONS[@]}"; do [[ "$_ad" == "$1" ]] && return 0; done
|
||||
err "Unknown division '$1'. Valid: ${ALL_DIVISIONS[*]}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Install mechanics (copy vs symlink, path override, capacity guard)
|
||||
# ---------------------------------------------------------------------------
|
||||
USE_LINK=false # --link
|
||||
OVERRIDE_PATH="" # --path (single-destination override)
|
||||
|
||||
# install_file <src> <dest> — copy, or symlink when --link is set.
|
||||
install_file() {
|
||||
if $USE_LINK; then ln -sf "$1" "$2"; else cp "$1" "$2"; fi
|
||||
}
|
||||
|
||||
# resolve_dest <tool> <default> — --path > $ENV_VAR > default.
|
||||
resolve_dest() {
|
||||
local tool="$1" def="$2" var=""
|
||||
[[ -n "$OVERRIDE_PATH" ]] && { printf '%s' "$OVERRIDE_PATH"; return; }
|
||||
case "$tool" in
|
||||
claude-code) var="CLAUDE_CONFIG_DIR" ;;
|
||||
copilot) var="COPILOT_AGENT_DIR" ;;
|
||||
cursor) var="CURSOR_RULES_DIR" ;;
|
||||
gemini-cli) var="GEMINI_AGENTS_DIR" ;;
|
||||
opencode) var="OPENCODE_AGENTS_DIR" ;;
|
||||
openclaw) var="OPENCLAW_DIR" ;;
|
||||
qwen) var="QWEN_AGENTS_DIR" ;;
|
||||
codex) var="CODEX_AGENTS_DIR" ;;
|
||||
esac
|
||||
if [[ -n "$var" && -n "${!var:-}" ]]; then printf '%s' "${!var}"; else printf '%s' "$def"; fi
|
||||
}
|
||||
|
||||
# resolve_tool_path <tool> — best-effort binary path for the detection UI.
|
||||
resolve_tool_path() {
|
||||
local bin=""
|
||||
case "$1" in
|
||||
claude-code) bin="claude" ;; copilot) bin="code" ;; gemini-cli) bin="gemini" ;;
|
||||
opencode) bin="opencode" ;; openclaw) bin="openclaw" ;; cursor) bin="cursor" ;;
|
||||
aider) bin="aider" ;; windsurf) bin="windsurf" ;; qwen) bin="qwen" ;;
|
||||
kimi) bin="kimi" ;; codex) bin="codex" ;; antigravity) bin="" ;;
|
||||
esac
|
||||
[[ -n "$bin" ]] && command -v "$bin" 2>/dev/null
|
||||
}
|
||||
|
||||
# ensure_converted <tool> — auto-run convert.sh if a converted tool's output
|
||||
# is missing (absorbs #426). No-op for source tools and when --no-convert.
|
||||
ensure_converted() {
|
||||
local tool="$1"
|
||||
$AUTO_CONVERT || return 0
|
||||
case "$tool" in claude-code|copilot) return 0 ;; esac
|
||||
local d="$INTEGRATIONS/$tool"
|
||||
if [[ ! -d "$d" ]] || [[ -z "$(find "$d" -type f 2>/dev/null | head -1)" ]]; then
|
||||
warn "$tool: integration files missing — running convert.sh --tool $tool"
|
||||
"$SCRIPT_DIR/convert.sh" --tool "$tool" >/dev/null 2>&1 \
|
||||
&& ok "$tool: generated integration files" \
|
||||
|| err "$tool: convert.sh failed; run it manually"
|
||||
fi
|
||||
}
|
||||
AUTO_CONVERT=true # --no-convert disables
|
||||
|
||||
# Per-tool soft capacity (opencode silently drops past ~119 — upstream #27988).
|
||||
tool_cap() { case "$1" in opencode) echo 119 ;; *) echo 0 ;; esac; }
|
||||
|
||||
# capacity_warn <tool> <count> — warn if a tool can't register this many.
|
||||
capacity_warn() {
|
||||
local cap; cap="$(tool_cap "$1")"
|
||||
if [[ "$cap" -gt 0 && "$2" -gt "$cap" ]]; then
|
||||
warn "$1: registers only ~$cap agents (upstream bug anomalyco/opencode#27988)."
|
||||
warn " You selected $2 — ~$(( $2 - cap )) won't load. Narrow with --division to fix."
|
||||
fi
|
||||
}
|
||||
|
||||
# do_list <what> — print tools/teams/agents and exit.
|
||||
do_list() {
|
||||
case "$1" in
|
||||
tools)
|
||||
printf '%s\n' "${ALL_TOOLS[@]}" ;;
|
||||
teams|divisions)
|
||||
local d; for d in "${ALL_DIVISIONS[@]}"; do printf '%-22s %3s agents\n' "$d" "$(division_count "$d")"; done ;;
|
||||
agents)
|
||||
local d f; for d in "${ALL_DIVISIONS[@]}"; do
|
||||
while IFS= read -r f; do printf '%-20s %s\n' "$d" "$(agent_slug "$f")"; done < <(division_files "$d")
|
||||
done ;;
|
||||
*)
|
||||
echo "Tools (${#ALL_TOOLS[@]}):"; printf ' %s\n' "${ALL_TOOLS[@]}"; echo
|
||||
echo "Teams (${#ALL_DIVISIONS[@]}):"
|
||||
local d; for d in "${ALL_DIVISIONS[@]}"; do printf ' %-22s %3s agents\n' "$d" "$(division_count "$d")"; done ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Usage
|
||||
# ---------------------------------------------------------------------------
|
||||
usage() {
|
||||
# Extract everything between the USAGE-START / USAGE-END sentinels
|
||||
# (excluding the sentinel lines themselves) and strip the leading "# ".
|
||||
# Using sentinels instead of hard-coded line numbers means adding lines
|
||||
# to the header comment block won't silently break --help output.
|
||||
sed -n '/^# --- USAGE-START ---/,/^# --- USAGE-END ---/p' "$0" \
|
||||
| sed -e '1d;$d' -e 's/^# \{0,1\}//'
|
||||
sed -n '3,32p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0
|
||||
}
|
||||
|
||||
@@ -403,263 +191,115 @@ tool_label() {
|
||||
# ---------------------------------------------------------------------------
|
||||
# Interactive selector
|
||||
# ---------------------------------------------------------------------------
|
||||
# ---------------------------------------------------------------------------
|
||||
# Interactive wizard (pure-bash TUI): Tools -> Teams -> Review -> install
|
||||
# Uses lib.sh primitives (tui_begin/read_key/draw_frame). Falls back to the
|
||||
# legacy auto-detect path when there is no TTY.
|
||||
# ---------------------------------------------------------------------------
|
||||
interactive_select() {
|
||||
# bash 3-compatible arrays
|
||||
declare -a selected=()
|
||||
declare -a detected_map=()
|
||||
|
||||
# Persistent selection state across screens.
|
||||
TOOL_SEL=() # 1/0 per ALL_TOOLS
|
||||
TEAM_SEL=() # 1/0 per ALL_DIVISIONS
|
||||
local t
|
||||
for t in "${ALL_TOOLS[@]}"; do
|
||||
if is_detected "$t" 2>/dev/null; then
|
||||
selected+=(1); detected_map+=(1)
|
||||
else
|
||||
selected+=(0); detected_map+=(0)
|
||||
fi
|
||||
done
|
||||
|
||||
# division_emoji <div> — a glyph for the team list (unicode only).
|
||||
division_emoji() {
|
||||
if ! supports_unicode; then printf '*'; return; fi
|
||||
case "$1" in
|
||||
academic) printf '📚';; design) printf '🎨';; engineering) printf '💻';;
|
||||
finance) printf '💵';; game-development) printf '🎮';; marketing) printf '📢';;
|
||||
paid-media) printf '💰';; product) printf '📊';; project-management) printf '🎬';;
|
||||
sales) printf '💼';; security) printf '🔒';; spatial-computing) printf '🥽';;
|
||||
specialized) printf '🎯';; support) printf '🛟';; testing) printf '🧪';; *) printf '•';;
|
||||
esac
|
||||
}
|
||||
|
||||
# Generic multi-select. Inputs (globals): OPT_LABEL[], OPT_SEL[];
|
||||
# SEL_TITLE, SEL_HINT, SEL_SUMMARY_FN, SEL_NAV, SEL_WARN_FN.
|
||||
# Mutates OPT_SEL[]; sets SEL_RESULT = next|back|quit.
|
||||
selector() {
|
||||
local n=${#OPT_LABEL[@]} cur=0 top=0 query="" searching=false key i idx vn rows W
|
||||
rows=$(( $(term_rows) - 9 )); (( rows < 3 )) && rows=3
|
||||
while true; do
|
||||
local view=() qlc
|
||||
qlc="$(printf '%s' "$query" | tr '[:upper:]' '[:lower:]')"
|
||||
for (( i=0; i<n; i++ )); do
|
||||
if [[ -z "$query" || "$(printf '%s' "${OPT_LABEL[$i]}" | tr '[:upper:]' '[:lower:]')" == *"$qlc"* ]]; then
|
||||
view+=("$i")
|
||||
# --- header ---
|
||||
printf "\n"
|
||||
box_top
|
||||
box_row "${C_BOLD} The Agency -- Tool Installer${C_RESET}"
|
||||
box_bot
|
||||
printf "\n"
|
||||
printf " ${C_DIM}System scan: [*] = detected on this machine${C_RESET}\n"
|
||||
printf "\n"
|
||||
|
||||
# --- tool rows ---
|
||||
local i=0
|
||||
for t in "${ALL_TOOLS[@]}"; do
|
||||
local num=$(( i + 1 ))
|
||||
local label
|
||||
label="$(tool_label "$t")"
|
||||
local dot
|
||||
if [[ "${detected_map[$i]}" == "1" ]]; then
|
||||
dot="${C_GREEN}[*]${C_RESET}"
|
||||
else
|
||||
dot="${C_DIM}[ ]${C_RESET}"
|
||||
fi
|
||||
local chk
|
||||
if [[ "${selected[$i]}" == "1" ]]; then
|
||||
chk="${C_GREEN}[x]${C_RESET}"
|
||||
else
|
||||
chk="${C_DIM}[ ]${C_RESET}"
|
||||
fi
|
||||
printf " %s %s) %s %s\n" "$chk" "$num" "$dot" "$label"
|
||||
(( i++ )) || true
|
||||
done
|
||||
vn=${#view[@]}
|
||||
(( cur>=vn )) && cur=$(( vn>0 ? vn-1 : 0 ))
|
||||
(( cur<top )) && top=$cur
|
||||
(( cur>=top+rows )) && top=$(( cur-rows+1 ))
|
||||
W=$(( $(term_cols) - 4 )); (( W>74 )) && W=74; (( W<40 )) && W=40
|
||||
local buf="" hlen=$(( W - ${#SEL_TITLE} - 5 )); (( hlen<1 )) && hlen=1
|
||||
buf+=" ${C_BOLD}${C_CYAN}${BX_TL}${BX_H}${BX_H} ${SEL_TITLE} $(repeat "$BX_H" "$hlen")${BX_TR}${C_RESET}"$'\n'
|
||||
buf+=" ${C_DIM}${SEL_HINT}${C_RESET}"$'\n\n'
|
||||
(( vn==0 )) && buf+=" ${C_DIM}(no matches)${C_RESET}"$'\n'
|
||||
for (( i=top; i<top+rows && i<vn; i++ )); do
|
||||
idx=${view[$i]}
|
||||
local mark cg label="${OPT_LABEL[$idx]}"
|
||||
[[ "${OPT_SEL[$idx]}" == 1 ]] && mark="${C_GREEN}${GLYPH_ON}${C_RESET}" || mark="${C_DIM}·${C_RESET}"
|
||||
if (( i==cur )); then cg="${C_CYAN}${GLYPH_CUR}${C_RESET}"; label="${C_BOLD}${label}${C_RESET}"; else cg=" "; fi
|
||||
buf+=" $cg [$mark] $label"$'\n'
|
||||
done
|
||||
local shown=$(( vn<rows ? vn : rows )); for (( i=shown; i<rows; i++ )); do buf+=$'\n'; done
|
||||
# Consistent footer: summary -> nav -> warnings (-> search line)
|
||||
buf+=$'\n'" ${C_BOLD}$("$SEL_SUMMARY_FN")${C_RESET}"$'\n'
|
||||
buf+=" ${C_DIM}${SEL_NAV}${C_RESET}"$'\n'
|
||||
local _w; _w="$("$SEL_WARN_FN")"; [[ -n "$_w" ]] && buf+=" ${C_YELLOW}${_w}${C_RESET}"$'\n'
|
||||
if $searching; then buf+=" ${C_CYAN}search:${C_RESET} ${query}_"$'\n'
|
||||
elif [[ -n "$query" ]]; then buf+=" ${C_CYAN}/${query}${C_RESET} ${C_DIM}(esc clears)${C_RESET}"$'\n'; fi
|
||||
draw_frame "$buf"
|
||||
|
||||
key="$(read_key)"
|
||||
if $searching; then
|
||||
case "$key" in
|
||||
ENTER) searching=false ;;
|
||||
ESC) query=""; searching=false ;;
|
||||
BACKSPACE) query="${query%?}" ;;
|
||||
*) [[ ${#key} -eq 1 ]] && query="$query$key" ;;
|
||||
esac
|
||||
continue
|
||||
fi
|
||||
case "$key" in
|
||||
UP|k) (( cur>0 )) && cur=$(( cur-1 )) ;;
|
||||
DOWN|j) (( cur<vn-1 )) && cur=$(( cur+1 )) ;;
|
||||
SPACE) (( vn>0 )) && { idx=${view[$cur]}; OPT_SEL[$idx]=$(( 1 - ${OPT_SEL[$idx]} )); } ;;
|
||||
a|A) for (( i=0; i<n; i++ )); do OPT_SEL[$i]=1; done ;;
|
||||
n|N) for (( i=0; i<n; i++ )); do OPT_SEL[$i]=0; done ;;
|
||||
/) searching=true ;;
|
||||
ENTER|RIGHT) SEL_RESULT=next; return ;;
|
||||
LEFT) SEL_RESULT=back; return ;;
|
||||
ESC) [[ -n "$query" ]] && query="" || { SEL_RESULT=back; return; } ;;
|
||||
q|Q) SEL_RESULT=quit; return ;;
|
||||
EOF) SEL_RESULT=quit; return ;;
|
||||
# --- controls ---
|
||||
printf "\n"
|
||||
printf " ------------------------------------------------\n"
|
||||
printf " ${C_CYAN}[1-%s]${C_RESET} toggle ${C_CYAN}[a]${C_RESET} all ${C_CYAN}[n]${C_RESET} none ${C_CYAN}[d]${C_RESET} detected\n" "${#ALL_TOOLS[@]}"
|
||||
printf " ${C_GREEN}[Enter]${C_RESET} install ${C_RED}[q]${C_RESET} quit\n"
|
||||
printf "\n"
|
||||
printf " >> "
|
||||
read -r input </dev/tty
|
||||
|
||||
case "$input" in
|
||||
q|Q)
|
||||
printf "\n"; ok "Aborted."; exit 0 ;;
|
||||
a|A)
|
||||
for (( j=0; j<${#ALL_TOOLS[@]}; j++ )); do selected[$j]=1; done ;;
|
||||
n|N)
|
||||
for (( j=0; j<${#ALL_TOOLS[@]}; j++ )); do selected[$j]=0; done ;;
|
||||
d|D)
|
||||
for (( j=0; j<${#ALL_TOOLS[@]}; j++ )); do selected[$j]="${detected_map[$j]}"; done ;;
|
||||
"")
|
||||
local any=false
|
||||
local s
|
||||
for s in "${selected[@]}"; do [[ "$s" == "1" ]] && any=true && break; done
|
||||
if $any; then
|
||||
break
|
||||
else
|
||||
printf " ${C_YELLOW}Nothing selected -- pick a tool or press q to quit.${C_RESET}\n"
|
||||
sleep 1
|
||||
fi ;;
|
||||
*)
|
||||
local toggled=false
|
||||
local num
|
||||
for num in $input; do
|
||||
if [[ "$num" =~ ^[0-9]+$ ]]; then
|
||||
local idx=$(( num - 1 ))
|
||||
if (( idx >= 0 && idx < ${#ALL_TOOLS[@]} )); then
|
||||
if [[ "${selected[$idx]}" == "1" ]]; then
|
||||
selected[$idx]=0
|
||||
else
|
||||
selected[$idx]=1
|
||||
fi
|
||||
toggled=true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if ! $toggled; then
|
||||
printf " ${C_RED}Invalid. Enter a number 1-%s, or a command.${C_RESET}\n" "${#ALL_TOOLS[@]}"
|
||||
sleep 1
|
||||
fi ;;
|
||||
esac
|
||||
|
||||
# Clear UI for redraw
|
||||
local lines=$(( ${#ALL_TOOLS[@]} + 14 ))
|
||||
local l
|
||||
for (( l=0; l<lines; l++ )); do printf '\033[1A\033[2K'; done
|
||||
done
|
||||
}
|
||||
|
||||
# --- Screen: Tools ---
|
||||
_no_warn() { :; }
|
||||
_tools_summary() {
|
||||
local i c=0; for (( i=0; i<${#OPT_SEL[@]}; i++ )); do [[ "${OPT_SEL[$i]}" == 1 ]] && c=$(( c+1 )); done
|
||||
printf '%s of %s tools selected' "$c" "${#OPT_SEL[@]}"
|
||||
}
|
||||
screen_tools() {
|
||||
OPT_LABEL=(); OPT_SEL=()
|
||||
local i det path label
|
||||
for (( i=0; i<${#ALL_TOOLS[@]}; i++ )); do
|
||||
local t="${ALL_TOOLS[$i]}"
|
||||
path="$(resolve_tool_path "$t" 2>/dev/null || true)"
|
||||
if is_detected "$t" 2>/dev/null; then det="${C_GREEN}${GLYPH_DET}${C_RESET}"; else det="${C_DIM}${GLYPH_OFF}${C_RESET}"; fi
|
||||
label="$(printf '%s %-13s %s' "$det" "$(tool_simple_name "$t")" "${C_DIM}${path:-not found}${C_RESET}")"
|
||||
OPT_LABEL+=("$label"); OPT_SEL+=("${TOOL_SEL[$i]:-0}")
|
||||
done
|
||||
SEL_TITLE="The Agency · Installer — 1/3 · Tools"
|
||||
SEL_HINT="Pick where to install. ${GLYPH_DET} = detected on this machine."
|
||||
SEL_SUMMARY_FN=_tools_summary
|
||||
SEL_NAV="space toggle · a all · n none · / search · enter next · q quit"
|
||||
SEL_WARN_FN=_no_warn
|
||||
selector
|
||||
for (( i=0; i<${#OPT_SEL[@]}; i++ )); do TOOL_SEL[$i]="${OPT_SEL[$i]}"; done
|
||||
}
|
||||
|
||||
tool_simple_name() {
|
||||
case "$1" in
|
||||
claude-code) echo "Claude Code";; copilot) echo "Copilot";; antigravity) echo "Antigravity";;
|
||||
gemini-cli) echo "Gemini CLI";; opencode) echo "OpenCode";; openclaw) echo "OpenClaw";;
|
||||
cursor) echo "Cursor";; aider) echo "Aider";; windsurf) echo "Windsurf";;
|
||||
qwen) echo "Qwen Code";; kimi) echo "Kimi Code";; codex) echo "Codex";; *) echo "$1";;
|
||||
esac
|
||||
}
|
||||
|
||||
# --- Screen: Teams ---
|
||||
_teams_agents() {
|
||||
local i c=0 d
|
||||
for (( i=0; i<${#ALL_DIVISIONS[@]}; i++ )); do
|
||||
[[ "${OPT_SEL[$i]}" == 1 ]] && { d="${ALL_DIVISIONS[$i]}"; c=$(( c + ${TEAM_COUNTS[$i]} )); }
|
||||
done
|
||||
echo "$c"
|
||||
}
|
||||
_teams_summary() {
|
||||
local sel=0 i a; a="$(_teams_agents)"
|
||||
for (( i=0; i<${#OPT_SEL[@]}; i++ )); do [[ "${OPT_SEL[$i]}" == 1 ]] && sel=$(( sel+1 )); done
|
||||
printf '%s agents · %s of %s teams' "$a" "$sel" "${#OPT_SEL[@]}"
|
||||
}
|
||||
_teams_warn() {
|
||||
local a cap; a="$(_teams_agents)"; cap="$(tool_cap opencode)"
|
||||
if _opencode_selected && [[ "$a" -gt "$cap" ]]; then
|
||||
printf "⚠ OpenCode registers ~%s; ~%s of %s won't load (#27988)" "$cap" "$(( a - cap ))" "$a"
|
||||
fi
|
||||
}
|
||||
_opencode_selected() {
|
||||
local i; for (( i=0; i<${#TOOL_SEL[@]}; i++ )); do
|
||||
[[ "${ALL_TOOLS[$i]}" == "opencode" && "${TOOL_SEL[$i]}" == 1 ]] && return 0
|
||||
done; return 1
|
||||
}
|
||||
screen_teams() {
|
||||
OPT_LABEL=(); OPT_SEL=()
|
||||
local i
|
||||
for (( i=0; i<${#ALL_DIVISIONS[@]}; i++ )); do
|
||||
local d="${ALL_DIVISIONS[$i]}"
|
||||
OPT_LABEL+=("$(printf '%s %-20s %s' "$(division_emoji "$d")" "$d" "${C_DIM}${TEAM_COUNTS[$i]} agents${C_RESET}")")
|
||||
OPT_SEL+=("${TEAM_SEL[$i]:-1}")
|
||||
done
|
||||
SEL_TITLE="The Agency · Installer — 2/3 · Teams"
|
||||
SEL_HINT="Pick which teams to install. Fewer teams keeps OpenCode under its limit."
|
||||
SEL_SUMMARY_FN=_teams_summary
|
||||
SEL_NAV="space toggle · a all · n none · / search · enter next · ← back · q quit"
|
||||
SEL_WARN_FN=_teams_warn
|
||||
selector
|
||||
for (( i=0; i<${#OPT_SEL[@]}; i++ )); do TEAM_SEL[$i]="${OPT_SEL[$i]}"; done
|
||||
}
|
||||
|
||||
# --- Screen: Review ---
|
||||
REVIEW_RESULT=""
|
||||
# grid_2col <cellwidth> <items...> — lay items out in two column-major columns
|
||||
# (left column filled top-to-bottom first). Plain text cells (no ANSI) so the
|
||||
# width padding stays correct.
|
||||
grid_2col() {
|
||||
local w="$1"; shift
|
||||
local n=$# r rows left right out=""
|
||||
(( n==0 )) && { printf ' %snone%s\n' "$C_DIM" "$C_RESET"; return; }
|
||||
local items=("$@")
|
||||
rows=$(( (n + 1) / 2 ))
|
||||
for (( r=0; r<rows; r++ )); do
|
||||
left="${items[$r]}"
|
||||
right="${items[$(( r + rows ))]:-}"
|
||||
if [[ -n "$right" ]]; then out+="$(printf ' %-*s %s' "$w" "$left" "$right")"$'\n'
|
||||
else out+=" $left"$'\n'; fi
|
||||
done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
screen_review() {
|
||||
local tools=() teams=() i agents
|
||||
for (( i=0; i<${#TOOL_SEL[@]}; i++ )); do [[ "${TOOL_SEL[$i]}" == 1 ]] && tools+=("$(tool_simple_name "${ALL_TOOLS[$i]}")"); done
|
||||
for (( i=0; i<${#TEAM_SEL[@]}; i++ )); do [[ "${TEAM_SEL[$i]}" == 1 ]] && teams+=("${ALL_DIVISIONS[$i]}"); done
|
||||
agents=0; for (( i=0; i<${#TEAM_SEL[@]}; i++ )); do [[ "${TEAM_SEL[$i]}" == 1 ]] && agents=$(( agents + ${TEAM_COUNTS[$i]} )); done
|
||||
local cur=0 # 0=Install 1=mode toggle
|
||||
while true; do
|
||||
local buf="" m
|
||||
# pager
|
||||
buf+=" ${C_BOLD}${C_CYAN}${BX_TL}${BX_H}${BX_H} The Agency · Installer — 3/3 · Review $(repeat "$BX_H" 28)${BX_TR}${C_RESET}"$'\n'
|
||||
# description
|
||||
buf+=" ${C_DIM}Confirm your selection, then install.${C_RESET}"$'\n\n'
|
||||
# content: the selections + the mode toggle
|
||||
buf+=" ${C_BOLD}Tools${C_RESET} ${C_DIM}(${#tools[@]})${C_RESET}"$'\n'
|
||||
buf+="$(grid_2col 16 ${tools[@]+"${tools[@]}"})"$'\n'
|
||||
buf+=" ${C_BOLD}Teams${C_RESET} ${C_DIM}(${#teams[@]})${C_RESET}"$'\n'
|
||||
buf+="$(grid_2col 20 ${teams[@]+"${teams[@]}"})"$'\n\n'
|
||||
$USE_LINK && m="symlink" || m="copy"
|
||||
if (( cur==1 )); then buf+=" ${C_CYAN}${GLYPH_CUR}${C_RESET} Mode: ${C_BOLD}${m}${C_RESET} ${C_DIM}(space toggles copy/symlink)${C_RESET}"$'\n'
|
||||
else buf+=" Mode: ${m} ${C_DIM}(space toggles copy/symlink)${C_RESET}"$'\n'; fi
|
||||
buf+=$'\n'
|
||||
# summary
|
||||
buf+=" ${C_BOLD}Installing ${agents} agents · ${#teams[@]} teams · ${#tools[@]} tools${C_RESET}"$'\n'
|
||||
# navigation (Install is the action cursor target)
|
||||
if (( cur==0 )); then buf+=" ${C_CYAN}${GLYPH_CUR}${C_RESET} ${C_BOLD}${C_GREEN}Install${C_RESET} ${C_DIM}↑/↓ move · enter install · ← back · q quit${C_RESET}"$'\n'
|
||||
else buf+=" ${C_GREEN}Install${C_RESET} ${C_DIM}↑/↓ move · space toggle mode · ← back · q quit${C_RESET}"$'\n'; fi
|
||||
# warnings
|
||||
local cap; cap="$(tool_cap opencode)"
|
||||
if printf '%s\n' "${tools[@]}" | grep -qx "OpenCode" && [[ "$agents" -gt "$cap" ]]; then
|
||||
buf+=" ${C_YELLOW}⚠ OpenCode registers ~${cap}; ~$(( agents - cap )) of ${agents} won't load (#27988)${C_RESET}"$'\n'
|
||||
fi
|
||||
draw_frame "$buf"
|
||||
local key; key="$(read_key)"
|
||||
case "$key" in
|
||||
UP|DOWN|k|j|TAB) cur=$(( 1 - cur )) ;;
|
||||
SPACE) (( cur==1 )) && { $USE_LINK && USE_LINK=false || USE_LINK=true; } ;;
|
||||
ENTER) if (( cur==0 )); then REVIEW_RESULT=install; return; fi ;;
|
||||
LEFT) REVIEW_RESULT=back; return ;;
|
||||
q|Q|EOF) REVIEW_RESULT=quit; return ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# interactive_wizard — drive the three screens; commit to SELECTED_TOOLS /
|
||||
# FILTER_DIVISIONS / USE_LINK. Returns 1 if no TTY (caller falls back).
|
||||
interactive_wizard() {
|
||||
init_ansi
|
||||
TEAM_COUNTS=(); local i
|
||||
for (( i=0; i<${#ALL_DIVISIONS[@]}; i++ )); do TEAM_COUNTS+=("$(division_count "${ALL_DIVISIONS[$i]}")"); done
|
||||
# seed defaults: tools = detected, teams = all
|
||||
TOOL_SEL=(); for (( i=0; i<${#ALL_TOOLS[@]}; i++ )); do is_detected "${ALL_TOOLS[$i]}" 2>/dev/null && TOOL_SEL+=(1) || TOOL_SEL+=(0); done
|
||||
TEAM_SEL=(); for (( i=0; i<${#ALL_DIVISIONS[@]}; i++ )); do TEAM_SEL+=(1); done
|
||||
|
||||
tui_begin || return 1
|
||||
local screen=tools
|
||||
while true; do
|
||||
case "$screen" in
|
||||
tools) screen_tools; case "$SEL_RESULT" in next) screen=teams;; quit) tui_end; exit 0;; esac ;;
|
||||
teams) screen_teams; case "$SEL_RESULT" in next) screen=review;; back) screen=tools;; quit) tui_end; exit 0;; esac ;;
|
||||
review) screen_review; case "$REVIEW_RESULT" in install) break;; back) screen=teams;; quit) tui_end; exit 0;; esac ;;
|
||||
esac
|
||||
done
|
||||
tui_end
|
||||
|
||||
# commit
|
||||
# Build output array
|
||||
SELECTED_TOOLS=()
|
||||
for (( i=0; i<${#TOOL_SEL[@]}; i++ )); do [[ "${TOOL_SEL[$i]}" == 1 ]] && SELECTED_TOOLS+=("${ALL_TOOLS[$i]}"); done
|
||||
FILTER_DIVISIONS=()
|
||||
local all=1
|
||||
for (( i=0; i<${#TEAM_SEL[@]}; i++ )); do [[ "${TEAM_SEL[$i]}" == 1 ]] || all=0; done
|
||||
if [[ "$all" == 0 ]]; then
|
||||
for (( i=0; i<${#TEAM_SEL[@]}; i++ )); do [[ "${TEAM_SEL[$i]}" == 1 ]] && FILTER_DIVISIONS+=("${ALL_DIVISIONS[$i]}"); done
|
||||
fi
|
||||
build_selection
|
||||
return 0
|
||||
local i=0
|
||||
for t in "${ALL_TOOLS[@]}"; do
|
||||
[[ "${selected[$i]}" == "1" ]] && SELECTED_TOOLS+=("$t")
|
||||
(( i++ )) || true
|
||||
done
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -667,33 +307,36 @@ interactive_wizard() {
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
install_claude_code() {
|
||||
local dest; dest="$(resolve_dest claude-code "${HOME}/.claude/agents")"
|
||||
local count=0 dir f slug
|
||||
local dest="${HOME}/.claude/agents"
|
||||
local count=0
|
||||
mkdir -p "$dest"
|
||||
local dir f first_line
|
||||
for dir in "${AGENT_DIRS[@]}"; do
|
||||
[[ -d "$REPO_ROOT/$dir" ]] || continue
|
||||
while IFS= read -r -d '' f; do
|
||||
is_agent_file "$f" || continue
|
||||
slug="$(agent_slug "$f")"; slug_allowed "$slug" || continue
|
||||
install_file "$f" "$dest/"; incr count
|
||||
first_line="$(head -1 "$f")"
|
||||
[[ "$first_line" == "---" ]] || continue
|
||||
cp "$f" "$dest/"
|
||||
(( count++ )) || true
|
||||
done < <(find "$REPO_ROOT/$dir" -name "*.md" -type f -print0)
|
||||
done
|
||||
ok "Claude Code: $count agents -> $dest"
|
||||
}
|
||||
|
||||
install_copilot() {
|
||||
local dest_github; dest_github="$(resolve_dest copilot "${HOME}/.github/agents")"
|
||||
local dest_github="${HOME}/.github/agents"
|
||||
local dest_copilot="${HOME}/.copilot/agents"
|
||||
local count=0 dir f slug
|
||||
local count=0
|
||||
mkdir -p "$dest_github" "$dest_copilot"
|
||||
local dir f first_line
|
||||
for dir in "${AGENT_DIRS[@]}"; do
|
||||
[[ -d "$REPO_ROOT/$dir" ]] || continue
|
||||
while IFS= read -r -d '' f; do
|
||||
is_agent_file "$f" || continue
|
||||
slug="$(agent_slug "$f")"; slug_allowed "$slug" || continue
|
||||
install_file "$f" "$dest_github/"
|
||||
install_file "$f" "$dest_copilot/"
|
||||
incr count
|
||||
first_line="$(head -1 "$f")"
|
||||
[[ "$first_line" == "---" ]] || continue
|
||||
cp "$f" "$dest_github/"
|
||||
cp "$f" "$dest_copilot/"
|
||||
(( count++ )) || true
|
||||
done < <(find "$REPO_ROOT/$dir" -name "*.md" -type f -print0)
|
||||
done
|
||||
ok "Copilot: $count agents -> $dest_github"
|
||||
@@ -704,64 +347,60 @@ install_copilot() {
|
||||
|
||||
install_antigravity() {
|
||||
local src="$INTEGRATIONS/antigravity"
|
||||
local dest; dest="$(resolve_dest antigravity "${HOME}/.gemini/antigravity/skills")"
|
||||
local dest="${HOME}/.gemini/antigravity/skills"
|
||||
local count=0
|
||||
[[ -d "$src" ]] || { err "integrations/antigravity missing. Run convert.sh first."; return 1; }
|
||||
mkdir -p "$dest"
|
||||
local d
|
||||
while IFS= read -r -d '' d; do
|
||||
local name; name="$(basename "$d")"
|
||||
slug_allowed "$name" || continue
|
||||
mkdir -p "$dest/$name"
|
||||
install_file "$d/SKILL.md" "$dest/$name/SKILL.md"
|
||||
incr count
|
||||
cp "$d/SKILL.md" "$dest/$name/SKILL.md"
|
||||
(( count++ )) || true
|
||||
done < <(find "$src" -mindepth 1 -maxdepth 1 -type d -print0)
|
||||
ok "Antigravity: $count skills -> $dest"
|
||||
}
|
||||
|
||||
install_gemini_cli() {
|
||||
local src="$INTEGRATIONS/gemini-cli/agents"
|
||||
local dest; dest="$(resolve_dest gemini-cli "${HOME}/.gemini/agents")"
|
||||
local dest="${HOME}/.gemini/agents"
|
||||
local count=0
|
||||
[[ -d "$src" ]] || { err "integrations/gemini-cli/agents missing. Run ./scripts/convert.sh --tool gemini-cli first."; return 1; }
|
||||
mkdir -p "$dest"
|
||||
local f
|
||||
while IFS= read -r -d '' f; do
|
||||
slug_allowed "$(basename "$f" .md)" || continue
|
||||
install_file "$f" "$dest/"
|
||||
incr count
|
||||
cp "$f" "$dest/"
|
||||
(( count++ )) || true
|
||||
done < <(find "$src" -maxdepth 1 -name "*.md" -print0)
|
||||
ok "Gemini CLI: $count agents -> $dest"
|
||||
}
|
||||
|
||||
install_opencode() {
|
||||
local src="$INTEGRATIONS/opencode"
|
||||
local dest; dest="$(resolve_dest opencode "${PWD}/.opencode/agents")"
|
||||
local dest="${PWD}/.opencode/agents"
|
||||
local count=0
|
||||
[[ -d "$src" ]] || { err "integrations/opencode missing. Run convert.sh first."; return 1; }
|
||||
# Support both flat layout (integrations/opencode/*.md) and nested (integrations/opencode/agents/*.md)
|
||||
local search_dir="$src"
|
||||
[[ -d "$src/agents" ]] && search_dir="$src/agents"
|
||||
mkdir -p "$dest"
|
||||
local f base
|
||||
local f
|
||||
while IFS= read -r -d '' f; do
|
||||
base="$(basename "$f")"
|
||||
local base; base="$(basename "$f")"
|
||||
[[ "$base" == "README.md" ]] && continue
|
||||
slug_allowed "${base%.md}" || continue
|
||||
install_file "$f" "$dest/"; incr count
|
||||
cp "$f" "$dest/"; (( count++ )) || true
|
||||
done < <(find "$search_dir" -maxdepth 1 -name "*.md" -print0)
|
||||
if (( count == 0 )); then
|
||||
warn "OpenCode: no agent files found in $search_dir. Run convert.sh --tool opencode first."
|
||||
else
|
||||
ok "OpenCode: $count agents -> $dest"
|
||||
fi
|
||||
capacity_warn opencode "$count"
|
||||
warn "OpenCode: project-scoped. Run from your project root to install there."
|
||||
}
|
||||
|
||||
install_openclaw() {
|
||||
local src="$INTEGRATIONS/openclaw"
|
||||
local dest; dest="$(resolve_dest openclaw "${HOME}/.openclaw/agency-agents")"
|
||||
local dest="${HOME}/.openclaw/agency-agents"
|
||||
local count=0
|
||||
local existing_agents=""
|
||||
[[ -d "$src" ]] || { err "integrations/openclaw missing. Run convert.sh first."; return 1; }
|
||||
@@ -772,12 +411,11 @@ install_openclaw() {
|
||||
local d
|
||||
while IFS= read -r -d '' d; do
|
||||
local name; name="$(basename "$d")"
|
||||
slug_allowed "$name" || continue
|
||||
[[ -f "$d/SOUL.md" && -f "$d/AGENTS.md" && -f "$d/IDENTITY.md" ]] || continue
|
||||
mkdir -p "$dest/$name"
|
||||
install_file "$d/SOUL.md" "$dest/$name/SOUL.md"
|
||||
install_file "$d/AGENTS.md" "$dest/$name/AGENTS.md"
|
||||
install_file "$d/IDENTITY.md" "$dest/$name/IDENTITY.md"
|
||||
cp "$d/SOUL.md" "$dest/$name/SOUL.md"
|
||||
cp "$d/AGENTS.md" "$dest/$name/AGENTS.md"
|
||||
cp "$d/IDENTITY.md" "$dest/$name/IDENTITY.md"
|
||||
if command -v openclaw >/dev/null 2>&1; then
|
||||
if [[ "$existing_agents" != *$'\n'"$name"$'\n'* ]]; then
|
||||
openclaw agents add "$name" --workspace "$dest/$name" --non-interactive || true
|
||||
@@ -797,14 +435,13 @@ install_openclaw() {
|
||||
|
||||
install_cursor() {
|
||||
local src="$INTEGRATIONS/cursor/rules"
|
||||
local dest; dest="$(resolve_dest cursor "${PWD}/.cursor/rules")"
|
||||
local dest="${PWD}/.cursor/rules"
|
||||
local count=0
|
||||
[[ -d "$src" ]] || { err "integrations/cursor missing. Run convert.sh first."; return 1; }
|
||||
mkdir -p "$dest"
|
||||
local f
|
||||
while IFS= read -r -d '' f; do
|
||||
slug_allowed "$(basename "$f" .mdc)" || continue
|
||||
install_file "$f" "$dest/"; incr count
|
||||
cp "$f" "$dest/"; (( count++ )) || true
|
||||
done < <(find "$src" -maxdepth 1 -name "*.mdc" -print0)
|
||||
ok "Cursor: $count rules -> $dest"
|
||||
warn "Cursor: project-scoped. Run from your project root to install there."
|
||||
@@ -818,9 +455,8 @@ install_aider() {
|
||||
warn "Aider: CONVENTIONS.md already exists at $dest (remove to reinstall)."
|
||||
return 0
|
||||
fi
|
||||
install_file "$src" "$dest"
|
||||
cp "$src" "$dest"
|
||||
ok "Aider: installed -> $dest"
|
||||
$SELECTION_ACTIVE && warn "Aider: single-file format — team/agent filtering N/A (installs the full roster)."
|
||||
warn "Aider: project-scoped. Run from your project root to install there."
|
||||
}
|
||||
|
||||
@@ -832,15 +468,14 @@ install_windsurf() {
|
||||
warn "Windsurf: .windsurfrules already exists at $dest (remove to reinstall)."
|
||||
return 0
|
||||
fi
|
||||
install_file "$src" "$dest"
|
||||
cp "$src" "$dest"
|
||||
ok "Windsurf: installed -> $dest"
|
||||
$SELECTION_ACTIVE && warn "Windsurf: single-file format — team/agent filtering N/A (installs the full roster)."
|
||||
warn "Windsurf: project-scoped. Run from your project root to install there."
|
||||
}
|
||||
|
||||
install_qwen() {
|
||||
local src="$INTEGRATIONS/qwen/agents"
|
||||
local dest; dest="$(resolve_dest qwen "${PWD}/.qwen/agents")"
|
||||
local dest="${PWD}/.qwen/agents"
|
||||
local count=0
|
||||
|
||||
[[ -d "$src" ]] || { err "integrations/qwen missing. Run convert.sh first."; return 1; }
|
||||
@@ -849,9 +484,8 @@ install_qwen() {
|
||||
|
||||
local f
|
||||
while IFS= read -r -d '' f; do
|
||||
slug_allowed "$(basename "$f" .md)" || continue
|
||||
install_file "$f" "$dest/"
|
||||
incr count
|
||||
cp "$f" "$dest/"
|
||||
(( count++ )) || true
|
||||
done < <(find "$src" -maxdepth 1 -name "*.md" -print0)
|
||||
|
||||
ok "Qwen Code: installed $count agents to $dest"
|
||||
@@ -861,7 +495,7 @@ install_qwen() {
|
||||
|
||||
install_kimi() {
|
||||
local src="$INTEGRATIONS/kimi"
|
||||
local dest; dest="$(resolve_dest kimi "${HOME}/.config/kimi/agents")"
|
||||
local dest="${HOME}/.config/kimi/agents"
|
||||
local count=0
|
||||
|
||||
[[ -d "$src" ]] || { err "integrations/kimi missing. Run convert.sh first."; return 1; }
|
||||
@@ -871,11 +505,10 @@ install_kimi() {
|
||||
local d
|
||||
while IFS= read -r -d '' d; do
|
||||
local name; name="$(basename "$d")"
|
||||
slug_allowed "$name" || continue
|
||||
mkdir -p "$dest/$name"
|
||||
install_file "$d/agent.yaml" "$dest/$name/agent.yaml"
|
||||
install_file "$d/system.md" "$dest/$name/system.md"
|
||||
incr count
|
||||
cp "$d/agent.yaml" "$dest/$name/agent.yaml"
|
||||
cp "$d/system.md" "$dest/$name/system.md"
|
||||
(( count++ )) || true
|
||||
done < <(find "$src" -mindepth 1 -maxdepth 1 -type d -print0)
|
||||
|
||||
ok "Kimi Code: installed $count agents to $dest"
|
||||
@@ -884,21 +517,19 @@ install_kimi() {
|
||||
|
||||
install_codex() {
|
||||
local src="$INTEGRATIONS/codex/agents"
|
||||
local dest; dest="$(resolve_dest codex "${HOME}/.codex/agents")"
|
||||
local dest="${HOME}/.codex/agents"
|
||||
local count=0
|
||||
[[ -d "$src" ]] || { err "integrations/codex missing. Run convert.sh first."; return 1; }
|
||||
mkdir -p "$dest"
|
||||
local f
|
||||
while IFS= read -r -d '' f; do
|
||||
slug_allowed "$(basename "$f" .toml)" || continue
|
||||
install_file "$f" "$dest/"
|
||||
incr count
|
||||
cp "$f" "$dest/"
|
||||
(( count++ )) || true
|
||||
done < <(find "$src" -maxdepth 1 -name "*.toml" -print0)
|
||||
ok "Codex: $count agents -> $dest"
|
||||
}
|
||||
|
||||
install_tool() {
|
||||
ensure_converted "$1"
|
||||
case "$1" in
|
||||
claude-code) install_claude_code ;;
|
||||
copilot) install_copilot ;;
|
||||
@@ -925,31 +556,9 @@ main() {
|
||||
local parallel_jobs
|
||||
parallel_jobs="$(parallel_jobs_default)"
|
||||
|
||||
local list_what=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--tool) tool="${2:?'--tool requires a value'}"; shift 2; interactive_mode="no" ;;
|
||||
--division)
|
||||
local _d
|
||||
IFS=',' read -ra _divs <<< "${2:?'--division requires a value'}"
|
||||
for _d in "${_divs[@]}"; do
|
||||
_d="$(printf '%s' "$_d" | xargs)"; [[ -z "$_d" ]] && continue
|
||||
validate_division "$_d"; FILTER_DIVISIONS+=("$_d")
|
||||
done
|
||||
interactive_mode="no"; shift 2 ;;
|
||||
--agent)
|
||||
local _a
|
||||
IFS=',' read -ra _ags <<< "${2:?'--agent requires a value'}"
|
||||
for _a in "${_ags[@]}"; do
|
||||
_a="$(printf '%s' "$_a" | xargs)"; [[ -n "$_a" ]] && FILTER_AGENTS+=("$_a")
|
||||
done
|
||||
interactive_mode="no"; shift 2 ;;
|
||||
--agents-file) AGENTS_FILE="${2:?'--agents-file requires a value'}"; interactive_mode="no"; shift 2 ;;
|
||||
--link) USE_LINK=true; shift ;;
|
||||
--path) OVERRIDE_PATH="${2:?'--path requires a value'}"; shift 2 ;;
|
||||
--no-convert) AUTO_CONVERT=false; shift ;;
|
||||
--dry-run) DRY_RUN=true; interactive_mode="no"; shift ;;
|
||||
--list) list_what="${2:-all}"; [[ "$list_what" == --* || -z "$list_what" ]] && { list_what="all"; shift; } || shift 2 ;;
|
||||
--interactive) interactive_mode="yes"; shift ;;
|
||||
--no-interactive) interactive_mode="no"; shift ;;
|
||||
--parallel) use_parallel=true; shift ;;
|
||||
@@ -959,9 +568,6 @@ main() {
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$list_what" ]] && { do_list "$list_what"; exit 0; }
|
||||
build_selection
|
||||
|
||||
check_integrations
|
||||
|
||||
# Validate explicit tool
|
||||
@@ -984,14 +590,14 @@ main() {
|
||||
|
||||
SELECTED_TOOLS=()
|
||||
|
||||
if $use_interactive && interactive_wizard; then
|
||||
: # wizard committed SELECTED_TOOLS + FILTER_DIVISIONS
|
||||
if $use_interactive; then
|
||||
interactive_select
|
||||
|
||||
elif [[ "$tool" != "all" ]]; then
|
||||
SELECTED_TOOLS=("$tool")
|
||||
|
||||
else
|
||||
# Non-interactive (or no TTY): auto-detect
|
||||
# Non-interactive: auto-detect
|
||||
header "The Agency -- Scanning for installed tools..."
|
||||
printf "\n"
|
||||
local t
|
||||
@@ -1013,28 +619,6 @@ main() {
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --dry-run: print the plan and exit without writing anything.
|
||||
if $DRY_RUN; then
|
||||
local agents; agents="$(selected_agent_count)"
|
||||
printf "\n"; header "The Agency -- Dry run (nothing written)"
|
||||
printf " Tools: %s\n" "${SELECTED_TOOLS[*]}"
|
||||
if $SELECTION_ACTIVE; then
|
||||
[[ ${#FILTER_DIVISIONS[@]} -gt 0 ]] && printf " Teams: %s\n" "${FILTER_DIVISIONS[*]}"
|
||||
[[ ${#FILTER_AGENTS[@]} -gt 0 ]] && printf " Agents: %s\n" "${FILTER_AGENTS[*]}"
|
||||
[[ -n "$AGENTS_FILE" ]] && printf " File: %s\n" "$AGENTS_FILE"
|
||||
else
|
||||
printf " Teams: all (%s)\n" "${#ALL_DIVISIONS[@]}"
|
||||
fi
|
||||
printf " Agents: %s Mode: %s\n" "$agents" "$($USE_LINK && echo symlink || echo copy)"
|
||||
local _t _cap
|
||||
for _t in "${SELECTED_TOOLS[@]}"; do
|
||||
_cap="$(tool_cap "$_t")"
|
||||
[[ "$_cap" -gt 0 && "$agents" -gt "$_cap" ]] && \
|
||||
warn "$_t caps ~$_cap — ~$(( agents - _cap )) of $agents won't register (anomalyco/opencode#27988)"
|
||||
done
|
||||
printf "\n"; exit 0
|
||||
fi
|
||||
|
||||
# When parent runs install.sh --parallel, it spawns workers with AGENCY_INSTALL_WORKER=1
|
||||
# so each worker only runs install_tool(s) and skips header/done box (avoids duplicate output).
|
||||
if [[ -n "${AGENCY_INSTALL_WORKER:-}" ]]; then
|
||||
@@ -1050,11 +634,6 @@ main() {
|
||||
printf " Repo: %s\n" "$REPO_ROOT"
|
||||
local n_selected=${#SELECTED_TOOLS[@]}
|
||||
printf " Installing: %s\n" "${SELECTED_TOOLS[*]}"
|
||||
if $SELECTION_ACTIVE; then
|
||||
[[ ${#FILTER_DIVISIONS[@]} -gt 0 ]] && printf " Teams: %s\n" "${FILTER_DIVISIONS[*]}"
|
||||
printf " Agents: %s of %s\n" "$(selected_agent_count)" "$(selected_agent_count_all)"
|
||||
fi
|
||||
$USE_LINK && printf " Mode: ${C_CYAN}symlink${C_RESET} (--link)\n"
|
||||
if $use_parallel; then
|
||||
ok "Installing $n_selected tools in parallel (output buffered per tool)."
|
||||
fi
|
||||
@@ -1066,8 +645,7 @@ main() {
|
||||
install_out_dir="$(mktemp -d)"
|
||||
export AGENCY_INSTALL_OUT_DIR="$install_out_dir"
|
||||
export AGENCY_INSTALL_SCRIPT="$SCRIPT_DIR/install.sh"
|
||||
export AGENCY_INSTALL_EXTRA="$(worker_flags)"
|
||||
printf '%s\n' "${SELECTED_TOOLS[@]}" | xargs -P "$parallel_jobs" -I {} sh -c 'AGENCY_INSTALL_WORKER=1 "$AGENCY_INSTALL_SCRIPT" --tool "{}" --no-interactive $AGENCY_INSTALL_EXTRA > "$AGENCY_INSTALL_OUT_DIR/{}" 2>&1'
|
||||
printf '%s\n' "${SELECTED_TOOLS[@]}" | xargs -P "$parallel_jobs" -I {} sh -c 'AGENCY_INSTALL_WORKER=1 "$AGENCY_INSTALL_SCRIPT" --tool "{}" --no-interactive > "$AGENCY_INSTALL_OUT_DIR/{}" 2>&1'
|
||||
for t in "${SELECTED_TOOLS[@]}"; do
|
||||
[[ -f "$install_out_dir/$t" ]] && cat "$install_out_dir/$t"
|
||||
done
|
||||
|
||||
-163
@@ -1,163 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# lib.sh — shared pure-bash helpers for scripts/convert.sh and scripts/install.sh.
|
||||
#
|
||||
# No external dependencies. Bash 3.2+ compatible (macOS ships 3.2).
|
||||
# Sourced, not executed. Groups:
|
||||
# 1. Frontmatter / slug helpers (agent data model)
|
||||
# 2. set -e-safe primitives
|
||||
# 3. Terminal capability + ANSI (color, unicode, sizing)
|
||||
# 4. TUI primitives (raw input, alt-screen, flicker-free draw)
|
||||
#
|
||||
# Everything here is namespaced loosely and guarded so it is safe to source
|
||||
# from a script already running under `set -euo pipefail`.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Frontmatter / slug helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# get_field <field> <file> — value of a YAML frontmatter field (first match).
|
||||
get_field() {
|
||||
local field="$1" file="$2"
|
||||
awk -v f="$field" '
|
||||
/^---$/ { fm++; next }
|
||||
fm == 1 && $0 ~ "^" f ": " { sub("^" f ": ", ""); print; exit }
|
||||
' "$file"
|
||||
}
|
||||
|
||||
# get_body <file> — file contents with the leading frontmatter block stripped.
|
||||
get_body() {
|
||||
awk 'BEGIN{fm=0} /^---$/{fm++; next} fm>=2{print}' "$1"
|
||||
}
|
||||
|
||||
# slugify <string> — "Frontend Developer" -> "frontend-developer"
|
||||
slugify() {
|
||||
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' \
|
||||
| sed 's/[^a-z0-9]/-/g; s/--*/-/g; s/^-//; s/-$//'
|
||||
}
|
||||
|
||||
# agent_slug <file> — slug derived from the file's `name:` frontmatter.
|
||||
# Single source of truth so convert + install always agree.
|
||||
agent_slug() {
|
||||
local name; name="$(get_field name "$1")"
|
||||
[[ -n "$name" ]] && slugify "$name"
|
||||
}
|
||||
|
||||
# is_agent_file <file> — true if the file starts with a YAML frontmatter fence.
|
||||
is_agent_file() {
|
||||
[[ -f "$1" ]] && [[ "$(head -1 "$1")" == "---" ]]
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. set -e-safe primitives (absorbs #505 — no more `(( x++ )) || true`)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# incr <varname> — increment a numeric variable in place, safely under set -e.
|
||||
incr() { printf -v "$1" '%d' "$(( ${!1:-0} + 1 ))"; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Terminal capability + ANSI
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
supports_color() { [[ -t 1 && -z "${NO_COLOR:-}" && "${TERM:-}" != "dumb" ]]; }
|
||||
supports_unicode() { [[ "${LANG:-}${LC_ALL:-}${LC_CTYPE:-}" == *[Uu][Tt][Ff]* ]]; }
|
||||
|
||||
term_cols() { local c; c="$(tput cols 2>/dev/null)"; [[ "$c" =~ ^[0-9]+$ ]] && echo "$c" || echo 80; }
|
||||
term_rows() { local r; r="$(tput lines 2>/dev/null)"; [[ "$r" =~ ^[0-9]+$ ]] && echo "$r" || echo 24; }
|
||||
|
||||
# init_ansi — populate C_* color vars + box-drawing chars (UTF-8 or ASCII).
|
||||
init_ansi() {
|
||||
if supports_color; then
|
||||
C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_DIM=$'\033[2m'; C_REV=$'\033[7m'
|
||||
C_RED=$'\033[0;31m'; C_GREEN=$'\033[0;32m'; C_YELLOW=$'\033[1;33m'
|
||||
C_BLUE=$'\033[0;34m'; C_CYAN=$'\033[0;36m'; C_MAGENTA=$'\033[0;35m'
|
||||
else
|
||||
C_RESET=''; C_BOLD=''; C_DIM=''; C_REV=''
|
||||
C_RED=''; C_GREEN=''; C_YELLOW=''; C_BLUE=''; C_CYAN=''; C_MAGENTA=''
|
||||
fi
|
||||
if supports_unicode; then
|
||||
BX_TL='╭'; BX_TR='╮'; BX_BL='╰'; BX_BR='╯'; BX_H='─'; BX_V='│'
|
||||
GLYPH_ON='✓'; GLYPH_DET='●'; GLYPH_OFF='○'; GLYPH_CUR='▸'
|
||||
else
|
||||
BX_TL='+'; BX_TR='+'; BX_BL='+'; BX_BR='+'; BX_H='-'; BX_V='|'
|
||||
GLYPH_ON='x'; GLYPH_DET='*'; GLYPH_OFF=' '; GLYPH_CUR='>'
|
||||
fi
|
||||
}
|
||||
|
||||
# repeat <char> <n> — print <char> n times.
|
||||
repeat() { local i; for (( i=0; i<$2; i++ )); do printf '%s' "$1"; done; }
|
||||
|
||||
# strip_ansi <string> — remove ANSI escape sequences (for width math).
|
||||
strip_ansi() { printf '%s' "$1" | sed $'s/\033\\[[0-9;]*m//g'; }
|
||||
|
||||
# vis_len <string> — visible length (ANSI-stripped). Note: assumes 1 col/char.
|
||||
vis_len() { local s; s="$(strip_ansi "$1")"; printf '%s' "${#s}"; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. TUI primitives (used by install.sh's interactive wizard)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_TUI_ACTIVE=0
|
||||
_TUI_STTY_SAVE=""
|
||||
|
||||
# tui_begin — enter alt screen, hide cursor, raw mode; install restore trap.
|
||||
tui_begin() {
|
||||
# Test hook: drive the wizard from piped keystrokes (skips the TTY gate and
|
||||
# the alt-screen/stty takeover). Used by the install-script test harness.
|
||||
[[ -n "${AGENCY_TUI_FORCE:-}" ]] && { _TUI_ACTIVE=1; return 0; }
|
||||
[[ -t 0 && -t 1 ]] || return 1
|
||||
_TUI_STTY_SAVE="$(stty -g 2>/dev/null)" || return 1
|
||||
stty -echo -icanon time 0 min 1 2>/dev/null || return 1
|
||||
printf '\033[?1049h\033[?25l' # alt screen + hide cursor
|
||||
_TUI_ACTIVE=1
|
||||
trap 'tui_end' EXIT INT TERM
|
||||
}
|
||||
|
||||
# tui_end — restore terminal (idempotent; safe from trap).
|
||||
tui_end() {
|
||||
[[ "$_TUI_ACTIVE" == "1" ]] || return 0
|
||||
printf '\033[?25h\033[?1049l' # show cursor + leave alt screen
|
||||
[[ -n "$_TUI_STTY_SAVE" ]] && stty "$_TUI_STTY_SAVE" 2>/dev/null
|
||||
_TUI_ACTIVE=0
|
||||
trap - EXIT INT TERM
|
||||
}
|
||||
|
||||
# read_key — read one keypress, echo a normalized token:
|
||||
# UP DOWN LEFT RIGHT ENTER SPACE ESC BACKSPACE TAB or the literal character.
|
||||
#
|
||||
# Reads escape sequences byte-by-byte with INTEGER timeouts (bash 3.2 has no
|
||||
# fractional -t). A real arrow sends ESC [ A (or ESC O A in application-cursor
|
||||
# mode) as one buffered burst, so the follow-up reads return instantly; only a
|
||||
# lone Esc waits out the 1s timeout. Handles both CSI ('[') and SS3 ('O').
|
||||
read_key() {
|
||||
local k k2 k3
|
||||
IFS= read -rsn1 k 2>/dev/null || { printf 'EOF'; return; }
|
||||
case "$k" in
|
||||
$'\033')
|
||||
if ! IFS= read -rsn1 -t 1 k2 2>/dev/null; then printf 'ESC'; return; fi
|
||||
if [[ "$k2" == '[' || "$k2" == 'O' ]]; then
|
||||
IFS= read -rsn1 -t 1 k3 2>/dev/null
|
||||
case "$k3" in
|
||||
A) printf 'UP' ;; B) printf 'DOWN' ;;
|
||||
C) printf 'RIGHT' ;; D) printf 'LEFT' ;;
|
||||
*) printf 'ESC' ;;
|
||||
esac
|
||||
else
|
||||
printf 'ESC'
|
||||
fi ;;
|
||||
$'\n'|$'\r'|'') printf 'ENTER' ;; # Enter is CR in raw mode (sometimes empty)
|
||||
' ') printf 'SPACE' ;;
|
||||
$'\t') printf 'TAB' ;;
|
||||
$'\177'|$'\010') printf 'BACKSPACE' ;;
|
||||
*) printf '%s' "$k" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# draw_frame <buffer> — home cursor and paint a pre-composed frame.
|
||||
# Flicker-free: erase-to-end-of-line (\033[K) on every line so a shorter new
|
||||
# line never leaves the previous frame's tail behind, then erase-to-end-of-
|
||||
# screen (\033[0J) to drop any leftover lines below the frame.
|
||||
draw_frame() {
|
||||
local buf="${1//$'\n'/$'\033[K'$'\n'}"
|
||||
printf '\033[H%s\033[K\033[0J' "$buf"
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
name: Workflow Architect
|
||||
description: Workflow design specialist who maps complete workflow trees for every system, user journey, and agent interaction — covering happy paths, all branch conditions, failure modes, recovery paths, handoff contracts, and observable states to produce build-ready specs that agents can implement against and QA can test against.
|
||||
color: orange
|
||||
emoji: "🗺️"
|
||||
emoji: "\U0001F5FA\uFE0F"
|
||||
vibe: Every path the system can take — mapped, named, and specified before a single line is written.
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user