add alias to avoid explicit python commands, scheduled runs and bug fixes
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Register a Windows scheduled task that runs `ai-chat-exporter sync` daily.
|
||||
|
||||
.DESCRIPTION
|
||||
The Windows counterpart to install-systemd-timer.sh. Creates a per-user task
|
||||
(no admin rights needed) that runs the exporter from this repository, with
|
||||
the working directory set to the repo so .env, cache\ and exports\ resolve
|
||||
exactly as they do for an interactive run.
|
||||
|
||||
-Provider is repeatable. The CLI takes one provider per run, so each becomes
|
||||
its own action, executed in order. List only the providers that work
|
||||
unattended on this machine: a web provider whose session token has expired
|
||||
fails the task every day, which trains you to ignore the failures you
|
||||
actually want to notice.
|
||||
|
||||
.EXAMPLE
|
||||
.\scheduling\Register-AiChatSyncTask.ps1 -Provider chatgpt,claude
|
||||
|
||||
.EXAMPLE
|
||||
.\scheduling\Register-AiChatSyncTask.ps1 -Provider all -Time 21:30
|
||||
|
||||
.EXAMPLE
|
||||
.\scheduling\Register-AiChatSyncTask.ps1 -Unregister
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string[]]$Provider = @('all'),
|
||||
[string]$Time = '09:00',
|
||||
[string]$TaskName = 'AiChatExporterSync',
|
||||
[switch]$Unregister
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$repo = Split-Path -Parent $PSScriptRoot
|
||||
$launcher = Join-Path $repo 'ai-chat-exporter.cmd'
|
||||
|
||||
if ($Unregister) {
|
||||
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue
|
||||
Write-Host "Removed scheduled task '$TaskName'."
|
||||
return
|
||||
}
|
||||
|
||||
if (-not (Test-Path $launcher)) {
|
||||
throw "Launcher not found at $launcher"
|
||||
}
|
||||
|
||||
# A single action looping over the providers, rather than one action each.
|
||||
# Task Scheduler runs multiple actions in order but reports only the last one's
|
||||
# result, so a failure in an earlier provider would be invisible. The loop keeps
|
||||
# going after a failure and propagates a non-zero exit code.
|
||||
$loop = ($Provider | ForEach-Object { "`"$launcher`" sync --provider $_ --joplin-optional || set RC=1" }) -join ' & '
|
||||
$actions = New-ScheduledTaskAction -Execute 'cmd.exe' `
|
||||
-Argument "/c set RC=0 & $loop & exit /b %RC%" `
|
||||
-WorkingDirectory $repo
|
||||
|
||||
$trigger = New-ScheduledTaskTrigger -Daily -At $Time
|
||||
|
||||
# StartWhenAvailable is the counterpart of systemd's Persistent=true: a machine
|
||||
# that was asleep at the scheduled time runs the archive when it wakes, rather
|
||||
# than skipping the day entirely.
|
||||
$settings = New-ScheduledTaskSettingsSet `
|
||||
-StartWhenAvailable `
|
||||
-DontStopIfGoingOnBatteries `
|
||||
-AllowStartIfOnBatteries `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Hours 2)
|
||||
|
||||
Register-ScheduledTask -TaskName $TaskName `
|
||||
-Action $actions `
|
||||
-Trigger $trigger `
|
||||
-Settings $settings `
|
||||
-Description 'Export AI chat history and sync it to Joplin' `
|
||||
-Force | Out-Null
|
||||
|
||||
Write-Host "Registered '$TaskName' - daily at $Time for: $($Provider -join ', ')"
|
||||
Write-Host ''
|
||||
Write-Host 'Next steps:'
|
||||
Write-Host " * Run it once now: Start-ScheduledTask -TaskName $TaskName"
|
||||
Write-Host " * Check the result: Get-ScheduledTaskInfo -TaskName $TaskName"
|
||||
Write-Host " * Read the log: Get-Content '$repo\cache\logs\exporter.log' -Tail 50"
|
||||
Write-Host ' * The terms-of-service notice must have been acknowledged'
|
||||
Write-Host ' interactively once on this machine, or the task exits 1.'
|
||||
Executable
+109
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install a systemd *user* timer that runs `ai-chat-exporter sync` on a schedule.
|
||||
#
|
||||
# ./scheduling/install-systemd-timer.sh --provider claude-code --provider codex
|
||||
# ./scheduling/install-systemd-timer.sh --provider all --time 21:30
|
||||
# ./scheduling/install-systemd-timer.sh --uninstall
|
||||
#
|
||||
# User units (not system units) are the right scope: the archive is per-user,
|
||||
# the .env holds that user's session tokens, and Joplin runs in their session.
|
||||
#
|
||||
# --provider is repeatable. The CLI takes one provider per run, so each one
|
||||
# becomes its own ExecStart line, executed in order. Prefer listing only the
|
||||
# providers that actually work unattended on this machine — a web provider with
|
||||
# an expired session token will fail the unit every single day, which trains you
|
||||
# to ignore the failure you actually want to see.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
UNIT_DIR="$HOME/.config/systemd/user"
|
||||
NAME="aichat-sync"
|
||||
TIME="09:00"
|
||||
PROVIDERS=()
|
||||
UNINSTALL=0
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--provider) PROVIDERS+=("$2"); shift 2 ;;
|
||||
--time) TIME="$2"; shift 2 ;;
|
||||
--name) NAME="$2"; shift 2 ;;
|
||||
--uninstall) UNINSTALL=1; shift ;;
|
||||
-h|--help) sed -n '2,20p' "${BASH_SOURCE[0]}"; exit 0 ;;
|
||||
*) echo "unknown argument: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$UNINSTALL" -eq 1 ]; then
|
||||
systemctl --user disable --now "$NAME.timer" 2>/dev/null || true
|
||||
rm -f "$UNIT_DIR/$NAME.timer" "$UNIT_DIR/$NAME.service"
|
||||
systemctl --user daemon-reload
|
||||
echo "Removed $NAME.timer and $NAME.service."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[ ${#PROVIDERS[@]} -eq 0 ] && PROVIDERS=("all")
|
||||
|
||||
if [ ! -x "$REPO/ai-chat-exporter" ]; then
|
||||
echo "error: $REPO/ai-chat-exporter is missing or not executable." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$UNIT_DIR"
|
||||
|
||||
# WorkingDirectory is the point of the whole unit: .env, cache/ and exports/ all
|
||||
# resolve against it, so the scheduled run writes to the same archive an
|
||||
# interactive run from this directory would.
|
||||
{
|
||||
echo "[Unit]"
|
||||
echo "Description=AI chat archive sync"
|
||||
echo "Documentation=file://$REPO/README.md"
|
||||
echo "After=network-online.target"
|
||||
echo "Wants=network-online.target"
|
||||
echo
|
||||
echo "[Service]"
|
||||
echo "Type=oneshot"
|
||||
echo "WorkingDirectory=$REPO"
|
||||
echo "Environment=AI_CHAT_EXPORTER_QUIET_CWD=1"
|
||||
# One ExecStart per provider would stop at the first failure, silently
|
||||
# skipping the rest — an expired ChatGPT token would mean codex never runs.
|
||||
# Loop instead, so every provider is attempted and the unit still reports
|
||||
# failure if any of them failed.
|
||||
printf 'ExecStart=/bin/sh -c '\''rc=0; for p in %s; do "$0" sync --provider "$p" --joplin-optional || rc=1; done; exit $rc'\'' %s\n' \
|
||||
"${PROVIDERS[*]}" "$REPO/ai-chat-exporter"
|
||||
} > "$UNIT_DIR/$NAME.service"
|
||||
|
||||
# Persistent=true runs a missed schedule at the next boot — the machine being
|
||||
# off at 09:00 should delay the archive, not skip it. RandomizedDelaySec keeps
|
||||
# the web providers from being hit at exactly the same second every day.
|
||||
cat > "$UNIT_DIR/$NAME.timer" <<EOF
|
||||
[Unit]
|
||||
Description=Run the AI chat archive sync daily
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* $TIME:00
|
||||
Persistent=true
|
||||
RandomizedDelaySec=300
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
EOF
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now "$NAME.timer"
|
||||
|
||||
echo "Installed $NAME.timer — daily at $TIME for: ${PROVIDERS[*]}"
|
||||
echo
|
||||
systemctl --user list-timers "$NAME.timer" --no-pager || true
|
||||
cat <<EOF
|
||||
|
||||
Next steps:
|
||||
• Timers only run while you have a session. To archive when logged out:
|
||||
loginctl enable-linger $USER
|
||||
• Run it once by hand to confirm:
|
||||
systemctl --user start $NAME.service
|
||||
• Read the log:
|
||||
journalctl --user -u $NAME.service -n 50
|
||||
• The terms-of-service notice must have been acknowledged interactively at
|
||||
least once on this machine, or the unit exits 1 with an explanation.
|
||||
EOF
|
||||
Reference in New Issue
Block a user