nextclaw-core 0.4.1 → 0.4.3
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/dist/agent/skills/README.md +24 -0
- package/dist/agent/skills/cron/SKILL.md +40 -0
- package/dist/agent/skills/github/SKILL.md +48 -0
- package/dist/agent/skills/skill-creator/SKILL.md +371 -0
- package/dist/agent/skills/summarize/SKILL.md +67 -0
- package/dist/agent/skills/tmux/SKILL.md +121 -0
- package/dist/agent/skills/tmux/scripts/find-sessions.sh +112 -0
- package/dist/agent/skills/tmux/scripts/wait-for-text.sh +83 -0
- package/dist/agent/skills/weather/SKILL.md +49 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +39 -6
- package/dist/skills/README.md +24 -0
- package/dist/skills/cron/SKILL.md +40 -0
- package/dist/skills/github/SKILL.md +48 -0
- package/dist/skills/skill-creator/SKILL.md +371 -0
- package/dist/skills/summarize/SKILL.md +67 -0
- package/dist/skills/tmux/SKILL.md +121 -0
- package/dist/skills/tmux/scripts/find-sessions.sh +112 -0
- package/dist/skills/tmux/scripts/wait-for-text.sh +83 -0
- package/dist/skills/weather/SKILL.md +49 -0
- package/package.json +2 -2
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'USAGE'
|
|
6
|
+
Usage: find-sessions.sh [-L socket-name|-S socket-path|-A] [-q pattern]
|
|
7
|
+
|
|
8
|
+
List tmux sessions on a socket (default tmux socket if none provided).
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
-L, --socket tmux socket name (passed to tmux -L)
|
|
12
|
+
-S, --socket-path tmux socket path (passed to tmux -S)
|
|
13
|
+
-A, --all scan all sockets under NEXTCLAW_TMUX_SOCKET_DIR
|
|
14
|
+
-q, --query case-insensitive substring to filter session names
|
|
15
|
+
-h, --help show this help
|
|
16
|
+
USAGE
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
socket_name=""
|
|
20
|
+
socket_path=""
|
|
21
|
+
query=""
|
|
22
|
+
scan_all=false
|
|
23
|
+
socket_dir="${NEXTCLAW_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/nextclaw-tmux-sockets}"
|
|
24
|
+
|
|
25
|
+
while [[ $# -gt 0 ]]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
-L|--socket) socket_name="${2-}"; shift 2 ;;
|
|
28
|
+
-S|--socket-path) socket_path="${2-}"; shift 2 ;;
|
|
29
|
+
-A|--all) scan_all=true; shift ;;
|
|
30
|
+
-q|--query) query="${2-}"; shift 2 ;;
|
|
31
|
+
-h|--help) usage; exit 0 ;;
|
|
32
|
+
*) echo "Unknown option: $1" >&2; usage; exit 1 ;;
|
|
33
|
+
esac
|
|
34
|
+
done
|
|
35
|
+
|
|
36
|
+
if [[ "$scan_all" == true && ( -n "$socket_name" || -n "$socket_path" ) ]]; then
|
|
37
|
+
echo "Cannot combine --all with -L or -S" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
if [[ -n "$socket_name" && -n "$socket_path" ]]; then
|
|
42
|
+
echo "Use either -L or -S, not both" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
47
|
+
echo "tmux not found in PATH" >&2
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
list_sessions() {
|
|
52
|
+
local label="$1"; shift
|
|
53
|
+
local tmux_cmd=(tmux "$@")
|
|
54
|
+
|
|
55
|
+
if ! sessions="$("${tmux_cmd[@]}" list-sessions -F '#{session_name}\t#{session_attached}\t#{session_created_string}' 2>/dev/null)"; then
|
|
56
|
+
echo "No tmux server found on $label" >&2
|
|
57
|
+
return 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
if [[ -n "$query" ]]; then
|
|
61
|
+
sessions="$(printf '%s\n' "$sessions" | grep -i -- "$query" || true)"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
if [[ -z "$sessions" ]]; then
|
|
65
|
+
echo "No sessions found on $label"
|
|
66
|
+
return 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
echo "Sessions on $label:"
|
|
70
|
+
printf '%s\n' "$sessions" | while IFS=$'\t' read -r name attached created; do
|
|
71
|
+
attached_label=$([[ "$attached" == "1" ]] && echo "attached" || echo "detached")
|
|
72
|
+
printf ' - %s (%s, started %s)\n' "$name" "$attached_label" "$created"
|
|
73
|
+
done
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if [[ "$scan_all" == true ]]; then
|
|
77
|
+
if [[ ! -d "$socket_dir" ]]; then
|
|
78
|
+
echo "Socket directory not found: $socket_dir" >&2
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
shopt -s nullglob
|
|
83
|
+
sockets=("$socket_dir"/*)
|
|
84
|
+
shopt -u nullglob
|
|
85
|
+
|
|
86
|
+
if [[ "${#sockets[@]}" -eq 0 ]]; then
|
|
87
|
+
echo "No sockets found under $socket_dir" >&2
|
|
88
|
+
exit 1
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
exit_code=0
|
|
92
|
+
for sock in "${sockets[@]}"; do
|
|
93
|
+
if [[ ! -S "$sock" ]]; then
|
|
94
|
+
continue
|
|
95
|
+
fi
|
|
96
|
+
list_sessions "socket path '$sock'" -S "$sock" || exit_code=$?
|
|
97
|
+
done
|
|
98
|
+
exit "$exit_code"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
tmux_cmd=(tmux)
|
|
102
|
+
socket_label="default socket"
|
|
103
|
+
|
|
104
|
+
if [[ -n "$socket_name" ]]; then
|
|
105
|
+
tmux_cmd+=(-L "$socket_name")
|
|
106
|
+
socket_label="socket name '$socket_name'"
|
|
107
|
+
elif [[ -n "$socket_path" ]]; then
|
|
108
|
+
tmux_cmd+=(-S "$socket_path")
|
|
109
|
+
socket_label="socket path '$socket_path'"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
list_sessions "$socket_label" "${tmux_cmd[@]:1}"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'USAGE'
|
|
6
|
+
Usage: wait-for-text.sh -t target -p pattern [options]
|
|
7
|
+
|
|
8
|
+
Poll a tmux pane for text and exit when found.
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
-t, --target tmux target (session:window.pane), required
|
|
12
|
+
-p, --pattern regex pattern to look for, required
|
|
13
|
+
-F, --fixed treat pattern as a fixed string (grep -F)
|
|
14
|
+
-T, --timeout seconds to wait (integer, default: 15)
|
|
15
|
+
-i, --interval poll interval in seconds (default: 0.5)
|
|
16
|
+
-l, --lines number of history lines to inspect (integer, default: 1000)
|
|
17
|
+
-h, --help show this help
|
|
18
|
+
USAGE
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
target=""
|
|
22
|
+
pattern=""
|
|
23
|
+
grep_flag="-E"
|
|
24
|
+
timeout=15
|
|
25
|
+
interval=0.5
|
|
26
|
+
lines=1000
|
|
27
|
+
|
|
28
|
+
while [[ $# -gt 0 ]]; do
|
|
29
|
+
case "$1" in
|
|
30
|
+
-t|--target) target="${2-}"; shift 2 ;;
|
|
31
|
+
-p|--pattern) pattern="${2-}"; shift 2 ;;
|
|
32
|
+
-F|--fixed) grep_flag="-F"; shift ;;
|
|
33
|
+
-T|--timeout) timeout="${2-}"; shift 2 ;;
|
|
34
|
+
-i|--interval) interval="${2-}"; shift 2 ;;
|
|
35
|
+
-l|--lines) lines="${2-}"; shift 2 ;;
|
|
36
|
+
-h|--help) usage; exit 0 ;;
|
|
37
|
+
*) echo "Unknown option: $1" >&2; usage; exit 1 ;;
|
|
38
|
+
esac
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
if [[ -z "$target" || -z "$pattern" ]]; then
|
|
42
|
+
echo "target and pattern are required" >&2
|
|
43
|
+
usage
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
if ! [[ "$timeout" =~ ^[0-9]+$ ]]; then
|
|
48
|
+
echo "timeout must be an integer number of seconds" >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
if ! [[ "$lines" =~ ^[0-9]+$ ]]; then
|
|
53
|
+
echo "lines must be an integer" >&2
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
58
|
+
echo "tmux not found in PATH" >&2
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# End time in epoch seconds (integer, good enough for polling)
|
|
63
|
+
start_epoch=$(date +%s)
|
|
64
|
+
deadline=$((start_epoch + timeout))
|
|
65
|
+
|
|
66
|
+
while true; do
|
|
67
|
+
# -J joins wrapped lines, -S uses negative index to read last N lines
|
|
68
|
+
pane_text="$(tmux capture-pane -p -J -t "$target" -S "-${lines}" 2>/dev/null || true)"
|
|
69
|
+
|
|
70
|
+
if printf '%s\n' "$pane_text" | grep $grep_flag -- "$pattern" >/dev/null 2>&1; then
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
now=$(date +%s)
|
|
75
|
+
if (( now >= deadline )); then
|
|
76
|
+
echo "Timed out after ${timeout}s waiting for pattern: $pattern" >&2
|
|
77
|
+
echo "Last ${lines} lines from $target:" >&2
|
|
78
|
+
printf '%s\n' "$pane_text" >&2
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
sleep "$interval"
|
|
83
|
+
done
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: weather
|
|
3
|
+
description: Get current weather and forecasts (no API key required).
|
|
4
|
+
homepage: https://wttr.in/:help
|
|
5
|
+
metadata: {"nextclaw":{"emoji":"🌤️","requires":{"bins":["curl"]}}}
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Weather
|
|
9
|
+
|
|
10
|
+
Two free services, no API keys needed.
|
|
11
|
+
|
|
12
|
+
## wttr.in (primary)
|
|
13
|
+
|
|
14
|
+
Quick one-liner:
|
|
15
|
+
```bash
|
|
16
|
+
curl -s "wttr.in/London?format=3"
|
|
17
|
+
# Output: London: ⛅️ +8°C
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Compact format:
|
|
21
|
+
```bash
|
|
22
|
+
curl -s "wttr.in/London?format=%l:+%c+%t+%h+%w"
|
|
23
|
+
# Output: London: ⛅️ +8°C 71% ↙5km/h
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Full forecast:
|
|
27
|
+
```bash
|
|
28
|
+
curl -s "wttr.in/London?T"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Format codes: `%c` condition · `%t` temp · `%h` humidity · `%w` wind · `%l` location · `%m` moon
|
|
32
|
+
|
|
33
|
+
Tips:
|
|
34
|
+
- URL-encode spaces: `wttr.in/New+York`
|
|
35
|
+
- Airport codes: `wttr.in/JFK`
|
|
36
|
+
- Units: `?m` (metric) `?u` (USCS)
|
|
37
|
+
- Today only: `?1` · Current only: `?0`
|
|
38
|
+
- PNG: `curl -s "wttr.in/Berlin.png" -o /tmp/weather.png`
|
|
39
|
+
|
|
40
|
+
## Open-Meteo (fallback, JSON)
|
|
41
|
+
|
|
42
|
+
Free, no key, good for programmatic use:
|
|
43
|
+
```bash
|
|
44
|
+
curl -s "https://api.open-meteo.com/v1/forecast?latitude=51.5&longitude=-0.12¤t_weather=true"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Find coordinates for a city, then query. Returns JSON with temp, windspeed, weathercode.
|
|
48
|
+
|
|
49
|
+
Docs: https://open-meteo.com/en/docs
|
package/dist/index.d.ts
CHANGED
|
@@ -148,7 +148,7 @@ declare class SessionManager {
|
|
|
148
148
|
getOrCreate(key: string): Session;
|
|
149
149
|
getIfExists(key: string): Session | null;
|
|
150
150
|
addMessage(session: Session, role: string, content: string, extra?: Record<string, unknown>): void;
|
|
151
|
-
getHistory(session: Session, maxMessages?: number): Array<Record<string,
|
|
151
|
+
getHistory(session: Session, maxMessages?: number): Array<Record<string, unknown>>;
|
|
152
152
|
clear(session: Session): void;
|
|
153
153
|
private load;
|
|
154
154
|
save(session: Session): void;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// src/agent/context.ts
|
|
2
2
|
import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
|
|
3
3
|
import { join as join3, extname } from "path";
|
|
4
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5
4
|
|
|
6
5
|
// src/agent/memory.ts
|
|
7
6
|
import { readFileSync, writeFileSync, existsSync as existsSync2, readdirSync } from "fs";
|
|
@@ -402,7 +401,7 @@ var ContextBuilder = class {
|
|
|
402
401
|
constructor(workspace, contextConfig) {
|
|
403
402
|
this.workspace = workspace;
|
|
404
403
|
this.memory = new MemoryStore(workspace);
|
|
405
|
-
this.skills = new SkillsLoader(workspace
|
|
404
|
+
this.skills = new SkillsLoader(workspace);
|
|
406
405
|
this.contextConfig = {
|
|
407
406
|
bootstrap: {
|
|
408
407
|
...DEFAULT_CONTEXT_CONFIG.bootstrap,
|
|
@@ -2297,7 +2296,25 @@ var SessionManager = class {
|
|
|
2297
2296
|
}
|
|
2298
2297
|
getHistory(session, maxMessages = 50) {
|
|
2299
2298
|
const recent = session.messages.length > maxMessages ? session.messages.slice(-maxMessages) : session.messages;
|
|
2300
|
-
return recent.map((msg) =>
|
|
2299
|
+
return recent.map((msg) => {
|
|
2300
|
+
const entry = {
|
|
2301
|
+
role: msg.role,
|
|
2302
|
+
content: msg.content
|
|
2303
|
+
};
|
|
2304
|
+
if (typeof msg.name === "string") {
|
|
2305
|
+
entry.name = msg.name;
|
|
2306
|
+
}
|
|
2307
|
+
if (typeof msg.tool_call_id === "string") {
|
|
2308
|
+
entry.tool_call_id = msg.tool_call_id;
|
|
2309
|
+
}
|
|
2310
|
+
if (Array.isArray(msg.tool_calls)) {
|
|
2311
|
+
entry.tool_calls = msg.tool_calls;
|
|
2312
|
+
}
|
|
2313
|
+
if (typeof msg.reasoning_content === "string" && msg.reasoning_content) {
|
|
2314
|
+
entry.reasoning_content = msg.reasoning_content;
|
|
2315
|
+
}
|
|
2316
|
+
return entry;
|
|
2317
|
+
});
|
|
2301
2318
|
}
|
|
2302
2319
|
clear(session) {
|
|
2303
2320
|
session.messages = [];
|
|
@@ -2537,6 +2554,7 @@ var AgentLoop = class {
|
|
|
2537
2554
|
chatId: msg.chatId,
|
|
2538
2555
|
sessionKey
|
|
2539
2556
|
});
|
|
2557
|
+
this.sessions.addMessage(session, "user", msg.content);
|
|
2540
2558
|
let iteration = 0;
|
|
2541
2559
|
let finalContent = null;
|
|
2542
2560
|
const maxIterations = this.options.maxIterations ?? 20;
|
|
@@ -2557,9 +2575,17 @@ var AgentLoop = class {
|
|
|
2557
2575
|
}
|
|
2558
2576
|
}));
|
|
2559
2577
|
this.context.addAssistantMessage(messages, response.content, toolCallDicts, response.reasoningContent ?? null);
|
|
2578
|
+
this.sessions.addMessage(session, "assistant", response.content ?? "", {
|
|
2579
|
+
tool_calls: toolCallDicts,
|
|
2580
|
+
reasoning_content: response.reasoningContent ?? null
|
|
2581
|
+
});
|
|
2560
2582
|
for (const call of response.toolCalls) {
|
|
2561
2583
|
const result = await this.tools.execute(call.name, call.arguments);
|
|
2562
2584
|
this.context.addToolResult(messages, call.id, call.name, result);
|
|
2585
|
+
this.sessions.addMessage(session, "tool", result, {
|
|
2586
|
+
tool_call_id: call.id,
|
|
2587
|
+
name: call.name
|
|
2588
|
+
});
|
|
2563
2589
|
}
|
|
2564
2590
|
} else {
|
|
2565
2591
|
finalContent = response.content;
|
|
@@ -2572,11 +2598,9 @@ var AgentLoop = class {
|
|
|
2572
2598
|
const { content: cleanedContent, replyTo } = parseReplyTags(finalContent, messageId);
|
|
2573
2599
|
finalContent = cleanedContent;
|
|
2574
2600
|
if (isSilentReplyText(finalContent, SILENT_REPLY_TOKEN)) {
|
|
2575
|
-
this.sessions.addMessage(session, "user", msg.content);
|
|
2576
2601
|
this.sessions.save(session);
|
|
2577
2602
|
return null;
|
|
2578
2603
|
}
|
|
2579
|
-
this.sessions.addMessage(session, "user", msg.content);
|
|
2580
2604
|
this.sessions.addMessage(session, "assistant", finalContent);
|
|
2581
2605
|
this.sessions.save(session);
|
|
2582
2606
|
return {
|
|
@@ -2611,6 +2635,7 @@ var AgentLoop = class {
|
|
|
2611
2635
|
chatId: originChatId,
|
|
2612
2636
|
sessionKey
|
|
2613
2637
|
});
|
|
2638
|
+
this.sessions.addMessage(session, "user", `[System: ${msg.senderId}] ${msg.content}`);
|
|
2614
2639
|
let iteration = 0;
|
|
2615
2640
|
let finalContent = null;
|
|
2616
2641
|
const maxIterations = this.options.maxIterations ?? 20;
|
|
@@ -2631,9 +2656,17 @@ var AgentLoop = class {
|
|
|
2631
2656
|
}
|
|
2632
2657
|
}));
|
|
2633
2658
|
this.context.addAssistantMessage(messages, response.content, toolCallDicts, response.reasoningContent ?? null);
|
|
2659
|
+
this.sessions.addMessage(session, "assistant", response.content ?? "", {
|
|
2660
|
+
tool_calls: toolCallDicts,
|
|
2661
|
+
reasoning_content: response.reasoningContent ?? null
|
|
2662
|
+
});
|
|
2634
2663
|
for (const call of response.toolCalls) {
|
|
2635
2664
|
const result = await this.tools.execute(call.name, call.arguments);
|
|
2636
2665
|
this.context.addToolResult(messages, call.id, call.name, result);
|
|
2666
|
+
this.sessions.addMessage(session, "tool", result, {
|
|
2667
|
+
tool_call_id: call.id,
|
|
2668
|
+
name: call.name
|
|
2669
|
+
});
|
|
2637
2670
|
}
|
|
2638
2671
|
} else {
|
|
2639
2672
|
finalContent = response.content;
|
|
@@ -2646,9 +2679,9 @@ var AgentLoop = class {
|
|
|
2646
2679
|
const { content: cleanedContent, replyTo } = parseReplyTags(finalContent, void 0);
|
|
2647
2680
|
finalContent = cleanedContent;
|
|
2648
2681
|
if (isSilentReplyText(finalContent, SILENT_REPLY_TOKEN)) {
|
|
2682
|
+
this.sessions.save(session);
|
|
2649
2683
|
return null;
|
|
2650
2684
|
}
|
|
2651
|
-
this.sessions.addMessage(session, "user", `[System: ${msg.senderId}] ${msg.content}`);
|
|
2652
2685
|
this.sessions.addMessage(session, "assistant", finalContent);
|
|
2653
2686
|
this.sessions.save(session);
|
|
2654
2687
|
return {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# nextclaw Skills
|
|
2
|
+
|
|
3
|
+
This directory contains built-in skills that extend nextclaw's capabilities.
|
|
4
|
+
|
|
5
|
+
## Skill Format
|
|
6
|
+
|
|
7
|
+
Each skill is a directory containing a `SKILL.md` file with:
|
|
8
|
+
- YAML frontmatter (name, description, metadata)
|
|
9
|
+
- Markdown instructions for the agent
|
|
10
|
+
|
|
11
|
+
## Attribution
|
|
12
|
+
|
|
13
|
+
These skills are adapted from [OpenClaw](https://github.com/openclaw/openclaw)'s skill system.
|
|
14
|
+
The skill format and metadata structure follow OpenClaw's conventions to maintain compatibility.
|
|
15
|
+
|
|
16
|
+
## Available Skills
|
|
17
|
+
|
|
18
|
+
| Skill | Description |
|
|
19
|
+
|-------|-------------|
|
|
20
|
+
| `github` | Interact with GitHub using the `gh` CLI |
|
|
21
|
+
| `weather` | Get weather info using wttr.in and Open-Meteo |
|
|
22
|
+
| `summarize` | Summarize URLs, files, and YouTube videos |
|
|
23
|
+
| `tmux` | Remote-control tmux sessions |
|
|
24
|
+
| `skill-creator` | Create new skills |
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cron
|
|
3
|
+
description: Schedule reminders and recurring tasks.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cron
|
|
7
|
+
|
|
8
|
+
Use the `cron` tool to schedule reminders or recurring tasks.
|
|
9
|
+
|
|
10
|
+
## Two Modes
|
|
11
|
+
|
|
12
|
+
1. **Reminder** - message is sent directly to user
|
|
13
|
+
2. **Task** - message is a task description, agent executes and sends result
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
Fixed reminder:
|
|
18
|
+
```
|
|
19
|
+
cron(action="add", message="Time to take a break!", every_seconds=1200)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Dynamic task (agent executes each time):
|
|
23
|
+
```
|
|
24
|
+
cron(action="add", message="Check Peiiii/nextclaw GitHub stars and report", every_seconds=600)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
List/remove:
|
|
28
|
+
```
|
|
29
|
+
cron(action="list")
|
|
30
|
+
cron(action="remove", job_id="abc123")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Time Expressions
|
|
34
|
+
|
|
35
|
+
| User says | Parameters |
|
|
36
|
+
|-----------|------------|
|
|
37
|
+
| every 20 minutes | every_seconds: 1200 |
|
|
38
|
+
| every hour | every_seconds: 3600 |
|
|
39
|
+
| every day at 8am | cron_expr: "0 8 * * *" |
|
|
40
|
+
| weekdays at 5pm | cron_expr: "0 17 * * 1-5" |
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: github
|
|
3
|
+
description: "Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries."
|
|
4
|
+
metadata: {"nextclaw":{"emoji":"🐙","requires":{"bins":["gh"]},"install":[{"id":"brew","kind":"brew","formula":"gh","bins":["gh"],"label":"Install GitHub CLI (brew)"},{"id":"apt","kind":"apt","package":"gh","bins":["gh"],"label":"Install GitHub CLI (apt)"}]}}
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# GitHub Skill
|
|
8
|
+
|
|
9
|
+
Use the `gh` CLI to interact with GitHub. Always specify `--repo owner/repo` when not in a git directory, or use URLs directly.
|
|
10
|
+
|
|
11
|
+
## Pull Requests
|
|
12
|
+
|
|
13
|
+
Check CI status on a PR:
|
|
14
|
+
```bash
|
|
15
|
+
gh pr checks 55 --repo owner/repo
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
List recent workflow runs:
|
|
19
|
+
```bash
|
|
20
|
+
gh run list --repo owner/repo --limit 10
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
View a run and see which steps failed:
|
|
24
|
+
```bash
|
|
25
|
+
gh run view <run-id> --repo owner/repo
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
View logs for failed steps only:
|
|
29
|
+
```bash
|
|
30
|
+
gh run view <run-id> --repo owner/repo --log-failed
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## API for Advanced Queries
|
|
34
|
+
|
|
35
|
+
The `gh api` command is useful for accessing data not available through other subcommands.
|
|
36
|
+
|
|
37
|
+
Get PR with specific fields:
|
|
38
|
+
```bash
|
|
39
|
+
gh api repos/owner/repo/pulls/55 --jq '.title, .state, .user.login'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## JSON Output
|
|
43
|
+
|
|
44
|
+
Most commands support `--json` for structured output. You can use `--jq` to filter:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
gh issue list --repo owner/repo --json number,title --jq '.[] | "\(.number): \(.title)"'
|
|
48
|
+
```
|