@lawrencehui/citio 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.dockerignore +8 -0
  2. package/Dockerfile +47 -0
  3. package/LICENSE +21 -0
  4. package/README.md +305 -0
  5. package/citio.example.yaml +43 -0
  6. package/dist/adapters/slack.d.ts +22 -0
  7. package/dist/adapters/slack.d.ts.map +1 -0
  8. package/dist/adapters/slack.js +624 -0
  9. package/dist/adapters/slack.js.map +1 -0
  10. package/dist/cli/init.d.ts +3 -0
  11. package/dist/cli/init.d.ts.map +1 -0
  12. package/dist/cli/init.js +1464 -0
  13. package/dist/cli/init.js.map +1 -0
  14. package/dist/cli/ops.d.ts +13 -0
  15. package/dist/cli/ops.d.ts.map +1 -0
  16. package/dist/cli/ops.js +167 -0
  17. package/dist/cli/ops.js.map +1 -0
  18. package/dist/config/schema.d.ts +81 -0
  19. package/dist/config/schema.d.ts.map +1 -0
  20. package/dist/config/schema.js +65 -0
  21. package/dist/config/schema.js.map +1 -0
  22. package/dist/core/agent-runner.d.ts +38 -0
  23. package/dist/core/agent-runner.d.ts.map +1 -0
  24. package/dist/core/agent-runner.js +531 -0
  25. package/dist/core/agent-runner.js.map +1 -0
  26. package/dist/core/mcp-entry.d.ts +7 -0
  27. package/dist/core/mcp-entry.d.ts.map +1 -0
  28. package/dist/core/mcp-entry.js +390 -0
  29. package/dist/core/mcp-entry.js.map +1 -0
  30. package/dist/core/provider-config.d.ts +2 -0
  31. package/dist/core/provider-config.d.ts.map +1 -0
  32. package/dist/core/provider-config.js +53 -0
  33. package/dist/core/provider-config.js.map +1 -0
  34. package/dist/core/session-manager.d.ts +15 -0
  35. package/dist/core/session-manager.d.ts.map +1 -0
  36. package/dist/core/session-manager.js +78 -0
  37. package/dist/core/session-manager.js.map +1 -0
  38. package/dist/core/skills.d.ts +24 -0
  39. package/dist/core/skills.d.ts.map +1 -0
  40. package/dist/core/skills.js +111 -0
  41. package/dist/core/skills.js.map +1 -0
  42. package/dist/core/workspace.d.ts +13 -0
  43. package/dist/core/workspace.d.ts.map +1 -0
  44. package/dist/core/workspace.js +238 -0
  45. package/dist/core/workspace.js.map +1 -0
  46. package/dist/index.d.ts +2 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +152 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/utils/app-state.d.ts +5 -0
  51. package/dist/utils/app-state.d.ts.map +1 -0
  52. package/dist/utils/app-state.js +35 -0
  53. package/dist/utils/app-state.js.map +1 -0
  54. package/dist/utils/claude.d.ts +4 -0
  55. package/dist/utils/claude.d.ts.map +1 -0
  56. package/dist/utils/claude.js +65 -0
  57. package/dist/utils/claude.js.map +1 -0
  58. package/dist/utils/codex.d.ts +5 -0
  59. package/dist/utils/codex.d.ts.map +1 -0
  60. package/dist/utils/codex.js +17 -0
  61. package/dist/utils/codex.js.map +1 -0
  62. package/dist/utils/env.d.ts +2 -0
  63. package/dist/utils/env.d.ts.map +1 -0
  64. package/dist/utils/env.js +16 -0
  65. package/dist/utils/env.js.map +1 -0
  66. package/dist/utils/installer-state.d.ts +33 -0
  67. package/dist/utils/installer-state.d.ts.map +1 -0
  68. package/dist/utils/installer-state.js +152 -0
  69. package/dist/utils/installer-state.js.map +1 -0
  70. package/dist/utils/runtime-env.d.ts +3 -0
  71. package/dist/utils/runtime-env.d.ts.map +1 -0
  72. package/dist/utils/runtime-env.js +24 -0
  73. package/dist/utils/runtime-env.js.map +1 -0
  74. package/dist/utils/secret-store.d.ts +18 -0
  75. package/dist/utils/secret-store.d.ts.map +1 -0
  76. package/dist/utils/secret-store.js +78 -0
  77. package/dist/utils/secret-store.js.map +1 -0
  78. package/dist/utils/slack-onboarding.d.ts +22 -0
  79. package/dist/utils/slack-onboarding.d.ts.map +1 -0
  80. package/dist/utils/slack-onboarding.js +205 -0
  81. package/dist/utils/slack-onboarding.js.map +1 -0
  82. package/package.json +76 -0
