create-ironclaws 1.0.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.
- package/README.md +101 -0
- package/bin/create.js +394 -0
- package/package.json +33 -0
- package/template/.env.example +38 -0
- package/template/CLAUDE.md +104 -0
- package/template/agent-credentials.yaml +33 -0
- package/template/agents.yaml +22 -0
- package/template/container/Dockerfile +70 -0
- package/template/container/Dockerfile.argus +34 -0
- package/template/container/agent-runner/package-lock.json +1524 -0
- package/template/container/agent-runner/package.json +23 -0
- package/template/container/agent-runner/src/index.ts +630 -0
- package/template/container/agent-runner/src/ipc-mcp-stdio.ts +339 -0
- package/template/container/agent-runner/tsconfig.json +15 -0
- package/template/container/build-argus.sh +25 -0
- package/template/container/build.sh +23 -0
- package/template/container/skills/agent-browser/SKILL.md +159 -0
- package/template/container/skills/agent-status/SKILL.md +69 -0
- package/template/container/skills/capabilities/SKILL.md +100 -0
- package/template/container/skills/edit-agent/SKILL.md +93 -0
- package/template/container/skills/slack-formatting/SKILL.md +92 -0
- package/template/container/skills/status/SKILL.md +104 -0
- package/template/container/tools/elastic_query.py +161 -0
- package/template/container/tools/gdrive_tool.py +185 -0
- package/template/container/tools/jira_tool.py +433 -0
- package/template/container/tools/slack_history_tool.py +144 -0
- package/template/container/tools/youtube_tool.py +174 -0
- package/template/docker-compose.yml +54 -0
- package/template/docs/how-it-works.md +496 -0
- package/template/eslint.config.js +32 -0
- package/template/groups/forge/CLAUDE.md +107 -0
- package/template/package-lock.json +5278 -0
- package/template/package.json +52 -0
- package/template/scripts/github-app-token.py +58 -0
- package/template/scripts/register-expense-agent.sh +121 -0
- package/template/scripts/run-migrations.ts +105 -0
- package/template/scripts/setup-onecli-secrets.sh +252 -0
- package/template/setup-agents.sh +142 -0
- package/template/src/channels/index.ts +13 -0
- package/template/src/channels/registry.test.ts +42 -0
- package/template/src/channels/registry.ts +28 -0
- package/template/src/channels/slack.test.ts +859 -0
- package/template/src/channels/slack.ts +373 -0
- package/template/src/claw-skill.test.ts +45 -0
- package/template/src/config.ts +94 -0
- package/template/src/container-runner.test.ts +221 -0
- package/template/src/container-runner.ts +1029 -0
- package/template/src/container-runtime.test.ts +149 -0
- package/template/src/container-runtime.ts +124 -0
- package/template/src/db-migration.test.ts +67 -0
- package/template/src/db.test.ts +484 -0
- package/template/src/db.ts +837 -0
- package/template/src/env.ts +42 -0
- package/template/src/formatting.test.ts +294 -0
- package/template/src/github-token.ts +48 -0
- package/template/src/google-token.ts +75 -0
- package/template/src/group-folder.test.ts +43 -0
- package/template/src/group-folder.ts +44 -0
- package/template/src/group-queue.test.ts +484 -0
- package/template/src/group-queue.ts +363 -0
- package/template/src/http-server.ts +343 -0
- package/template/src/index.ts +960 -0
- package/template/src/ipc-auth.test.ts +679 -0
- package/template/src/ipc.ts +548 -0
- package/template/src/logger.ts +16 -0
- package/template/src/mount-security.ts +421 -0
- package/template/src/network-policy.ts +119 -0
- package/template/src/remote-control.test.ts +397 -0
- package/template/src/remote-control.ts +224 -0
- package/template/src/router.ts +52 -0
- package/template/src/routing.test.ts +170 -0
- package/template/src/sender-allowlist.test.ts +216 -0
- package/template/src/sender-allowlist.ts +128 -0
- package/template/src/task-scheduler.test.ts +129 -0
- package/template/src/task-scheduler.ts +290 -0
- package/template/src/timezone.test.ts +73 -0
- package/template/src/timezone.ts +37 -0
- package/template/src/types.ts +114 -0
- package/template/src/worktree.ts +206 -0
- package/template/tsconfig.json +20 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: capabilities
|
|
3
|
+
description: Show what this NanoClaw instance can do — installed skills, available tools, and system info. Read-only. Use when the user asks what the bot can do, what's installed, or runs /capabilities.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /capabilities — System Capabilities Report
|
|
7
|
+
|
|
8
|
+
Generate a structured read-only report of what this NanoClaw instance can do.
|
|
9
|
+
|
|
10
|
+
**Main-channel check:** Only the main channel has `/workspace/project` mounted. Run:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
test -d /workspace/project && echo "MAIN" || echo "NOT_MAIN"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
If `NOT_MAIN`, respond with:
|
|
17
|
+
> This command is available in your main chat only. Send `/capabilities` there to see what I can do.
|
|
18
|
+
|
|
19
|
+
Then stop — do not generate the report.
|
|
20
|
+
|
|
21
|
+
## How to gather the information
|
|
22
|
+
|
|
23
|
+
Run these commands and compile the results into the report format below.
|
|
24
|
+
|
|
25
|
+
### 1. Installed skills
|
|
26
|
+
|
|
27
|
+
List skill directories available to you:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
ls -1 /home/node/.claude/skills/ 2>/dev/null || echo "No skills found"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Each directory is an installed skill. The directory name is the skill name (e.g., `agent-browser` → `/agent-browser`).
|
|
34
|
+
|
|
35
|
+
### 2. Available tools
|
|
36
|
+
|
|
37
|
+
Read the allowed tools from your SDK configuration. You always have access to:
|
|
38
|
+
- **Core:** Bash, Read, Write, Edit, Glob, Grep
|
|
39
|
+
- **Web:** WebSearch, WebFetch
|
|
40
|
+
- **Orchestration:** Task, TaskOutput, TaskStop, TeamCreate, TeamDelete, SendMessage (⚠️ for sub-agent teams only — does NOT send to Slack users)
|
|
41
|
+
- **Other:** TodoWrite, ToolSearch, Skill, NotebookEdit
|
|
42
|
+
- **MCP:** mcp__nanoclaw__* (messaging, tasks, group management)
|
|
43
|
+
|
|
44
|
+
### 3. MCP server tools
|
|
45
|
+
|
|
46
|
+
The NanoClaw MCP server exposes these tools (via `mcp__nanoclaw__*` prefix):
|
|
47
|
+
- `send_message` — send a message to the user/group
|
|
48
|
+
- `schedule_task` — schedule a recurring or one-time task
|
|
49
|
+
- `list_tasks` — list scheduled tasks
|
|
50
|
+
- `pause_task` — pause a scheduled task
|
|
51
|
+
- `resume_task` — resume a paused task
|
|
52
|
+
- `cancel_task` — cancel and delete a task
|
|
53
|
+
- `update_task` — update an existing task
|
|
54
|
+
- `register_group` — register a new chat/group (main only)
|
|
55
|
+
|
|
56
|
+
### 4. Container tools (Python tools)
|
|
57
|
+
|
|
58
|
+
Check for Python tools available in the container:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
ls /workspace/extra/tools/*.py 2>/dev/null || echo "No extra tools found"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 5. Group info
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
ls /workspace/group/CLAUDE.md 2>/dev/null && echo "Group memory: yes" || echo "Group memory: no"
|
|
68
|
+
ls /workspace/extra/ 2>/dev/null && echo "Extra mounts: $(ls /workspace/extra/ 2>/dev/null | wc -l | tr -d ' ')" || echo "Extra mounts: none"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Report format
|
|
72
|
+
|
|
73
|
+
Present the report as a clean, readable message. Example:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
📋 *NanoClaw Capabilities*
|
|
77
|
+
|
|
78
|
+
*Installed Skills:*
|
|
79
|
+
• /agent-browser — Browse the web, fill forms, extract data
|
|
80
|
+
• /capabilities — This report
|
|
81
|
+
(list all found skills)
|
|
82
|
+
|
|
83
|
+
*Tools:*
|
|
84
|
+
• Core: Bash, Read, Write, Edit, Glob, Grep
|
|
85
|
+
• Web: WebSearch, WebFetch
|
|
86
|
+
• Orchestration: Task, TeamCreate, SendMessage
|
|
87
|
+
• MCP: send_message, schedule_task, list_tasks, pause/resume/cancel/update_task, register_group
|
|
88
|
+
|
|
89
|
+
*Container Tools:*
|
|
90
|
+
• agent-browser: ✓
|
|
91
|
+
|
|
92
|
+
*System:*
|
|
93
|
+
• Group memory: yes/no
|
|
94
|
+
• Extra mounts: N directories
|
|
95
|
+
• Main channel: yes
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Adapt the output based on what you actually find — don't list things that aren't installed.
|
|
99
|
+
|
|
100
|
+
**See also:** `/status` for a quick health check of session, workspace, and tasks.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: edit-agent
|
|
3
|
+
description: Collaboratively edit any agent's CLAUDE.md — propose a diff, wait for confirmation, then apply. Use when asked to update, change, or improve an agent's instructions or behaviour.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Edit Agent — Collaborative CLAUDE.md Editor
|
|
7
|
+
|
|
8
|
+
Use this when the user wants to change how an agent behaves.
|
|
9
|
+
|
|
10
|
+
## Step 1 — Identify the target agent
|
|
11
|
+
|
|
12
|
+
Figure out which agent to edit from the user's message. If ambiguous, ask which one.
|
|
13
|
+
|
|
14
|
+
Get the folder name:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
python3 -c "
|
|
18
|
+
import sqlite3
|
|
19
|
+
db = sqlite3.connect('/workspace/project/store/messages.db')
|
|
20
|
+
rows = db.execute('SELECT name, folder FROM registered_groups').fetchall()
|
|
21
|
+
for r in rows:
|
|
22
|
+
print(f'{r[0]} -> {r[1]}')
|
|
23
|
+
db.close()
|
|
24
|
+
"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Step 2 — Read the current CLAUDE.md
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cat /workspace/project/groups/<folder>/CLAUDE.md
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Step 3 — Understand what needs to change
|
|
34
|
+
|
|
35
|
+
Read the current file carefully. Understand the full context before proposing anything.
|
|
36
|
+
|
|
37
|
+
## Step 4 — Propose the change
|
|
38
|
+
|
|
39
|
+
Show the user EXACTLY what will change. Be specific — quote the text being removed and the text replacing it, or show where new content will be inserted.
|
|
40
|
+
|
|
41
|
+
Format:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Here's what I'm proposing to change in *<Agent Name>*'s CLAUDE.md:
|
|
45
|
+
|
|
46
|
+
*Remove:*
|
|
47
|
+
> <exact text being removed>
|
|
48
|
+
|
|
49
|
+
*Replace with:*
|
|
50
|
+
> <exact new text>
|
|
51
|
+
|
|
52
|
+
*Reason:* <one sentence explaining why>
|
|
53
|
+
|
|
54
|
+
Should I go ahead?
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If it's an addition rather than a replacement:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
Here's what I'm proposing to add to *<Agent Name>*'s CLAUDE.md:
|
|
61
|
+
|
|
62
|
+
*Add after "<section heading>":*
|
|
63
|
+
> <exact new text>
|
|
64
|
+
|
|
65
|
+
Should I go ahead?
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Step 5 — Wait for explicit confirmation
|
|
69
|
+
|
|
70
|
+
Do NOT apply the change until the user says yes / go ahead / confirmed / approved or similar.
|
|
71
|
+
|
|
72
|
+
If they say no or want adjustments — revise and re-propose.
|
|
73
|
+
|
|
74
|
+
## Step 6 — Apply the change
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Read current content, apply edit, write back
|
|
78
|
+
# Use Python for safe file manipulation
|
|
79
|
+
python3 -c "
|
|
80
|
+
content = open('/workspace/extra/groups/<folder>/CLAUDE.md').read()
|
|
81
|
+
# Apply the specific change
|
|
82
|
+
content = content.replace('<old text>', '<new text>')
|
|
83
|
+
open('/workspace/extra/groups/<folder>/CLAUDE.md', 'w').write(content)
|
|
84
|
+
print('Done')
|
|
85
|
+
"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Step 7 — Confirm
|
|
89
|
+
|
|
90
|
+
Tell the user:
|
|
91
|
+
- What was changed
|
|
92
|
+
- That the change is live immediately (no restart needed for CLAUDE.md changes)
|
|
93
|
+
- The agent will use the new instructions from its next message
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: slack-formatting
|
|
3
|
+
description: Format messages for Slack using mrkdwn syntax. Use when responding to Slack channels (folder starts with "slack_" or JID contains slack identifiers).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Slack Message Formatting (mrkdwn)
|
|
7
|
+
|
|
8
|
+
When responding to Slack channels, use Slack's mrkdwn syntax instead of standard Markdown.
|
|
9
|
+
|
|
10
|
+
## Slack context
|
|
11
|
+
|
|
12
|
+
All agents in this deployment communicate via Slack. These formatting rules always apply.
|
|
13
|
+
|
|
14
|
+
## Formatting reference
|
|
15
|
+
|
|
16
|
+
### Text styles
|
|
17
|
+
|
|
18
|
+
| Style | Syntax | Example |
|
|
19
|
+
|-------|--------|---------|
|
|
20
|
+
| Bold | `*text*` | *bold text* |
|
|
21
|
+
| Italic | `_text_` | _italic text_ |
|
|
22
|
+
| Strikethrough | `~text~` | ~strikethrough~ |
|
|
23
|
+
| Code (inline) | `` `code` `` | `inline code` |
|
|
24
|
+
| Code block | ` ```code``` ` | Multi-line code |
|
|
25
|
+
|
|
26
|
+
### Links and mentions
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
<https://example.com|Link text> # Named link
|
|
30
|
+
<https://example.com> # Auto-linked URL
|
|
31
|
+
<@U1234567890> # Mention user by ID
|
|
32
|
+
<#C1234567890> # Mention channel by ID
|
|
33
|
+
<!here> # @here
|
|
34
|
+
<!channel> # @channel
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Lists
|
|
38
|
+
|
|
39
|
+
Slack supports simple bullet lists but NOT numbered lists:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
• First item
|
|
43
|
+
• Second item
|
|
44
|
+
• Third item
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Use `•` (bullet character) or `- ` or `* ` for bullets.
|
|
48
|
+
|
|
49
|
+
### Block quotes
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
> This is a block quote
|
|
53
|
+
> It can span multiple lines
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Emoji
|
|
57
|
+
|
|
58
|
+
Use standard emoji shortcodes: `:white_check_mark:`, `:x:`, `:rocket:`, `:tada:`
|
|
59
|
+
|
|
60
|
+
## What NOT to use
|
|
61
|
+
|
|
62
|
+
- **NO** `##` headings (use `*Bold text*` for headers instead)
|
|
63
|
+
- **NO** `**double asterisks**` for bold (use `*single asterisks*`)
|
|
64
|
+
- **NO** `[text](url)` links (use `<url|text>` instead)
|
|
65
|
+
- **NO** `1.` numbered lists (use bullets with numbers: `• 1. First`)
|
|
66
|
+
- **NO** tables (use code blocks or plain text alignment)
|
|
67
|
+
- **NO** `---` horizontal rules
|
|
68
|
+
|
|
69
|
+
## Example message
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
*Daily Standup Summary*
|
|
73
|
+
|
|
74
|
+
_March 21, 2026_
|
|
75
|
+
|
|
76
|
+
• *Completed:* Fixed authentication bug in login flow
|
|
77
|
+
• *In Progress:* Building new dashboard widgets
|
|
78
|
+
• *Blocked:* Waiting on API access from DevOps
|
|
79
|
+
|
|
80
|
+
> Next sync: Monday 10am
|
|
81
|
+
|
|
82
|
+
:white_check_mark: All tests passing | <https://ci.example.com/builds/123|View Build>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Quick rules
|
|
86
|
+
|
|
87
|
+
1. Use `*bold*` not `**bold**`
|
|
88
|
+
2. Use `<url|text>` not `[text](url)`
|
|
89
|
+
3. Use `•` bullets, avoid numbered lists
|
|
90
|
+
4. Use `:emoji:` shortcodes
|
|
91
|
+
5. Quote blocks with `>`
|
|
92
|
+
6. Skip headings — use bold text instead
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: status
|
|
3
|
+
description: Quick read-only health check — session context, workspace mounts, tool availability, and task snapshot. Use when the user asks for system status or runs /status.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /status — System Status Check
|
|
7
|
+
|
|
8
|
+
Generate a quick read-only status report of the current agent environment.
|
|
9
|
+
|
|
10
|
+
**Main-channel check:** Only the main channel has `/workspace/project` mounted. Run:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
test -d /workspace/project && echo "MAIN" || echo "NOT_MAIN"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
If `NOT_MAIN`, respond with:
|
|
17
|
+
> This command is available in your main chat only. Send `/status` there to check system status.
|
|
18
|
+
|
|
19
|
+
Then stop — do not generate the report.
|
|
20
|
+
|
|
21
|
+
## How to gather the information
|
|
22
|
+
|
|
23
|
+
Run the checks below and compile results into the report format.
|
|
24
|
+
|
|
25
|
+
### 1. Session context
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
echo "Timestamp: $(date)"
|
|
29
|
+
echo "Working dir: $(pwd)"
|
|
30
|
+
echo "Channel: main"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Workspace and mount visibility
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
echo "=== Workspace ==="
|
|
37
|
+
ls /workspace/ 2>/dev/null
|
|
38
|
+
echo "=== Group folder ==="
|
|
39
|
+
ls /workspace/group/ 2>/dev/null | head -20
|
|
40
|
+
echo "=== Extra mounts ==="
|
|
41
|
+
ls /workspace/extra/ 2>/dev/null || echo "none"
|
|
42
|
+
echo "=== IPC ==="
|
|
43
|
+
ls /workspace/ipc/ 2>/dev/null
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 3. Tool availability
|
|
47
|
+
|
|
48
|
+
Confirm which tool families are available to you:
|
|
49
|
+
|
|
50
|
+
- **Core:** Bash, Read, Write, Edit, Glob, Grep
|
|
51
|
+
- **Web:** WebSearch, WebFetch
|
|
52
|
+
- **Orchestration:** Task, TaskOutput, TaskStop, TeamCreate, TeamDelete, SendMessage
|
|
53
|
+
- **MCP:** mcp__nanoclaw__* (send_message, schedule_task, list_tasks, pause_task, resume_task, cancel_task, update_task, register_group)
|
|
54
|
+
|
|
55
|
+
### 4. Container utilities
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
which agent-browser 2>/dev/null && echo "agent-browser: available" || echo "agent-browser: not installed"
|
|
59
|
+
node --version 2>/dev/null
|
|
60
|
+
claude --version 2>/dev/null
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 5. Task snapshot
|
|
64
|
+
|
|
65
|
+
Use the MCP tool to list tasks:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
Call mcp__nanoclaw__list_tasks to get scheduled tasks.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
If no tasks exist, report "No scheduled tasks."
|
|
72
|
+
|
|
73
|
+
## Report format
|
|
74
|
+
|
|
75
|
+
Present as a clean, readable message:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
🔍 *NanoClaw Status*
|
|
79
|
+
|
|
80
|
+
*Session:*
|
|
81
|
+
• Channel: main
|
|
82
|
+
• Time: 2026-03-14 09:30 UTC
|
|
83
|
+
• Working dir: /workspace/group
|
|
84
|
+
|
|
85
|
+
*Workspace:*
|
|
86
|
+
• Group folder: ✓ (N files)
|
|
87
|
+
• Extra mounts: none / N directories
|
|
88
|
+
• IPC: ✓ (messages, tasks, input)
|
|
89
|
+
|
|
90
|
+
*Tools:*
|
|
91
|
+
• Core: ✓ Web: ✓ Orchestration: ✓ MCP: ✓
|
|
92
|
+
|
|
93
|
+
*Container:*
|
|
94
|
+
• agent-browser: ✓ / not installed
|
|
95
|
+
• Node: vXX.X.X
|
|
96
|
+
• Claude Code: vX.X.X
|
|
97
|
+
|
|
98
|
+
*Scheduled Tasks:*
|
|
99
|
+
• N active tasks / No scheduled tasks
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Adapt based on what you actually find. Keep it concise — this is a quick health check, not a deep diagnostic.
|
|
103
|
+
|
|
104
|
+
**See also:** `/capabilities` for a full list of installed skills and tools.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Standalone Elastic/Kibana log query tool for Argus.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python3 elastic_query.py --namespace it-ops
|
|
6
|
+
python3 elastic_query.py --namespace it-ops --container my-service
|
|
7
|
+
python3 elastic_query.py --namespace it-ops --from 2024-01-01T10:00:00Z --to 2024-01-01T11:00:00Z
|
|
8
|
+
python3 elastic_query.py # searches all namespaces with default lookback
|
|
9
|
+
|
|
10
|
+
Environment variables (required):
|
|
11
|
+
ELASTIC_BASE_URL e.g. https://kibana.yourcompany.com
|
|
12
|
+
ELASTIC_API_KEY Kibana API key
|
|
13
|
+
|
|
14
|
+
Optional environment variables:
|
|
15
|
+
ELASTIC_LOOKBACK_MINUTES default 60
|
|
16
|
+
ELASTIC_MAX_LOGS default 100
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
from datetime import datetime, timedelta, timezone
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
import requests
|
|
27
|
+
except ImportError:
|
|
28
|
+
print("ERROR: 'requests' is not installed. Run: pip3 install requests", file=sys.stderr)
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
|
|
31
|
+
KIBANA_PROXY_PATH = "/api/console/proxy"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parse_args():
|
|
35
|
+
p = argparse.ArgumentParser(description="Query Elastic error logs via Kibana proxy")
|
|
36
|
+
p.add_argument("--namespace", help="Kubernetes namespace (omit to search all)")
|
|
37
|
+
p.add_argument("--container", help="Kubernetes container name (optional filter)")
|
|
38
|
+
p.add_argument("--from", dest="from_ts", help="Start timestamp (ISO 8601 or 'now-Xm/h/d')")
|
|
39
|
+
p.add_argument("--to", dest="to_ts", help="End timestamp (ISO 8601 or 'now')")
|
|
40
|
+
p.add_argument("--lookback", type=int, help="Lookback minutes (overrides ELASTIC_LOOKBACK_MINUTES)")
|
|
41
|
+
p.add_argument("--max", type=int, help="Max log entries (overrides ELASTIC_MAX_LOGS)")
|
|
42
|
+
return p.parse_args()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def resolve_ts(ts: str) -> str:
|
|
46
|
+
"""Resolve 'now-Xm/h/d' and 'now' to ISO timestamps."""
|
|
47
|
+
if not ts.startswith("now"):
|
|
48
|
+
return ts
|
|
49
|
+
import re
|
|
50
|
+
m = re.match(r"now-(\d+)([mhd])$", ts)
|
|
51
|
+
if m:
|
|
52
|
+
amount, unit = int(m.group(1)), m.group(2)
|
|
53
|
+
delta = {"m": timedelta(minutes=amount), "h": timedelta(hours=amount), "d": timedelta(days=amount)}[unit]
|
|
54
|
+
return (datetime.now(timezone.utc) - delta).isoformat()
|
|
55
|
+
return datetime.now(timezone.utc).isoformat()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def query_logs(namespace, container, from_ts, to_ts, max_logs, base_url, api_key):
|
|
59
|
+
if from_ts and to_ts:
|
|
60
|
+
time_range = {"gte": from_ts, "lte": to_ts}
|
|
61
|
+
else:
|
|
62
|
+
now = datetime.now(timezone.utc)
|
|
63
|
+
lookback = int(os.environ.get("ELASTIC_LOOKBACK_MINUTES", "60"))
|
|
64
|
+
since = now - timedelta(minutes=lookback)
|
|
65
|
+
time_range = {"gte": since.isoformat(), "lte": now.isoformat()}
|
|
66
|
+
|
|
67
|
+
must = [{"range": {"@timestamp": time_range}}]
|
|
68
|
+
if namespace:
|
|
69
|
+
must.append({"match_phrase": {"kubernetes.namespace": namespace}})
|
|
70
|
+
if container:
|
|
71
|
+
must.append({"match_phrase": {"kubernetes.container.name": container}})
|
|
72
|
+
|
|
73
|
+
body = {
|
|
74
|
+
"size": max_logs,
|
|
75
|
+
"sort": [{"@timestamp": {"order": "desc"}}],
|
|
76
|
+
"query": {
|
|
77
|
+
"bool": {
|
|
78
|
+
"must": must,
|
|
79
|
+
"should": [
|
|
80
|
+
{"match_phrase": {"log.level": "error"}},
|
|
81
|
+
{"match_phrase": {"log.level": "ERROR"}},
|
|
82
|
+
{"match": {"message": "error"}},
|
|
83
|
+
{"match": {"message": "exception"}},
|
|
84
|
+
{"match": {"message": "traceback"}},
|
|
85
|
+
{"match": {"message": "Traceback"}},
|
|
86
|
+
],
|
|
87
|
+
"minimum_should_match": 1,
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"_source": [
|
|
91
|
+
"@timestamp",
|
|
92
|
+
"message",
|
|
93
|
+
"log.level",
|
|
94
|
+
"kubernetes.pod.name",
|
|
95
|
+
"kubernetes.container.name",
|
|
96
|
+
],
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
url = f"{base_url.rstrip('/')}{KIBANA_PROXY_PATH}"
|
|
100
|
+
params = {"path": "/filebeat-*/_search", "method": "GET"}
|
|
101
|
+
# Authorization header is optional here — if ELASTIC_API_KEY env var is set,
|
|
102
|
+
# use it directly. If absent, the OneCLI HTTPS proxy injects it automatically.
|
|
103
|
+
headers = {"kbn-xsrf": "true", "Content-Type": "application/json"}
|
|
104
|
+
if api_key:
|
|
105
|
+
headers["Authorization"] = f"ApiKey {api_key}"
|
|
106
|
+
|
|
107
|
+
resp = requests.post(url, params=params, headers=headers, json=body, timeout=30)
|
|
108
|
+
resp.raise_for_status()
|
|
109
|
+
hits = resp.json().get("hits", {}).get("hits", [])
|
|
110
|
+
return [h["_source"] for h in hits]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def format_logs(logs):
|
|
114
|
+
if not logs:
|
|
115
|
+
return "No error logs found in the specified time window."
|
|
116
|
+
lines = []
|
|
117
|
+
for log in logs:
|
|
118
|
+
ts = log.get("@timestamp", "?")
|
|
119
|
+
level = log.get("log.level") or (log.get("log") or {}).get("level", "?")
|
|
120
|
+
k8s = log.get("kubernetes") or {}
|
|
121
|
+
pod = (k8s.get("pod") or {}).get("name", "?")
|
|
122
|
+
ctr = (k8s.get("container") or {}).get("name", "?")
|
|
123
|
+
msg = log.get("message", "").strip()
|
|
124
|
+
lines.append(f"[{ts}] [{level}] [{ctr}/{pod}] {msg}")
|
|
125
|
+
return "\n".join(lines)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def main():
|
|
129
|
+
args = parse_args()
|
|
130
|
+
|
|
131
|
+
base_url = os.environ.get("ELASTIC_BASE_URL", "").strip()
|
|
132
|
+
if not base_url:
|
|
133
|
+
print("ERROR: ELASTIC_BASE_URL must be set in the environment.", file=sys.stderr)
|
|
134
|
+
sys.exit(1)
|
|
135
|
+
|
|
136
|
+
# api_key is optional — if absent, OneCLI HTTPS proxy injects the Authorization header.
|
|
137
|
+
api_key = os.environ.get("ELASTIC_API_KEY", "").strip()
|
|
138
|
+
|
|
139
|
+
from_ts = resolve_ts(args.from_ts) if args.from_ts else None
|
|
140
|
+
to_ts = resolve_ts(args.to_ts) if args.to_ts else None
|
|
141
|
+
max_logs = args.max or int(os.environ.get("ELASTIC_MAX_LOGS", "100"))
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
logs = query_logs(
|
|
145
|
+
namespace=args.namespace,
|
|
146
|
+
container=args.container,
|
|
147
|
+
from_ts=from_ts,
|
|
148
|
+
to_ts=to_ts,
|
|
149
|
+
max_logs=max_logs,
|
|
150
|
+
base_url=base_url,
|
|
151
|
+
api_key=api_key,
|
|
152
|
+
)
|
|
153
|
+
except Exception as e:
|
|
154
|
+
print(f"ERROR: Elastic query failed: {e}", file=sys.stderr)
|
|
155
|
+
sys.exit(1)
|
|
156
|
+
|
|
157
|
+
print(format_logs(logs))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if __name__ == "__main__":
|
|
161
|
+
main()
|