@jairodri/huckleberry-cli 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/agents/cos.json +20 -0
- package/agents/hermes/AGENTS.md +62 -0
- package/agents/hermes/SOUL.md +29 -0
- package/agents/hermes/fix-kimaki/SKILL.md +100 -0
- package/agents/hermes/hermes-fixer.md +115 -0
- package/agents/hermes.json +26 -0
- package/bin/huckleberry.js +4 -0
- package/kanban/SKILL.md +167 -0
- package/kanban/kanban +3 -0
- package/kanban/package.json +9 -0
- package/kanban/src/cli.mjs +553 -0
- package/kanban/src/dispatcher.mjs +479 -0
- package/kanban/src/server.mjs +108 -0
- package/kanban/templates/b2b-outreach.yaml +84 -0
- package/kanban/templates/blog-social-campaign.yaml +1161 -0
- package/kanban/templates/client-onboard.yaml +107 -0
- package/kanban/templates/cro-pipeline.yaml +296 -0
- package/kanban/templates/social-campaign.yaml +356 -0
- package/mcp/gemini-codegen-server.mjs +134 -0
- package/package.json +46 -0
- package/plugins/env-inject.ts +61 -0
- package/plugins/inkstone-gate.ts +55 -0
- package/scripts/heartbeat.sh +41 -0
- package/scripts/install-services.ps1 +82 -0
- package/scripts/install-services.sh +74 -0
- package/scripts/keep-awake.ps1 +48 -0
- package/scripts/keep-awake.sh +31 -0
- package/scripts/keepalive.sh +43 -0
- package/skills/curated.txt +68 -0
- package/src/cli.js +227 -0
- package/src/interview.js +78 -0
- package/src/postinstall.js +22 -0
- package/taskmaster/SKILL.md +78 -0
- package/taskmaster/dispatcher.mjs +146 -0
- package/taskmaster/next-task.js +123 -0
- package/taskmaster/update-status.js +118 -0
- package/templates/AGENTS.md +90 -0
- package/templates/COS_PROTOCOLS.md +71 -0
- package/templates/EXECUTIVE-MODE.md +29 -0
- package/templates/HEARTBEAT.md +42 -0
- package/templates/IDENTITY.md +22 -0
- package/templates/MEMORY.md +17 -0
- package/templates/ORG.md +33 -0
- package/templates/SOUL.md +43 -0
- package/templates/TOOLS.md +66 -0
- package/templates/business-context-gate.md +9 -0
- package/templates/evergreen.md +29 -0
- package/templates/opencode.json +80 -0
- package/wiki/discord.md +52 -0
- package/wiki/inkstone.md +64 -0
- package/wiki/kimaki.md +48 -0
- package/wiki/opencode.md +43 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Huckleberry Heartbeat — reads taskmaster and reports status
|
|
3
|
+
# Called by cron every 2-4 hours
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
HB_DIR="${HOME}/.huckleberry"
|
|
7
|
+
TASK_FILE="${HB_DIR}/taskmaster/tasks/tasks.json"
|
|
8
|
+
STATE_FILE="${HB_DIR}/heartbeat-state.json"
|
|
9
|
+
LOG="${HB_DIR}/heartbeat.log"
|
|
10
|
+
|
|
11
|
+
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG"; }
|
|
12
|
+
|
|
13
|
+
if [ ! -f "$TASK_FILE" ]; then
|
|
14
|
+
log "No task file found — first run or empty"
|
|
15
|
+
echo '{"last_report": null, "tasks_since_last": []}' > "$STATE_FILE"
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
log "=== Heartbeat ==="
|
|
20
|
+
|
|
21
|
+
# Read task statuses
|
|
22
|
+
COMPLETED=$(python3 -c "
|
|
23
|
+
import json
|
|
24
|
+
with open('$TASK_FILE') as f: d = json.load(f)
|
|
25
|
+
tasks = d.get('tasks', [])
|
|
26
|
+
done = [t for t in tasks if t.get('status') == 'done']
|
|
27
|
+
active = [t for t in tasks if t.get('status') == 'in-progress']
|
|
28
|
+
blocked = [t for t in tasks if t.get('status') == 'blocked']
|
|
29
|
+
print(f'{len(done)}|{len(active)}|{len(blocked)}')
|
|
30
|
+
" 2>/dev/null || echo "0|0|0")
|
|
31
|
+
|
|
32
|
+
echo "$COMPLETED"
|
|
33
|
+
log "Status: $COMPLETED tasks (done|active|blocked)"
|
|
34
|
+
|
|
35
|
+
# Save state for COS to read
|
|
36
|
+
echo "{
|
|
37
|
+
\"checked_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
|
|
38
|
+
\"task_summary\": \"$COMPLETED\"
|
|
39
|
+
}" > "$STATE_FILE"
|
|
40
|
+
|
|
41
|
+
log "=== Heartbeat complete ==="
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Huckleberry Windows Services Setup
|
|
2
|
+
# Creates Scheduled Tasks matching the 12 macOS launchd agents
|
|
3
|
+
# Run as Administrator: Right-click > Run as Administrator
|
|
4
|
+
|
|
5
|
+
$hbDir = "$env:USERPROFILE\.huckleberry"
|
|
6
|
+
$kimakiDir = "$env:USERPROFILE\.kimaki"
|
|
7
|
+
$logDir = "$env:USERPROFILE\.huckleberry\logs"
|
|
8
|
+
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
|
|
9
|
+
|
|
10
|
+
function New-Task {
|
|
11
|
+
param(
|
|
12
|
+
[string]$Name,
|
|
13
|
+
[string]$Command,
|
|
14
|
+
[string]$Args = "",
|
|
15
|
+
[string]$Trigger = "AtStartup",
|
|
16
|
+
[int]$IntervalSeconds = 0,
|
|
17
|
+
[string]$Description = ""
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
$taskName = "Huckleberry-$Name"
|
|
21
|
+
$action = New-ScheduledTaskAction -Execute $Command -Argument $Args
|
|
22
|
+
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
|
|
23
|
+
|
|
24
|
+
if ($Trigger -eq "AtStartup") {
|
|
25
|
+
$trigger = New-ScheduledTaskTrigger -AtStartup -RandomDelay (New-TimeSpan -Seconds 30)
|
|
26
|
+
} elseif ($Trigger -eq "Repetition") {
|
|
27
|
+
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Seconds $IntervalSeconds) -RepetitionDuration ([TimeSpan]::MaxValue)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
|
|
32
|
+
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Settings $settings -Description $Description -Force | Out-Null
|
|
33
|
+
Write-Host " [OK] $taskName" -ForegroundColor Green
|
|
34
|
+
} catch {
|
|
35
|
+
Write-Host " [WARN] $taskName — $_" -ForegroundColor Yellow
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Write-Host ""
|
|
40
|
+
Write-Host " Huckleberry — Setting up Windows services..." -ForegroundColor Cyan
|
|
41
|
+
Write-Host ""
|
|
42
|
+
|
|
43
|
+
# 1. kimaki bot (KeepAlive = always running)
|
|
44
|
+
New-Task -Name "kimaki-bot" -Command "powershell.exe" -Args "-WindowStyle Hidden -Command `"cd '$kimakiDir'; .\start-kimaki.sh`"" -Description "Huckleberry kimaki Discord bridge"
|
|
45
|
+
|
|
46
|
+
# 2. kimaki watchdog
|
|
47
|
+
New-Task -Name "kimaki-watchdog" -Command "node" -Args "$kimakiDir\watchdog\index.js" -Description "Huckleberry kimaki watchdog"
|
|
48
|
+
|
|
49
|
+
# 3. Health monitor (every 5 min)
|
|
50
|
+
New-Task -Name "kimaki-monitor" -Command "powershell.exe" -Args "-WindowStyle Hidden -File `"$hbDir\scripts\keepalive.ps1`"" -Trigger "Repetition" -IntervalSeconds 300 -Description "Huckleberry health monitor (every 5min)"
|
|
51
|
+
|
|
52
|
+
# 4. Kanban server
|
|
53
|
+
New-Task -Name "kanban-server" -Command "node" -Args "$hbDir\kanban\src\server.mjs" -Description "Huckleberry kanban API server"
|
|
54
|
+
|
|
55
|
+
# 5. Kanban dispatcher
|
|
56
|
+
New-Task -Name "kanban-dispatcher" -Command "node" -Args "$hbDir\kanban\src\dispatcher.mjs" -Description "Huckleberry kanban task dispatcher"
|
|
57
|
+
|
|
58
|
+
# 6. Kanban completion watchdog
|
|
59
|
+
New-Task -Name "kanban-completion-watchdog" -Command "node" -Args "$hbDir\kanban\src\completion-watchdog.mjs" -Description "Huckleberry kanban completion tracker"
|
|
60
|
+
|
|
61
|
+
# 7. Kanban stall detector
|
|
62
|
+
New-Task -Name "kanban-stall-detector" -Command "node" -Args "$hbDir\kanban\src\stall-detector.mjs" -Description "Huckleberry kanban stall detection"
|
|
63
|
+
|
|
64
|
+
# 8. Kanban tunnel (keepalive)
|
|
65
|
+
New-Task -Name "kanban-tunnel" -Command "powershell.exe" -Args "-WindowStyle Hidden -File `"$hbDir\scripts\keepalive.ps1`"" -Description "Huckleberry kanban tunnel keepalive"
|
|
66
|
+
|
|
67
|
+
# 9. Dream dispatcher (every 30 min)
|
|
68
|
+
New-Task -Name "dream-dispatcher" -Command "node" -Args "$hbDir\taskmaster\dispatcher.mjs" -Trigger "Repetition" -IntervalSeconds 1800 -Description "Huckleberry dream cycle dispatcher (every 30min)"
|
|
69
|
+
|
|
70
|
+
# 10. Dream distiller (hourly)
|
|
71
|
+
New-Task -Name "dream-distiller" -Command "powershell.exe" -Args "-WindowStyle Hidden -File `"$hbDir\scripts\heartbeat.ps1`"" -Trigger "Repetition" -IntervalSeconds 3600 -Description "Huckleberry memory distiller"
|
|
72
|
+
|
|
73
|
+
# 11. Keystone load balancer
|
|
74
|
+
New-Task -Name "keystone" -Command "node" -Args "$env:USERPROFILE\keystone\keystone.js" -Description "Huckleberry keystone load balancer"
|
|
75
|
+
|
|
76
|
+
# 12. Monitor and notify (every 5 min)
|
|
77
|
+
New-Task -Name "monitor-and-notify" -Command "powershell.exe" -Args "-WindowStyle Hidden -File `"$hbDir\scripts\heartbeat.ps1`"" -Trigger "Repetition" -IntervalSeconds 300 -Description "Huckleberry notifications"
|
|
78
|
+
|
|
79
|
+
Write-Host ""
|
|
80
|
+
Write-Host " Services configured. Start manually or reboot." -ForegroundColor Green
|
|
81
|
+
Write-Host " To remove: .\huckleberry\scripts\uninstall-services.ps1" -ForegroundColor DarkGray
|
|
82
|
+
Write-Host ""
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Huckleberry macOS Services Setup
|
|
3
|
+
# Installs launchd plist files matching the 12 agents
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
HB_DIR="$HOME/.huckleberry"
|
|
7
|
+
LAUNCH_DIR="$HOME/Library/LaunchAgents"
|
|
8
|
+
LOG_DIR="$HB_DIR/logs"
|
|
9
|
+
mkdir -p "$LAUNCH_DIR" "$LOG_DIR"
|
|
10
|
+
|
|
11
|
+
SERVICES=(
|
|
12
|
+
# Name, Label, Program, Args, Interval (0 = KeepAlive)
|
|
13
|
+
"kimaki-bot:com.huckleberry.kimaki.bot:/bin/bash:$HOME/.kimaki/start-kimaki.sh:0"
|
|
14
|
+
"kimaki-watchdog:com.huckleberry.kimaki.watchdog:/opt/homebrew/bin/node:$HOME/.kimaki/watchdog/index.js:0"
|
|
15
|
+
"kimaki-monitor:com.huckleberry.kimaki.monitor:/bin/bash:$HB_DIR/scripts/keepalive.sh:300"
|
|
16
|
+
"kanban-server:com.huckleberry.kanban.server:/opt/homebrew/bin/node:$HB_DIR/kanban/src/server.mjs:0"
|
|
17
|
+
"kanban-dispatcher:com.huckleberry.kanban.dispatcher:/opt/homebrew/bin/node:$HB_DIR/kanban/src/dispatcher.mjs:0"
|
|
18
|
+
"kanban-completion-watchdog:com.huckleberry.kanban.completion-watchdog:/opt/homebrew/bin/node:$HB_DIR/kanban/src/completion-watchdog.mjs:0"
|
|
19
|
+
"kanban-stall-detector:com.huckleberry.kanban.stall-detector:/opt/homebrew/bin/node:$HB_DIR/kanban/src/stall-detector.mjs:0"
|
|
20
|
+
"kanban-tunnel:com.huckleberry.kanban.tunnel:/bin/bash:$HB_DIR/scripts/keepalive.sh:0"
|
|
21
|
+
"dream-dispatcher:com.huckleberry.dream.dispatcher:/opt/homebrew/bin/node:$HB_DIR/taskmaster/dispatcher.mjs:1800"
|
|
22
|
+
"dream-distiller:com.huckleberry.dream.distiller:/bin/bash:$HB_DIR/scripts/heartbeat.sh:3600"
|
|
23
|
+
"keystone:com.huckleberry.keystone:/opt/homebrew/bin/node:$HOME/keystone/keystone.js:0"
|
|
24
|
+
"monitor-and-notify:com.huckleberry.monitor.notify:/bin/bash:$HB_DIR/scripts/heartbeat.sh:300"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
for service in "${SERVICES[@]}"; do
|
|
28
|
+
IFS=":" read -r name label prog args interval <<< "$service"
|
|
29
|
+
PLIST="$LAUNCH_DIR/$label.plist"
|
|
30
|
+
|
|
31
|
+
cat > "$PLIST" << PLISTEOF
|
|
32
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
33
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
34
|
+
<plist version="1.0">
|
|
35
|
+
<dict>
|
|
36
|
+
<key>Label</key>
|
|
37
|
+
<string>$label</string>
|
|
38
|
+
<key>ProgramArguments</key>
|
|
39
|
+
<array>
|
|
40
|
+
<string>$prog</string>
|
|
41
|
+
<string>$args</string>
|
|
42
|
+
</array>
|
|
43
|
+
<key>RunAtLoad</key>
|
|
44
|
+
<true/>
|
|
45
|
+
PLISTEOF
|
|
46
|
+
|
|
47
|
+
if [ "$interval" = "0" ]; then
|
|
48
|
+
# KeepAlive service (always running)
|
|
49
|
+
cat >> "$PLIST" << 'KEEPA'
|
|
50
|
+
<key>KeepAlive</key>
|
|
51
|
+
<true/>
|
|
52
|
+
KEEPA
|
|
53
|
+
else
|
|
54
|
+
# Periodic service
|
|
55
|
+
echo " <key>StartInterval</key>" >> "$PLIST"
|
|
56
|
+
echo " <integer>$interval</integer>" >> "$PLIST"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
cat >> "$PLIST" << 'ENDPLIST'
|
|
60
|
+
<key>StandardOutPath</key>
|
|
61
|
+
<string>'$LOG_DIR'/'$name'.log</string>
|
|
62
|
+
<key>StandardErrorPath</key>
|
|
63
|
+
<string>'$LOG_DIR'/'$name'.err</string>
|
|
64
|
+
</dict>
|
|
65
|
+
</plist>
|
|
66
|
+
ENDPLIST
|
|
67
|
+
|
|
68
|
+
# Load the plist
|
|
69
|
+
launchctl load "$PLIST" 2>/dev/null && echo " [OK] $label" || echo " [WARN] $label"
|
|
70
|
+
done
|
|
71
|
+
|
|
72
|
+
echo ""
|
|
73
|
+
echo " All services installed. Check with: launchctl list | grep huckleberry"
|
|
74
|
+
echo " Remove: launchctl unload ~/Library/LaunchAgents/com.huckleberry.*.plist"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Huckleberry Sleep Prevention
|
|
2
|
+
# Prevents Windows from sleeping while Huckleberry services are running
|
|
3
|
+
# Equivalent to macOS `caffeinate`
|
|
4
|
+
|
|
5
|
+
$scriptName = "huckleberry-keep-awake"
|
|
6
|
+
$logFile = "$env:USERPROFILE\.huckleberry\keep-awake.log"
|
|
7
|
+
|
|
8
|
+
function Log($msg) {
|
|
9
|
+
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
10
|
+
Add-Content -Path $logFile -Value "[$ts] $msg"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
Log "Keep-awake started"
|
|
14
|
+
|
|
15
|
+
# Method 1: Disable sleep via powercfg (temporary, needs admin)
|
|
16
|
+
try {
|
|
17
|
+
powercfg -change -standby-timeout-ac 0 2>&1 | Out-Null
|
|
18
|
+
powercfg -change -hibernate-timeout-ac 0 2>&1 | Out-Null
|
|
19
|
+
Log "Sleep disabled via powercfg"
|
|
20
|
+
} catch {
|
|
21
|
+
Log "powercfg failed (may need admin): $_"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Method 2: Keep system awake by setting thread execution state
|
|
25
|
+
# Run this in a loop to prevent sleep
|
|
26
|
+
while ($true) {
|
|
27
|
+
try {
|
|
28
|
+
# Use Add-Type to call kernel32 SetThreadExecutionState
|
|
29
|
+
Add-Type -TypeDefinition @"
|
|
30
|
+
using System;
|
|
31
|
+
using System.Runtime.InteropServices;
|
|
32
|
+
public class SleepPreventer {
|
|
33
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
34
|
+
public static extern uint SetThreadExecutionState(uint esFlags);
|
|
35
|
+
public static void PreventSleep() {
|
|
36
|
+
SetThreadExecutionState(0x80000003); // ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED
|
|
37
|
+
}
|
|
38
|
+
public static void AllowSleep() {
|
|
39
|
+
SetThreadExecutionState(0x80000000); // ES_CONTINUOUS
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
"@ -ErrorAction SilentlyContinue
|
|
43
|
+
[SleepPreventer]::PreventSleep()
|
|
44
|
+
} catch {
|
|
45
|
+
# Fallback: keep process alive
|
|
46
|
+
}
|
|
47
|
+
Start-Sleep -Seconds 60
|
|
48
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Huckleberry macOS Sleep Prevention
|
|
3
|
+
# Uses caffeinate to prevent sleep while services run
|
|
4
|
+
|
|
5
|
+
LOGFILE="$HOME/.huckleberry/keep-awake.log"
|
|
6
|
+
PIDFILE="$HOME/.huckleberry/keep-awake.pid"
|
|
7
|
+
|
|
8
|
+
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOGFILE"; }
|
|
9
|
+
|
|
10
|
+
case "${1:-start}" in
|
|
11
|
+
start)
|
|
12
|
+
log "Starting caffeinate..."
|
|
13
|
+
caffeinate -dimsu -w $$ &
|
|
14
|
+
CPID=$!
|
|
15
|
+
echo $CPID > "$PIDFILE"
|
|
16
|
+
log "caffeinate PID: $CPID"
|
|
17
|
+
;;
|
|
18
|
+
stop)
|
|
19
|
+
if [ -f "$PIDFILE" ]; then
|
|
20
|
+
kill $(cat "$PIDFILE") 2>/dev/null && log "caffeinate stopped"
|
|
21
|
+
rm -f "$PIDFILE"
|
|
22
|
+
fi
|
|
23
|
+
;;
|
|
24
|
+
status)
|
|
25
|
+
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
|
|
26
|
+
echo "Keep-awake is running"
|
|
27
|
+
else
|
|
28
|
+
echo "Keep-awake is stopped"
|
|
29
|
+
fi
|
|
30
|
+
;;
|
|
31
|
+
esac
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Huckleberry Keepalive — checks and restarts critical services
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
HB_DIR="${HOME}/.huckleberry"
|
|
6
|
+
LOG="${HB_DIR}/keepalive.log"
|
|
7
|
+
MAX_RESTARTS=3
|
|
8
|
+
RESTART_COUNT=0
|
|
9
|
+
|
|
10
|
+
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG"; }
|
|
11
|
+
|
|
12
|
+
check_service() {
|
|
13
|
+
local name=$1
|
|
14
|
+
local pid_pattern=$2
|
|
15
|
+
local restart_cmd=$3
|
|
16
|
+
|
|
17
|
+
if pgrep -f "$pid_pattern" > /dev/null 2>&1; then
|
|
18
|
+
log "$name: OK"
|
|
19
|
+
return 0
|
|
20
|
+
else
|
|
21
|
+
log "$name: DOWN — restarting..."
|
|
22
|
+
eval "$restart_cmd" &
|
|
23
|
+
log "$name: restart issued"
|
|
24
|
+
return 1
|
|
25
|
+
fi
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
log "=== Keepalive check ==="
|
|
29
|
+
|
|
30
|
+
# Check Inkstone MCP
|
|
31
|
+
check_service "inkstone" "inkstone.*mcp/server" "node ~/.huckleberry/mcp/inkstone/dist/mcp/server.js &"
|
|
32
|
+
|
|
33
|
+
# Check opencode server if running
|
|
34
|
+
if pgrep -f "opencode" > /dev/null 2>&1; then
|
|
35
|
+
log "opencode: OK"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Check kimaki
|
|
39
|
+
if pgrep -f "kimaki" > /dev/null 2>&1; then
|
|
40
|
+
log "kimaki: OK"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
log "=== Keepalive complete ==="
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Curated Skills for Huckleberry Starter Pack (~45)
|
|
2
|
+
|
|
3
|
+
# Core Infrastructure
|
|
4
|
+
claw-compactor
|
|
5
|
+
context-recovery
|
|
6
|
+
humanizer
|
|
7
|
+
guardrail-framework
|
|
8
|
+
self-improve
|
|
9
|
+
|
|
10
|
+
# Memory & Knowledge
|
|
11
|
+
inkstone-admin
|
|
12
|
+
project-rag
|
|
13
|
+
notebooklm
|
|
14
|
+
|
|
15
|
+
# Research & Intelligence
|
|
16
|
+
agent-reach
|
|
17
|
+
smart-search
|
|
18
|
+
firecrawl
|
|
19
|
+
perplexity-research
|
|
20
|
+
competitive-intel
|
|
21
|
+
|
|
22
|
+
# Content & Writing
|
|
23
|
+
copywriting
|
|
24
|
+
copy-editing
|
|
25
|
+
concreteness-fixer
|
|
26
|
+
article-generator
|
|
27
|
+
seo-content
|
|
28
|
+
narrative-research-article
|
|
29
|
+
hook-generator
|
|
30
|
+
content-strategy
|
|
31
|
+
|
|
32
|
+
# Brand & Voice
|
|
33
|
+
brand-voice
|
|
34
|
+
voice-extraction
|
|
35
|
+
voice-brand-alignment
|
|
36
|
+
positioning-angles
|
|
37
|
+
|
|
38
|
+
# Outreach & Sales
|
|
39
|
+
cold-email
|
|
40
|
+
lead-email-craft
|
|
41
|
+
warm-outbound
|
|
42
|
+
b2b-cold-outreach
|
|
43
|
+
|
|
44
|
+
# Social Distribution
|
|
45
|
+
social-construction
|
|
46
|
+
social-campaign
|
|
47
|
+
typefully
|
|
48
|
+
x-post-generator-openclaw
|
|
49
|
+
muse
|
|
50
|
+
platform-formatting
|
|
51
|
+
|
|
52
|
+
# Marketing & Growth
|
|
53
|
+
cmo
|
|
54
|
+
brainstorm
|
|
55
|
+
audience-research
|
|
56
|
+
landing-page-architect
|
|
57
|
+
content-repost-manager
|
|
58
|
+
|
|
59
|
+
# SEO & Analytics
|
|
60
|
+
keyword-research
|
|
61
|
+
ai-seo
|
|
62
|
+
seo-audit
|
|
63
|
+
schema-markup
|
|
64
|
+
auto-indexing
|
|
65
|
+
|
|
66
|
+
# Creative
|
|
67
|
+
image-gen
|
|
68
|
+
visual-style
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, copyFileSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import { runInterview } from "./interview.js";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const ROOT = join(__dirname, "..");
|
|
9
|
+
const PACKAGE_ROOT = ROOT;
|
|
10
|
+
|
|
11
|
+
const VERSION = JSON.parse(
|
|
12
|
+
readFileSync(join(PACKAGE_ROOT, "package.json"), "utf-8")
|
|
13
|
+
).version;
|
|
14
|
+
|
|
15
|
+
const commands = { install, update, "install-pack": installPack, interview: runInterview, help, version: () => console.log(`@huckleberry/cli v${VERSION}`) };
|
|
16
|
+
|
|
17
|
+
export async function run(args) {
|
|
18
|
+
const cmd = args[0];
|
|
19
|
+
if (!cmd || cmd === "--help" || cmd === "-h") return help();
|
|
20
|
+
if (cmd === "--version" || cmd === "-v") return console.log(`@huckleberry/cli v${VERSION}`);
|
|
21
|
+
const handler = commands[cmd];
|
|
22
|
+
if (!handler) {
|
|
23
|
+
console.error(`Unknown command: ${cmd}\nRun 'huckleberry help' for usage.`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
try { await handler(args.slice(1)); }
|
|
27
|
+
catch (err) { console.error(`[ERROR] ${err.message}`); process.exit(1); }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function install() {
|
|
31
|
+
console.log(`\n Huckleberry AI System v${VERSION} — Installing...`);
|
|
32
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
33
|
+
const hbDir = join(homeDir, ".huckleberry");
|
|
34
|
+
const templateDir = join(PACKAGE_ROOT, "templates");
|
|
35
|
+
|
|
36
|
+
console.log(` [1/8] Platform: ${detectPlatform()}`);
|
|
37
|
+
console.log(` [2/8] Priming OmniRoute with free models...`);
|
|
38
|
+
|
|
39
|
+
console.log(` [3/8] Creating bootstrap files...`);
|
|
40
|
+
mkdirSync(hbDir, { recursive: true });
|
|
41
|
+
mkdirSync(join(hbDir, "memory"), { recursive: true });
|
|
42
|
+
mkdirSync(join(hbDir, "taskmaster", "tasks"), { recursive: true });
|
|
43
|
+
mkdirSync(join(hbDir, "scripts"), { recursive: true });
|
|
44
|
+
mkdirSync(join(hbDir, "wiki"), { recursive: true });
|
|
45
|
+
mkdirSync(join(hbDir, "agents", "hermes"), { recursive: true });
|
|
46
|
+
mkdirSync(join(hbDir, "agents", "hermes", "fix-kimaki"), { recursive: true });
|
|
47
|
+
|
|
48
|
+
for (const t of ["SOUL.md","IDENTITY.md","COS_PROTOCOLS.md","TOOLS.md","HEARTBEAT.md","AGENTS.md","MEMORY.md","EXECUTIVE-MODE.md","ORG.md"]) {
|
|
49
|
+
const src = join(templateDir, t);
|
|
50
|
+
if (existsSync(src)) copyTemplate(src, join(hbDir, t));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Copy Hermes agent bootstrap files
|
|
54
|
+
const hermesSrc = join(PACKAGE_ROOT, "agents", "hermes");
|
|
55
|
+
if (existsSync(hermesSrc)) copyDirSync(hermesSrc, join(hbDir, "agents", "hermes"));
|
|
56
|
+
// Copy agent configs
|
|
57
|
+
if (existsSync(join(PACKAGE_ROOT, "agents", "cos.json"))) copyFileSync(join(PACKAGE_ROOT, "agents", "cos.json"), join(hbDir, "agents", "cos.json"));
|
|
58
|
+
if (existsSync(join(PACKAGE_ROOT, "agents", "hermes.json"))) copyFileSync(join(PACKAGE_ROOT, "agents", "hermes.json"), join(hbDir, "agents", "hermes.json"));
|
|
59
|
+
|
|
60
|
+
console.log(` [4/8] Setting up ~/.opencode/...`);
|
|
61
|
+
const opencodeDir = join(homeDir, ".opencode");
|
|
62
|
+
mkdirSync(opencodeDir, { recursive: true });
|
|
63
|
+
mkdirSync(join(opencodeDir, "plugin"), { recursive: true });
|
|
64
|
+
mkdirSync(join(opencodeDir, "mcp"), { recursive: true });
|
|
65
|
+
|
|
66
|
+
const ocConfig = join(templateDir, "opencode.json");
|
|
67
|
+
if (existsSync(ocConfig)) {
|
|
68
|
+
const config = readFileSync(ocConfig, "utf-8")
|
|
69
|
+
.replace(/\{\{USERPROFILE\}\}/g, homeDir.replace(/\\/g, "/"))
|
|
70
|
+
.replace(/\{\{HUCKLEBERRY_HOME\}\}/g, hbDir.replace(/\\/g, "/"));
|
|
71
|
+
writeFileSync(join(opencodeDir, "opencode.json"), config, "utf-8");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (existsSync(join(PACKAGE_ROOT, "plugins"))) copyDirSync(join(PACKAGE_ROOT, "plugins"), join(opencodeDir, "plugin"));
|
|
75
|
+
if (existsSync(join(PACKAGE_ROOT, "mcp"))) copyDirSync(join(PACKAGE_ROOT, "mcp"), join(opencodeDir, "mcp"));
|
|
76
|
+
|
|
77
|
+
console.log(` [5/8] Deploying skills...`);
|
|
78
|
+
const skillsDir = join(homeDir, ".claude", "skills");
|
|
79
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
80
|
+
const curated = join(PACKAGE_ROOT, "skills", "curated.txt");
|
|
81
|
+
if (existsSync(curated)) {
|
|
82
|
+
const list = readFileSync(curated, "utf-8").split("\n").filter(Boolean);
|
|
83
|
+
console.log(` Skills manifest: ${list.length} skills listed`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(` [6/8] Setting up taskmaster...`);
|
|
87
|
+
const taskmasterSrc = join(PACKAGE_ROOT, "taskmaster");
|
|
88
|
+
if (existsSync(taskmasterSrc)) copyDirSync(taskmasterSrc, join(hbDir, "taskmaster"));
|
|
89
|
+
writeFileSync(join(hbDir, "taskmaster", "tasks", "tasks.json"), JSON.stringify({ projects: [], tasks: [] }, null, 2), "utf-8");
|
|
90
|
+
|
|
91
|
+
console.log(` [7/10] Writing scripts...`);
|
|
92
|
+
if (existsSync(join(PACKAGE_ROOT, "scripts"))) copyDirSync(join(PACKAGE_ROOT, "scripts"), join(hbDir, "scripts"));
|
|
93
|
+
|
|
94
|
+
console.log(` [8/10] Writing wiki...`);
|
|
95
|
+
if (existsSync(join(PACKAGE_ROOT, "wiki"))) copyDirSync(join(PACKAGE_ROOT, "wiki"), join(hbDir, "wiki"));
|
|
96
|
+
|
|
97
|
+
console.log(` [9/10] Installing Inkstone memory system...`);
|
|
98
|
+
const inkstoneDir = join(hbDir, "mcp", "inkstone");
|
|
99
|
+
mkdirSync(inkstoneDir, { recursive: true });
|
|
100
|
+
// Create a standalone package.json that depends on @jairodri/huckleberry-inkstone
|
|
101
|
+
writeFileSync(
|
|
102
|
+
join(inkstoneDir, "package.json"),
|
|
103
|
+
JSON.stringify({
|
|
104
|
+
name: "inkstone",
|
|
105
|
+
private: true,
|
|
106
|
+
version: "1.0.0",
|
|
107
|
+
dependencies: { "@jairodri/huckleberry-inkstone": "^1.0.0" },
|
|
108
|
+
}, null, 2),
|
|
109
|
+
"utf-8"
|
|
110
|
+
);
|
|
111
|
+
try {
|
|
112
|
+
execSync("npm install --production 2>/dev/null", { cwd: inkstoneDir, stdio: "pipe", timeout: 120000 });
|
|
113
|
+
console.log(` Inkstone installed to ${inkstoneDir}`);
|
|
114
|
+
} catch {
|
|
115
|
+
console.error(` Inkstone npm install failed. Run manually: cd ${inkstoneDir} && npm install`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log(` [10/10] Setting up kanban workflows...`);
|
|
119
|
+
const kanbanDir = join(hbDir, "kanban");
|
|
120
|
+
mkdirSync(kanbanDir, { recursive: true });
|
|
121
|
+
mkdirSync(join(kanbanDir, "src"), { recursive: true });
|
|
122
|
+
mkdirSync(join(kanbanDir, "templates"), { recursive: true });
|
|
123
|
+
const kanbanSrc = join(PACKAGE_ROOT, "kanban");
|
|
124
|
+
if (existsSync(kanbanSrc)) copyDirSync(kanbanSrc, kanbanDir);
|
|
125
|
+
// Init kanban DB
|
|
126
|
+
try {
|
|
127
|
+
execSync("node src/cli.mjs init 2>/dev/null", { cwd: kanbanDir, stdio: "pipe", timeout: 30000 });
|
|
128
|
+
console.log(` Kanban workflows installed`);
|
|
129
|
+
} catch {
|
|
130
|
+
console.error(` Kanban init skipped (requires bcrypt-sqlite). Install: cd ${kanbanDir} && npm install`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log(` [11/12] Setting up background services...`);
|
|
134
|
+
const platform = detectPlatform();
|
|
135
|
+
const scriptsDir = join(hbDir, "scripts");
|
|
136
|
+
if (platform === "Windows" && existsSync(join(scriptsDir, "install-services.ps1"))) {
|
|
137
|
+
try {
|
|
138
|
+
execSync(`powershell -ExecutionPolicy Bypass -File "${join(scriptsDir, "install-services.ps1")}" 2>&1`, { stdio: "pipe", timeout: 60000 });
|
|
139
|
+
console.log(` 12 Windows Scheduled Tasks created`);
|
|
140
|
+
} catch {
|
|
141
|
+
console.error(` Windows services setup skipped (run manually: install-services.ps1)`);
|
|
142
|
+
}
|
|
143
|
+
} else if (platform === "macOS" && existsSync(join(scriptsDir, "install-services.sh"))) {
|
|
144
|
+
try {
|
|
145
|
+
execSync(`bash "${join(scriptsDir, "install-services.sh")}" 2>&1`, { stdio: "pipe", timeout: 30000 });
|
|
146
|
+
console.log(` 12 macOS launchd agents installed`);
|
|
147
|
+
} catch {
|
|
148
|
+
console.error(` macOS services setup skipped (run manually: install-services.sh)`);
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
console.log(` Service setup skipped for ${platform}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(` [12/12] Sleep prevention...`);
|
|
155
|
+
if (platform === "Windows") {
|
|
156
|
+
console.log(` Windows: Huckleberry prevents sleep while running (keep-awake.ps1)`);
|
|
157
|
+
console.log(` Disable sleep manually: powercfg -change -standby-timeout-ac 0`);
|
|
158
|
+
} else if (platform === "macOS") {
|
|
159
|
+
console.log(` macOS: caffeinate prevents sleep while running (keep-awake.sh)`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log(`\n ✓ Huckleberry v${VERSION} installed to ${hbDir}`);
|
|
163
|
+
console.log(` ✓ Config: ${join(opencodeDir, "opencode.json")}`);
|
|
164
|
+
console.log(`\n Next steps:`);
|
|
165
|
+
console.log(` 1. npm install -g opencode`);
|
|
166
|
+
console.log(` 2. npm install -g kimaki`);
|
|
167
|
+
console.log(` 3. huckleberry interview`);
|
|
168
|
+
console.log(` 4. cd ~/.huckleberry && opencode\n`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function update() {
|
|
172
|
+
console.log(`\n Updating Huckleberry...\n`);
|
|
173
|
+
try {
|
|
174
|
+
execSync("npm update -g @huckleberry/cli", { stdio: "inherit" });
|
|
175
|
+
console.log(` ✓ @huckleberry/cli updated`);
|
|
176
|
+
} catch {
|
|
177
|
+
console.error(` ⚠ @huckleberry/cli update failed`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Update Inkstone independently
|
|
181
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
182
|
+
const inkstoneDir = join(homeDir, ".huckleberry", "mcp", "inkstone");
|
|
183
|
+
if (existsSync(join(inkstoneDir, "package.json"))) {
|
|
184
|
+
console.log(` Updating Inkstone memory system...`);
|
|
185
|
+
try {
|
|
186
|
+
execSync("npm update @jairodri/huckleberry-inkstone 2>/dev/null", { cwd: inkstoneDir, stdio: "pipe", timeout: 60000 });
|
|
187
|
+
console.log(` ✓ Inkstone updated`);
|
|
188
|
+
} catch {
|
|
189
|
+
console.error(` ⚠ Inkstone update failed. Run: cd ${inkstoneDir} && npm install`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
console.log(`\n ✓ Huckleberry updated\n`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function installPack(args) {
|
|
197
|
+
const packName = args[0];
|
|
198
|
+
if (!packName) { console.error("Usage: huckleberry install-pack <pack-name>\nAvailable packs: content-creator, outreach, seo, creative, video"); process.exit(1); }
|
|
199
|
+
console.log(`\n Installing pack: ${packName}...\n`);
|
|
200
|
+
console.log(` Pack system coming soon — each pack includes: agent config + skills + kanban templates + channel setup\n`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function help() {
|
|
204
|
+
console.log(`\n Huckleberry AI System v${VERSION}\n\n Usage: huckleberry <command>\n\n Commands:\n install One-click install\n update Update to latest version\n install-pack Install agent/skill pack\n help Show this help\n version Show version\n\n Packs:\n content-creator Writing + social + content agent\n outreach Sales + cold email + lead management\n seo Keyword research + rankings\n creative Images + branding + design\n video Video production\n`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function detectPlatform() {
|
|
208
|
+
const p = process.platform;
|
|
209
|
+
return p === "darwin" ? "macOS" : p === "win32" ? "Windows" : p;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function copyTemplate(src, dst) {
|
|
213
|
+
try {
|
|
214
|
+
let content = readFileSync(src, "utf-8");
|
|
215
|
+
content = content.replace(/\{\{VERSION\}\}/g, VERSION).replace(/\{\{INSTALL_DATE\}\}/g, new Date().toISOString().split("T")[0]);
|
|
216
|
+
writeFileSync(dst, content, "utf-8");
|
|
217
|
+
} catch (err) { console.error(` Failed: ${src} — ${err.message}`); }
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function copyDirSync(src, dst) {
|
|
221
|
+
mkdirSync(dst, { recursive: true });
|
|
222
|
+
for (const entry of readdirSync(src)) {
|
|
223
|
+
if (entry === ".git" || entry === "node_modules") continue;
|
|
224
|
+
const s = join(src, entry), d = join(dst, entry);
|
|
225
|
+
if (statSync(s).isDirectory()) copyDirSync(s, d); else copyFileSync(s, d);
|
|
226
|
+
}
|
|
227
|
+
}
|