package/.dockerignore ADDED
@@ -0,0 +1,8 @@
1
+ node_modules
2
+ src
3
+ .git
4
+ .gstack
5
+ *.md
6
+ !README.md
7
+ tsconfig.json
8
+ .env
package/Dockerfile ADDED
@@ -0,0 +1,47 @@
1
+ FROM node:22-slim
2
+
3
+ # Create non-root user
4
+ RUN useradd -m -s /bin/bash citio
5
+
6
+ # Install coding agent CLIs globally
7
+ RUN npm install -g @openai/codex @anthropic-ai/claude-code
8
+
9
+ # Install base tools (git, gh, aws-cli, jq, curl)
10
+ RUN apt-get update && apt-get install -y \
11
+ git curl jq unzip openssh-client procps \
12
+ && curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscli.zip" \
13
+ && unzip awscli.zip && ./aws/install && rm -rf aws awscli.zip \
14
+ && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
15
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
16
+ && apt-get update && apt-get install -y gh \
17
+ && rm -rf /var/lib/apt/lists/*
18
+
19
+ # Copy application
20
+ WORKDIR /app
21
+ # package-lock.json is present in source builds (npm ci = reproducible) but is
22
+ # stripped by npm from the published package, so fall back to npm install there.
23
+ COPY package.json package-lock.json* ./
24
+ RUN if [ -f package-lock.json ]; then npm ci --omit=dev; else npm install --omit=dev; fi
25
+ COPY dist/ ./dist/
26
+
27
+ # Config volume β€” mount your citio.yaml here, edit anytime, restart to apply
28
+ RUN mkdir -p /config /workspace /memory /tmp/citio \
29
+ && chown -R citio:citio /config /workspace /memory /tmp/citio /app
30
+ VOLUME ["/config", "/workspace", "/memory"]
31
+
32
+ # Health check
33
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
34
+ CMD curl -f http://localhost:3001/healthz || exit 1
35
+
36
+ # Run as non-root
37
+ USER citio
38
+
39
+ # Default env vars (override at runtime)
40
+ ENV CITIO_CONFIG=/config/citio.yaml
41
+ ENV CITIO_WORKSPACE=/workspace
42
+ ENV CITIO_MEMORY=/memory
43
+ ENV NODE_ENV=production
44
+
45
+ EXPOSE 3001
46
+
47
+ ENTRYPOINT ["node", "dist/index.js"]
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lawrence Hui <hkc.lawrence@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,305 @@
1
+ <div align="center">
2
+
3
+ # πŸ€– Citio
4
+
5
+ **Your own AI engineering teammate β€” self-hosted, living in Slack.**
6
+
7
+ `@mention` it or DM it and ask for real engineering work β€” investigate a bug, dig through CloudWatch logs, fix code, open a PR β€” and Citio runs **Claude Code** or **OpenAI Codex** inside your own infrastructure to do it. Slack is the interface, a controlled MCP tool layer is the safety boundary, and every credential stays in your AWS account.
8
+
9
+ **No Team or Enterprise plan required.** Citio runs on an individual **Claude Max/Pro** or **ChatGPT Go/Plus/Pro (Codex)** subscription β€” the agent you already pay for, now working from Slack.
10
+
11
+ <br/>
12
+
13
+ [![Status](https://img.shields.io/badge/status-pre--1.0-orange.svg)](docs/KNOWN_LIMITATIONS.md)
14
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
15
+ [![Node](https://img.shields.io/badge/node-%E2%89%A5%2022-339933.svg?logo=node.js&logoColor=white)](package.json)
16
+ [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6.svg?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
17
+ [![Slack-native](https://img.shields.io/badge/Slack-native-4A154B.svg?logo=slack&logoColor=white)](#-how-it-works)
18
+ [![Deploy: AWS ECS](https://img.shields.io/badge/deploy-AWS%20ECS%20Fargate-FF9900.svg?logo=amazonaws&logoColor=white)](#-quickstart)
19
+ [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
20
+
21
+ [![Claude Code](https://img.shields.io/badge/Claude%20Code-supported-D97757.svg?logo=anthropic&logoColor=white)](https://docs.anthropic.com/en/docs/claude-code)
22
+ [![OpenAI Codex](https://img.shields.io/badge/OpenAI%20Codex-supported-412991.svg?logo=openai&logoColor=white)](https://openai.com/codex/)
23
+ [![No Enterprise plan needed](https://img.shields.io/badge/subscription-friendly-success.svg)](#-citio-vs-hosted-slack-agents)
24
+
25
+ [**Quickstart**](#-quickstart) Β· [**How it works**](#-how-it-works) Β· [**Compare**](#-citio-vs-hosted-slack-agents) Β· [**Configuration**](#-configuration) Β· [**Customize**](#-customizing-your-instance) Β· [**Architecture**](docs/ARCHITECTURE.md) Β· [**Contributing**](CONTRIBUTING.md) Β· [**Security**](SECURITY.md)
26
+
27
+ <br/>
28
+
29
+ <!-- ![Citio turning a Slack message into a pull request](docs/screenshots/demo.gif) β€” uncomment when the asset lands -->
30
+
31
+ *A Slack message becomes an investigated, tested pull request β€” without leaving the thread.*
32
+
33
+ </div>
34
+
35
+ ---
36
+
37
+ ## ✨ Why Citio
38
+
39
+ Most teams can already chat with an LLM. The harder problem is letting a team ask for **real engineering work** from Slack without handing a raw shell and a pile of credentials directly to the model.
40
+
41
+ Citio closes that gap:
42
+
43
+ - πŸ’¬ **Slack is the user interface** β€” DM the bot or `@mention` it in a channel.
44
+ - 🧠 **Claude Code or Codex is the execution engine** β€” the provider CLI does the reasoning and planning.
45
+ - πŸ›‘οΈ **Citio is the control plane** β€” it owns orchestration, session handling, repo setup, AWS/GitHub access, and a controlled MCP tool layer so the agent never touches raw credentials.
46
+ - 🏠 **Everything runs in your infra** β€” your container, your AWS account, your keys.
47
+
48
+ The result is something that can investigate bugs, inspect logs, edit code, and open pull requests β€” without a human sitting in the middle of every request.
49
+
50
+ ## πŸ†š Citio vs. hosted Slack agents
51
+
52
+ Anthropic's [Claude Tag](https://techcrunch.com/2026/06/23/anthropics-claude-tag-is-learning-your-company-one-slack-message-at-a-time/) (June 2026) popularized exactly this idea β€” `@mention` an AI teammate in Slack and it does the work in-thread β€” but it's an Anthropic-hosted service gated to **Claude Team and Enterprise** plans, Claude-only. Citio takes the self-hosted, bring-your-own-subscription path:
53
+
54
+ | | **Citio** | **Claude Tag** |
55
+ | ------------------ | -------------------------------------------------- | ------------------------------------ |
56
+ | **Hosting** | Your AWS account, your infra | Anthropic-hosted SaaS |
57
+ | **Plan required** | Individual **Claude Max/Pro** *or* **ChatGPT Go/Plus/Pro** | **Claude Team or Enterprise** |
58
+ | **Providers** | Claude Code **or** OpenAI Codex | Claude only |
59
+ | **Credentials** | Stay with you, behind an MCP allowlist | Managed by the vendor |
60
+ | **Best for** | Solo devs & small teams who self-host | Orgs already on Team/Enterprise |
61
+
62
+ If you already pay for a Claude or ChatGPT subscription, Citio puts that same agent to work from Slack β€” no per-seat enterprise upgrade, no handing your code and credentials to someone else's cloud.
63
+
64
+ ## 🧩 Features
65
+
66
+ - 🀝 **Bring your own agent** β€” Claude Code or OpenAI Codex, your subscription or API key.
67
+ - 🧰 **Controlled MCP tools** β€” `investigate_codebase`, `read_file`, `write_file`, `create_branch`, `create_pr`, `run_command` (allowlisted), `check_ci_status`, `query_logs`, `recall_context`, and more.
68
+ - πŸ” **Credential boundary** β€” the agent calls MCP tools; secrets live with Citio, not the model. Command execution is allowlisted and shell-metacharacter-rejected.
69
+ - 🧡 **Slack-native** β€” DMs and channel mentions, streamed progress, redacted output.
70
+ - πŸ’Ύ **Persistent workspace & memory** β€” optional AWS EFS keeps repos, sessions, and provider auth across redeploys.
71
+ - πŸͺ„ **One-command installer** β€” interactive setup wires up Slack, GitHub, provider auth, and deploys to ECS.
72
+
73
+ ## πŸ—οΈ How it works
74
+
75
+ ```mermaid
76
+ flowchart TD
77
+ A["Slack DM or @mention"] --> B["SlackAdapter"]
78
+ B --> C["AgentRunner"]
79
+ C --> D["Claude Code or Codex CLI"]
80
+ D --> E["Citio MCP Server"]
81
+ E --> F["GitHub repos in persistent workspace"]
82
+ E --> G["GitHub PR / CI operations"]
83
+ E --> H["AWS CloudWatch / ECS reads"]
84
+ E --> I["Persistent org memory"]
85
+ ```
86
+
87
+ Runtime shape:
88
+
89
+ 1. A Slack request is normalized by the **Slack adapter**.
90
+ 2. **AgentRunner** serializes work and manages provider sessions (one active task per container).
91
+ 3. It spawns the **Claude Code / Codex CLI** as the agent, wired to Citio's **MCP server** via `--mcp-config`.
92
+ 4. The agent uses MCP tools for codebase reads/writes, PR creation, log queries, and progress updates β€” never raw credentials.
93
+ 5. Workspace, memory, and auth persist through **EFS** when enabled.
94
+
95
+ More detail: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
96
+
97
+ ## πŸš€ Quickstart
98
+
99
+ ### Prerequisites
100
+
101
+ **On your machine** (the installer hard-checks for Docker, AWS CLI, and Git):
102
+
103
+ | Tool | Version / note |
104
+ | ---- | -------------- |
105
+ | **Node.js** | β‰₯ 22 |
106
+ | **Docker** | Running. The image is built `linux/amd64` β€” on Apple Silicon, Docker Desktop's buildx cross-builds it. |
107
+ | **AWS CLI** | v2, authenticated (`aws configure` or `aws sso login`) with a profile that has the permissions below. New to AWS or unsure about permissions? **[docs/AWS_SETUP.md](docs/AWS_SETUP.md)** walks through account, CLI, credentials, a least-privilege IAM policy, costs, and teardown. |
108
+ | **Git** | Any recent version. |
109
+
110
+ > The agent CLIs (`claude`, `codex`), `gh`, and `jq` ship **inside the container image** β€” you don't install them on the host.
111
+
112
+ **Accounts & tokens**
113
+
114
+ - An **agent subscription**: Claude Max/Pro, or ChatGPT Go/Plus/Pro for Codex (API key works as a fallback).
115
+ - A **Slack app** (the installer can create it for you from a config token) + the target channel ID.
116
+ - A **GitHub fine-grained PAT** with `contents: write` + `pull_requests: write` on the repos you want worked on.
117
+
118
+ **Minimum AWS profile permissions**
119
+
120
+ The installer provisions the whole stack (ECR repo, ECS cluster/service, EFS, IAM roles, a security group) and reads logs, so the deploying profile needs create/manage rights across those services. Easiest path: use an **admin-capable profile in a dev/sandbox account**. For least privilege, this inline policy covers exactly what the installer calls:
121
+
122
+ <details>
123
+ <summary>Least-privilege IAM policy (click to expand)</summary>
124
+
125
+ ```json
126
+ {
127
+ "Version": "2012-10-17",
128
+ "Statement": [
129
+ { "Sid": "Identity", "Effect": "Allow", "Action": ["sts:GetCallerIdentity"], "Resource": "*" },
130
+ { "Sid": "Ecr", "Effect": "Allow", "Action": [
131
+ "ecr:CreateRepository", "ecr:GetAuthorizationToken",
132
+ "ecr:BatchCheckLayerAvailability", "ecr:InitiateLayerUpload",
133
+ "ecr:UploadLayerPart", "ecr:CompleteLayerUpload", "ecr:PutImage",
134
+ "ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer"
135
+ ], "Resource": "*" },
136
+ { "Sid": "Ecs", "Effect": "Allow", "Action": [
137
+ "ecs:CreateCluster", "ecs:RegisterTaskDefinition", "ecs:CreateService",
138
+ "ecs:UpdateService", "ecs:DescribeServices", "ecs:DescribeTasks",
139
+ "ecs:ListTasks", "ecs:RunTask"
140
+ ], "Resource": "*" },
141
+ { "Sid": "Efs", "Effect": "Allow", "Action": [
142
+ "elasticfilesystem:CreateFileSystem", "elasticfilesystem:DescribeFileSystems",
143
+ "elasticfilesystem:CreateMountTarget", "elasticfilesystem:DescribeMountTargets"
144
+ ], "Resource": "*" },
145
+ { "Sid": "Iam", "Effect": "Allow", "Action": [
146
+ "iam:CreateRole", "iam:AttachRolePolicy", "iam:PutRolePolicy", "iam:PassRole"
147
+ ], "Resource": "*" },
148
+ { "Sid": "Ec2", "Effect": "Allow", "Action": [
149
+ "ec2:CreateSecurityGroup", "ec2:DescribeSecurityGroups",
150
+ "ec2:DescribeSubnets", "ec2:DescribeVpcs"
151
+ ], "Resource": "*" },
152
+ { "Sid": "Logs", "Effect": "Allow", "Action": [
153
+ "logs:FilterLogEvents", "logs:GetLogEvents",
154
+ "logs:DescribeLogGroups", "logs:DescribeLogStreams", "logs:StartLiveTail"
155
+ ], "Resource": "*" }
156
+ ]
157
+ }
158
+ ```
159
+
160
+ `iam:PassRole` is required because the ECS task definition references the `citio-task-execution` role. Scope `Resource` down to your account/region ARNs for production.
161
+
162
+ </details>
163
+
164
+ ### Install and run
165
+
166
+ **Fastest β€” one command** (uses the published package, no clone, no build):
167
+
168
+ ```bash
169
+ npx citio
170
+ ```
171
+
172
+ **Or build from source** (to read/modify the code first, or to contribute):
173
+
174
+ ```bash
175
+ git clone https://github.com/lawrencehui/Citio.git
176
+ cd Citio
177
+ npm ci
178
+ npm run build
179
+ npm run init
180
+ ```
181
+
182
+ Both launch the **same** guided installer, which will:
183
+
184
+ - collect provider and auth settings (subscription OAuth first, API key as fallback)
185
+ - collect Slack and GitHub credentials (stored in your OS keychain when available)
186
+ - let you select which repos the agent can work on
187
+ - write a local `citio.yaml`
188
+ - build the image and deploy it to AWS ECS
189
+
190
+ ## βš™οΈ Configuration
191
+
192
+ The installer generates a local `citio.yaml`. The committed [`citio.example.yaml`](citio.example.yaml) shows the full shape:
193
+
194
+ ```yaml
195
+ name: citio
196
+ engine:
197
+ default_provider: claude # or "codex"
198
+ max_concurrent_sessions: 1
199
+ slack:
200
+ bot_token: ${SLACK_BOT_TOKEN}
201
+ app_token: ${SLACK_APP_TOKEN}
202
+ channel_id: C0123456789
203
+ workspace:
204
+ repos:
205
+ - url: https://github.com/your-org/your-repo.git
206
+ branch: main
207
+ rules:
208
+ - Always create PRs for code changes. Never push directly to main.
209
+ deploy:
210
+ provider: aws
211
+ aws:
212
+ region: eu-west-2
213
+ ecr_repo: citio # AWS resource names are yours to choose
214
+ ```
215
+
216
+ > ⚠️ `citio.yaml` holds local machine state (and is `.gitignore`d). Don't commit it.
217
+
218
+ **Runtime environment variables**
219
+
220
+ | Variable | Purpose |
221
+ | -------------------- | ------------------------------------------------ |
222
+ | `CITIO_CONFIG` | Path to the config file (default `citio.yaml`) |
223
+ | `CITIO_CONFIG_B64` | Base64-encoded config (used by ECS, no file mount) |
224
+ | `CITIO_WORKSPACE` | Workspace path (default `/workspace`) |
225
+ | `CITIO_MEMORY` | Memory/audit path (default `/memory`) |
226
+
227
+ > πŸ” **Tokens are not stored as plaintext env vars.** Your Slack, GitHub, and provider tokens are written to an **AWS Secrets Manager** secret (`citio/runtime`) and injected into the container by ECS at start β€” so they are **not** readable via `ecs:DescribeTaskDefinition`. The values above are non-sensitive runtime config only.
228
+
229
+ ## πŸŽ›οΈ Customizing your instance
230
+
231
+ Yes β€” a Citio instance is configured almost entirely through `citio.yaml` (the installer writes it for you, and you can hand-edit then redeploy). The main knobs:
232
+
233
+ | Setting | Where | What it controls |
234
+ | ------- | ----- | ---------------- |
235
+ | **Provider** | `engine.default_provider` | `claude` or `codex` |
236
+ | **Agent rules** | `workspace.rules[]` | Plain-English guardrails injected into the agent ("always open PRs", "check logs before editing", your own policies) |
237
+ | **Repos** | `workspace.repos[]` | Which repos (and branches) the agent may clone and work on |
238
+ | **Who can use it** | `slack.authorized_users[]` / `admin_users[]` | Restrict channel `@mention`s and DMs to specific Slack user IDs (empty = everyone) |
239
+ | **Session limits** | `engine.max_session_duration_minutes`, `max_concurrent_sessions` | How long a task can run; how many run at once (1 = strictly serialized) |
240
+ | **Skills** | `skills.installed[]` | Optional community skill packs the agent can use |
241
+ | **Commit identity** | `workspace.git.user_name` / `user_email` | Author on commits the agent makes |
242
+ | **Bot name** | Slack app manifest (set at install) | The `@name` it answers to |
243
+ | **AWS sizing & names** | `deploy.aws.task_cpu`, `task_memory`, `ephemeral_storage_gb`, `ecr_repo`, `ecs_cluster`, `ecs_service`, `region` | Container resources and the names of the resources Citio provisions |
244
+
245
+ The fastest way to change behavior is usually `workspace.rules` β€” those instructions shape how the agent investigates, edits, and reports. After editing `citio.yaml`, re-run `npm run init` (or restart the container) to apply.
246
+
247
+ See [`citio.example.yaml`](citio.example.yaml) for the full annotated shape.
248
+
249
+ ## 🧱 Supported today
250
+
251
+ | Area | Support |
252
+ | --------------- | ------------------------------------------------------------- |
253
+ | **Providers** | Claude Code, OpenAI Codex |
254
+ | **Deploy** | AWS ECS / Fargate, AWS ECR |
255
+ | **Persistence** | Optional AWS EFS for workspace, memory, and provider auth |
256
+
257
+ Citio is currently **AWS-first**. Multi-cloud support is not part of the current public release.
258
+
259
+ ## πŸ§ͺ Development
260
+
261
+ ```bash
262
+ npm run typecheck # tsc --noEmit
263
+ npm run build # compile to dist/
264
+ npm run test # node:test suite
265
+ npm run dev # run locally with tsx
266
+ ```
267
+
268
+ ## πŸ“Έ Screenshots
269
+
270
+ **The PR Citio opened** β€” real, reviewable work on GitHub:
271
+
272
+ <!-- ![A pull request opened by Citio](docs/screenshots/pr.png) β€” uncomment when the asset lands -->
273
+
274
+ **Working in a channel** β€” `@mention` it where your team already talks:
275
+
276
+ <!-- ![Citio responding to a channel mention](docs/screenshots/slack-channel.png) β€” uncomment when the asset lands -->
277
+
278
+ **The installer** β€” one guided command from zero to deployed:
279
+
280
+ <!-- ![The Citio interactive installer](docs/screenshots/installer.png) β€” uncomment when the asset lands -->
281
+
282
+ ## πŸ—ΊοΈ Status & roadmap
283
+
284
+ Citio is **pre-1.0** β€” usable for AWS-first self-hosted experimentation.
285
+
286
+ - βœ… Slack-native control plane for Claude Code / Codex
287
+ - βœ… Controlled MCP tool layer with audit log
288
+ - βœ… One-command ECS installer with optional EFS persistence
289
+ - ⏳ Not yet a hardened sandbox (provider CLIs retain native shell inside the container)
290
+ - ⏳ One active agent task per container
291
+ - ⏳ Single-cloud (AWS) only
292
+
293
+ Full caveats: [docs/KNOWN_LIMITATIONS.md](docs/KNOWN_LIMITATIONS.md)
294
+
295
+ ## πŸ™Œ Contributing
296
+
297
+ Contributions are welcome β€” see [CONTRIBUTING.md](CONTRIBUTING.md). Keep diffs small, prefer runtime-safe behavior over clever abstractions, and don't commit local machine state.
298
+
299
+ ## πŸ›‘οΈ Security
300
+
301
+ Found a vulnerability? Please report it privately β€” see [SECURITY.md](SECURITY.md). Don't open a public issue for credential handling, auth bypass, shell injection, or sandbox escape.
302
+
303
+ ## πŸ“„ License
304
+
305
+ [MIT](LICENSE)
@@ -0,0 +1,43 @@
1
+ name: citio
2
+ version: 1
3
+
4
+ slack:
5
+ bot_token: ${SLACK_BOT_TOKEN}
6
+ app_token: ${SLACK_APP_TOKEN}
7
+ channel_id: C0123456789
8
+ respond_without_mention: true # ambient mode: reply to plain messages in channel_id (skips messages @mentioning others)
9
+ authorized_users: []
10
+ admin_users: []
11
+
12
+ engine:
13
+ default_provider: claude
14
+ max_session_duration_minutes: 60
15
+ max_concurrent_sessions: 1
16
+ providers:
17
+ codex: {} # optional: { model: "...", reasoning_effort: low|medium|high }
18
+ claude: {} # optional: { model: "claude-sonnet-4-6" } β€” faster ops replies than the opus default
19
+
20
+ skills:
21
+ installed: []
22
+ directory: /workspace/.citio/skills/
23
+
24
+ workspace:
25
+ repos:
26
+ - url: https://github.com/example-org/example-repo.git
27
+ branch: main
28
+ rules:
29
+ - Always create PRs for code changes. Never push directly to main.
30
+ - When investigating bugs, check logs first before making code changes.
31
+ - Report findings back to the team with clear summaries.
32
+ - After any update or change pushed to a PR, always quote the PR link in your response.
33
+
34
+ deploy:
35
+ provider: aws
36
+ aws:
37
+ region: eu-west-2
38
+ ecr_repo: citio
39
+ ecs_cluster: citio
40
+ ecs_service: citio
41
+ task_cpu: 2048
42
+ task_memory: 8192
43
+ ephemeral_storage_gb: 100
@@ -0,0 +1,22 @@
1
+ import { AgentRunner } from "../core/agent-runner.js";
2
+ import { SessionManager } from "../core/session-manager.js";
3
+ import type { CitioConfig } from "../config/schema.js";
4
+ export declare class SlackAdapter {
5
+ private app;
6
+ private config;
7
+ private agentRunner;
8
+ private sessionManager;
9
+ constructor(config: CitioConfig, agentRunner: AgentRunner, sessionManager: SessionManager);
10
+ private setupAssistant;
11
+ private activeRequests;
12
+ /** Called on SIGTERM: tell every waiting thread we're restarting. */
13
+ notifyShutdown(): Promise<void>;
14
+ private handleChannelRequest;
15
+ private getProviderSessionId;
16
+ private rememberProviderSession;
17
+ private buildPrompt;
18
+ private startProgressPolling;
19
+ start(): Promise<void>;
20
+ stop(): Promise<void>;
21
+ }
22
+ //# sourceMappingURL=slack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/adapters/slack.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AA4GvD,qBAAa,YAAY;IACvB,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,cAAc,CAAiB;gBAE3B,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc;IAczF,OAAO,CAAC,cAAc;IAoQtB,OAAO,CAAC,cAAc,CAAkF;IAExG,qEAAqE;IAC/D,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;YAcvB,oBAAoB;IAwIlC,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,uBAAuB;IAI/B,OAAO,CAAC,WAAW;IA4CnB,OAAO,CAAC,oBAAoB;IA4CtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAStB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAI5B"}