spinuptui 1.3.0-alpha.1 → 1.3.0-alpha.2
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/CHANGELOG.md +13 -1
- package/README.md +6 -1
- package/package.json +1 -1
- package/src/config.ts +22 -0
- package/src/index.tsx +18 -39
- package/src/lib/cliScheduler.ts +534 -0
- package/src/lib/flock.ts +51 -0
- package/src/lib/schedule.ts +156 -0
- package/src/lib/scheduler.test.ts +353 -0
- package/src/lib/schedulerGit.ts +119 -0
- package/src/lib/schedulerInstall.ts +325 -0
- package/src/lib/schedulerJobs.ts +288 -0
- package/src/lib/schedulerRun.ts +246 -0
- package/src/lib/schedulerTick.ts +307 -0
- package/src/modules/beacon.ts +2 -1
- package/src/modules/contract.ts +22 -1
- package/src/modules/index.ts +2 -7
- package/src/modules/scheduler.ts +146 -0
- package/src/ui/App.tsx +5 -1
- package/src/ui/main.tsx +42 -0
- package/src/ui/store.tsx +6 -0
- package/src/ui/views/Modules.tsx +41 -5
- package/src/ui/views/SchedulerInstall.tsx +172 -0
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,17 @@ version; such changes are called out here.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [1.3.0-alpha.2] - 2026-10-26
|
|
15
|
+
|
|
16
|
+
A pre-release, published on npm's `next` tag (`bun add -g spinuptui@next --no-cache`). `@latest` and Homebrew stay on 1.2.0.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **Scheduler: follow-ups that run themselves.** Queue a job for later (`spinuptui scheduler add verify-410s --kind spinuptui --when "2026-10-21 08:00" --expires 2026-10-28 --created-by "…" -- incidents example.com`) and this machine runs it at that time, writes a report, and sends you the result through Beacon. Jobs can be a SpinupTUI command, a shell script, or a prompt for an agent CLI (Claude Code or Codex, read-only by default). They can be one-offs or recurring (`every 30m`, `weekdays 08:00`, `mon,thu 08:00`), and every job carries an expiry date so a forgotten one stops on its own. Jobs and reports live in `~/SpinupTUI/scheduler/`, a visible folder of your own. Make `~/SpinupTUI/` a git repository and Scheduler pulls it before each run and commits and pushes the reports, so you can queue a job from any machine with a commit. Only one machine runs a repository's jobs: the first station to tick it claims it, so a second machine with a dispatcher can't run them twice. `scheduler source claim` and `release` move them between machines. A job can say what happens when the station reaches it late (`--late run`, `skip`, or a grace window like `2h`). A skipped job is paged, not dropped silently. `scheduler list`, `show`, `run`, `cancel` and `validate` round it out. See [docs/scheduler.md](docs/scheduler.md).
|
|
20
|
+
- **`spinuptui scheduler install --plan|--apply`** sets up the one LaunchAgent everything runs on: a tick at login and every five minutes, with the PATH a bare launchd environment lacks written into it. `--apply` doesn't report success until a tick has actually run under launchd. `uninstall` removes it and keeps your jobs and reports. Scheduler shows up live on the Modules tab (`6`), where `⏎` shows the same plan and installs it on confirm, and in `spinuptui modules`, which reports how long ago the last tick ran. Modules will declare their own recurring work for Scheduler to run, so a station needs only this one unit. The design is in [docs/2026-10-20_scheduler-spec.md](docs/2026-10-20_scheduler-spec.md).
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- **Headless commands start about three times faster.** `beacon send`, `incidents`, `ssh-exec`, `scheduler` and the rest no longer load the dashboard's renderer before looking at their arguments, which matters for something started every five minutes.
|
|
24
|
+
|
|
14
25
|
## [1.3.0-alpha.1] - 2026-10-21
|
|
15
26
|
|
|
16
27
|
A pre-release, published on npm's `next` tag (`bun add -g spinuptui@next --no-cache`). `@latest` and Homebrew stay on 1.2.0. It continues `1.2.0-alpha.1` under a new number, because stable 1.2.0 shipped without the modules: it has Beacon and the Modules tab (see [1.2.0-alpha.1]) plus everything in [1.2.0]. The stable 1.3.0 will roll these notes up with the next modules.
|
|
@@ -1506,7 +1517,8 @@ Initial tagged release.
|
|
|
1506
1517
|
### Notes
|
|
1507
1518
|
- Read-only release: works with a SpinupWP **Read Only** API token.
|
|
1508
1519
|
|
|
1509
|
-
[Unreleased]: https://github.com/mwender/spinupwp-tui/compare/v1.3.0-alpha.
|
|
1520
|
+
[Unreleased]: https://github.com/mwender/spinupwp-tui/compare/v1.3.0-alpha.2...HEAD
|
|
1521
|
+
[1.3.0-alpha.2]: https://github.com/mwender/spinupwp-tui/compare/v1.3.0-alpha.1...v1.3.0-alpha.2
|
|
1510
1522
|
[1.3.0-alpha.1]: https://github.com/mwender/spinupwp-tui/compare/v1.2.0...v1.3.0-alpha.1
|
|
1511
1523
|
[1.2.0]: https://github.com/mwender/spinupwp-tui/compare/v1.1.2...v1.2.0
|
|
1512
1524
|
[1.2.0-alpha.1]: https://github.com/mwender/spinupwp-tui/compare/v1.1.2...v1.2.0-alpha.1
|
package/README.md
CHANGED
|
@@ -82,6 +82,11 @@
|
|
|
82
82
|
pages you on Telegram from any cron job, watcher or agent, with retries, long
|
|
83
83
|
messages split instead of dropped, and a send log. A guided setup creates the
|
|
84
84
|
bot connection for you. → [docs/beacon.md](docs/beacon.md)
|
|
85
|
+
Then **Scheduler**: follow-ups that run themselves. Queue "check this
|
|
86
|
+
tomorrow at 8" from any session, and the result arrives through Beacon. Jobs
|
|
87
|
+
and reports live in `~/SpinupTUI/scheduler/`; make that folder a git
|
|
88
|
+
repository to queue from any machine. One LaunchAgent ticks every
|
|
89
|
+
5 minutes and is the clock the other modules run on. → [docs/scheduler.md](docs/scheduler.md)
|
|
85
90
|
- **Non-interactive CLI subcommands for external tooling** — `spinuptui ssh <domain>`
|
|
86
91
|
resolves a domain to a live SSH target with a live connectivity probe;
|
|
87
92
|
`spinuptui ssh-exec <domain> -- <command>` runs a command over SSH, but only
|
|
@@ -375,7 +380,7 @@ These can be set in `config.json` or via an environment variable:
|
|
|
375
380
|
| `w` | Open the selected server/site in the SpinupWP web app |
|
|
376
381
|
| `/` | Jump to global search |
|
|
377
382
|
| `A` | Accounts — switch, add, rename, or remove SpinupWP accounts (from anywhere) |
|
|
378
|
-
| `⏎` / `s` | Set up the selected module — Beacon opens its guided Telegram setup (Modules tab) |
|
|
383
|
+
| `⏎` / `s` | Set up the selected module — Beacon opens its guided Telegram setup, Scheduler its install plan (Modules tab) |
|
|
379
384
|
| `t` | Send a test page through Beacon (Modules tab, once Beacon is set up) |
|
|
380
385
|
| `r` | Refresh data from the API |
|
|
381
386
|
| `i` | Explain the current screen (what each pane and key does) |
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -109,6 +109,12 @@ export interface AppConfig {
|
|
|
109
109
|
// it relies on the file's 600 mode. The env channel (SPINUPTUI_BEACON_*) is
|
|
110
110
|
// resolved in lib/beacon.ts, not here, so this is only what's stored.
|
|
111
111
|
beacon: BeaconStored | null
|
|
112
|
+
// Scheduler's sources and installed dispatcher (lib/scheduler*.ts,
|
|
113
|
+
// docs/2026-10-20_scheduler-spec.md). Machine-level: a station runs one clock.
|
|
114
|
+
scheduler: SchedulerStored | null
|
|
115
|
+
// Where modules put the user's work (Scheduler's jobs and reports…); null =
|
|
116
|
+
// the default, ~/SpinupTUI. SPINUPTUI_HOME overrides. See modules/contract.ts.
|
|
117
|
+
homeDir: string | null
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
export interface BeaconTelegramChannel {
|
|
@@ -126,6 +132,18 @@ export interface BeaconStored {
|
|
|
126
132
|
channels?: Record<string, BeaconChannel>
|
|
127
133
|
}
|
|
128
134
|
|
|
135
|
+
export interface SchedulerStored {
|
|
136
|
+
// Named job sources beyond the built-in `local` one: directories holding
|
|
137
|
+
// jobs/ and runs/, pulled and pushed when they are git repositories.
|
|
138
|
+
sources?: Record<string, { path: string }>
|
|
139
|
+
defaultSource?: string
|
|
140
|
+
// Git sources this machine only queues to: it never claims or runs them
|
|
141
|
+
// (set by `scheduler source release`, cleared by `claim`).
|
|
142
|
+
queueOnly?: string[]
|
|
143
|
+
// Written by `scheduler install --apply`, cleared by uninstall.
|
|
144
|
+
dispatcher?: { label: string; unitPath: string; bin: string; installedAt: string }
|
|
145
|
+
}
|
|
146
|
+
|
|
129
147
|
export interface ServerProviderRef {
|
|
130
148
|
id: number
|
|
131
149
|
databaseProviderId?: number
|
|
@@ -224,6 +242,8 @@ export interface StoredConfig {
|
|
|
224
242
|
kumaMonitors?: Record<string, KumaMonitorRef>
|
|
225
243
|
lastSeenVersion?: string
|
|
226
244
|
beacon?: BeaconStored
|
|
245
|
+
scheduler?: SchedulerStored
|
|
246
|
+
homeDir?: string
|
|
227
247
|
}
|
|
228
248
|
|
|
229
249
|
// ---- Accounts (profiles) -------------------------------------------------------
|
|
@@ -510,6 +530,8 @@ export function loadConfig(): AppConfig {
|
|
|
510
530
|
kumaMonitors: stored.kumaMonitors ?? {},
|
|
511
531
|
lastSeenVersion: stored.lastSeenVersion?.trim() || null,
|
|
512
532
|
beacon: stored.beacon ?? null,
|
|
533
|
+
scheduler: stored.scheduler ?? null,
|
|
534
|
+
homeDir: stored.homeDir?.trim() || null,
|
|
513
535
|
}
|
|
514
536
|
}
|
|
515
537
|
|
package/src/index.tsx
CHANGED
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
// Entry point. Handles
|
|
3
|
-
//
|
|
4
|
-
// yet) or the main app wrapped in the data store.
|
|
2
|
+
// Entry point. Handles the non-interactive CLI subcommands, then hands off to
|
|
3
|
+
// the dashboard (ui/main.tsx).
|
|
5
4
|
|
|
6
5
|
// First, before any config read or spawn: keep a site checkout's .env from
|
|
7
6
|
// leaking into child processes (see the module).
|
|
8
7
|
import "./lib/cwdDotenv.ts"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
// Nothing UI-related is imported statically: headless commands (the Scheduler
|
|
9
|
+
// tick every 5 minutes, beacon send, incidents…) never load the renderer or
|
|
10
|
+
// React. The dashboard is a dynamic import at the bottom.
|
|
12
11
|
import pkg from "../package.json" with { type: "json" }
|
|
13
|
-
import {
|
|
14
|
-
import { StoreProvider } from "./ui/store.tsx"
|
|
15
|
-
import { App } from "./ui/App.tsx"
|
|
16
|
-
import { AccountsContext, type AccountsApi } from "./ui/accounts.tsx"
|
|
17
|
-
import { resetWpCoreJobs } from "./ui/wpCoreJobs.tsx"
|
|
18
|
-
import { Onboarding } from "./ui/Onboarding.tsx"
|
|
19
|
-
import { isDevMode } from "./dev/devMode.ts"
|
|
12
|
+
import { configPath, loadConfig, listProfiles } from "./config.ts"
|
|
20
13
|
import { SpinupWPClient } from "./api/client.ts"
|
|
21
14
|
import { resolveSshAccess } from "./lib/cliSsh.ts"
|
|
22
15
|
import { resolveIncidents } from "./lib/cliIncidents.ts"
|
|
@@ -25,6 +18,7 @@ import { execSshCommand } from "./lib/sshExec.ts"
|
|
|
25
18
|
import { runPullFiles, runPullDb } from "./lib/cliPull.ts"
|
|
26
19
|
import { runBeacon, runModules } from "./lib/cliBeacon.ts"
|
|
27
20
|
import { runOffline, runOnline } from "./lib/cliOffline.ts"
|
|
21
|
+
import { runScheduler } from "./lib/cliScheduler.ts"
|
|
28
22
|
|
|
29
23
|
const args = process.argv.slice(2)
|
|
30
24
|
const positionals = args.filter((a) => !a.startsWith("-"))
|
|
@@ -113,6 +107,11 @@ Usage:
|
|
|
113
107
|
Add a channel from a file holding TELEGRAM_BOT_TOKEN and
|
|
114
108
|
TELEGRAM_CHAT_ID; sends a test message before saving.
|
|
115
109
|
spinuptui beacon test | status | remove <name>
|
|
110
|
+
spinuptui scheduler add <id> --kind spinuptui|script|agent --when <time> | --every <schedule>
|
|
111
|
+
--expires <date> --created-by <who> -- <body>
|
|
112
|
+
Queue a follow-up for this station to run later and report
|
|
113
|
+
on through Beacon (\`spinuptui scheduler\` for the rest)
|
|
114
|
+
spinuptui scheduler list | status | install --plan|--apply
|
|
116
115
|
spinuptui modules List SpinupTUI's modules and their status (--json)
|
|
117
116
|
spinuptui --version Print the version
|
|
118
117
|
spinuptui --help Show this help
|
|
@@ -313,31 +312,11 @@ if (command === "modules") {
|
|
|
313
312
|
process.exit(await runModules(args.slice(1)))
|
|
314
313
|
}
|
|
315
314
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const forceLogin = command === "login"
|
|
319
|
-
|
|
320
|
-
function Root() {
|
|
321
|
-
const [configured, setConfigured] = useState(isDevMode() ? true : forceLogin ? false : hasToken())
|
|
322
|
-
// The store is keyed by account: switching remounts it (see ui/accounts.tsx).
|
|
323
|
-
const [account, setAccount] = useState(() => pinActiveProfile())
|
|
324
|
-
const switchAccount = useCallback(async (id: string) => {
|
|
325
|
-
await setActiveProfile(id)
|
|
326
|
-
resetWpCoreJobs()
|
|
327
|
-
setAccount(id)
|
|
328
|
-
}, [])
|
|
329
|
-
const accounts = useMemo<AccountsApi>(() => ({ switchAccount }), [switchAccount])
|
|
330
|
-
if (!configured) {
|
|
331
|
-
return <Onboarding onComplete={() => setConfigured(true)} />
|
|
332
|
-
}
|
|
333
|
-
return (
|
|
334
|
-
<AccountsContext.Provider value={accounts}>
|
|
335
|
-
<StoreProvider key={account}>
|
|
336
|
-
<App />
|
|
337
|
-
</StoreProvider>
|
|
338
|
-
</AccountsContext.Provider>
|
|
339
|
-
)
|
|
315
|
+
if (command === "scheduler") {
|
|
316
|
+
process.exit(await runScheduler(args.slice(args.indexOf("scheduler") + 1)))
|
|
340
317
|
}
|
|
341
318
|
|
|
342
|
-
|
|
343
|
-
|
|
319
|
+
// `spinuptui login` forces the onboarding wizard even when a token already exists,
|
|
320
|
+
// so the token can be (re)saved to the config file for global use.
|
|
321
|
+
const { startDashboard } = await import("./ui/main.tsx")
|
|
322
|
+
await startDashboard({ forceLogin: command === "login" })
|