aiur-cli 0.0.4 → 0.0.5-nightly.32b9365

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 CHANGED
@@ -4,6 +4,10 @@
4
4
 
5
5
  Oversee a fleet of coding agents that **coordinate via events**.
6
6
 
7
+ Aiur watches your issue tracker, dispatches one agent per ticket into its own
8
+ workspace and checkout, and gives you four ways to supervise the fleet instead
9
+ of babysitting individual coding sessions.
10
+
7
11
  > [!WARNING]
8
12
  > **Use at your own risk.** Aiur is an unstable, vibecoded engineering preview for trusted
9
13
  > environments only. It **bypasses all agent permission prompts** and has very few efficiency
@@ -12,73 +16,104 @@ Oversee a fleet of coding agents that **coordinate via events**.
12
16
  > Provided "as is", without warranty of any kind. You assume all risk for any cost, token
13
17
  > spend, data loss, or damage from running it.
14
18
 
15
- ## Install
16
-
17
- ```bash
18
- npm install -g aiur-cli
19
- ```
20
-
21
- Requires [tmux](https://github.com/tmux/tmux) at runtime. Install
22
- [opencode](https://opencode.ai) too for in-pane agent chat.
23
-
24
- When a newer `aiur-cli` is published, a one-line update notice is printed to
25
- stderr (checked in the background at most once a day, never blocking your
26
- command). Set `AIUR_NO_UPDATE_NOTIFIER=1` to silence it; it is also skipped under
27
- CI (`CI=true`).
28
-
29
19
  ## Get started
30
20
 
31
21
  ```bash
22
+ npm install -g aiur-cli # Node.js 18+
23
+
32
24
  cd your-repo
33
- aiur init # interactive setup wizard
34
- aiur # start agents (foreground); use `aiur --bg` to detach
25
+ aiur init # interactive setup wizard
26
+ aiur # start the run
35
27
  ```
36
28
 
37
- `aiur init` scaffolds `.aiurconfig` and provisions the repo. It walks you through:
29
+ `aiur init` scaffolds `.aiur/config` and provisions the repo: tracker and
30
+ repository, agent backends and routing, concurrency and turn limits, the GitHub
31
+ token, and the `agent:*` lifecycle labels. Re-running resumes from your saved
32
+ answers.
33
+
34
+ When setup finishes, label the issues you want worked `agent:todo` and run
35
+ `aiur`.
36
+
37
+ | Requirement | Why |
38
+ | --- | --- |
39
+ | [tmux](https://github.com/tmux/tmux) | Runtime for the TUI and agent panes |
40
+ | [opencode](https://opencode.ai) | In-pane agent chat (provisioned on install) |
41
+ | Claude Code or Codex | The agents themselves |
38
42
 
39
- 1. **Where to store config** — repo-local `./.aiurconfig` or global `~/.aiurconfig`.
40
- 2. **Tracker** — GitHub or Linear, plus the repo.
41
- 3. **Agents & routing** — Claude and/or Codex, optional per-complexity model
42
- routing, and the permission mode.
43
- 4. **Limits** — max concurrent agents, turns, duration, pre-warmed sessions, and
44
- polling interval.
45
- 5. **GitHub token** — to create labels and act as the bot account.
46
- 6. **Labels** — the lifecycle (`agent:*`), complexity, model, and remote-control
47
- labels aiur routes on. Only missing labels are created; a group that already
48
- exists is reported as created and skipped.
43
+ ## The four surfaces
49
44
 
50
- Re-running resumes from your saved answers. When setup finishes, add `agent:todo`
51
- to the issues you want worked and run `aiur`.
45
+ | Surface | What it gives you | How to open it |
46
+ | --- | --- | --- |
47
+ | **CLI** | Scriptable control of a run from any shell | `aiur status`, `aiur pause 142` |
48
+ | **TUI** | Live agent board plus chat panes, in your terminal | `aiur` (foreground) |
49
+ | **Dashboard** | Browser view of the fleet, decisions, and analytics | `http://127.0.0.1:4000` |
50
+ | **Stream Deck** | Physical keys and dictation for the fleet | Separate sidecar, see below |
52
51
 
53
- ## Commands
52
+ ### CLI
54
53
 
55
54
  | Command | What it does |
56
- |---|---|
57
- | `aiur init` | Interactive setup wizard (scaffold `.aiurconfig`) |
58
- | `aiur` | Start the workflow in the foreground (local-only bind) |
59
- | `aiur --bg` | Start a headless BEAM in one detached tmux lifetime session |
60
- | `aiur status` | Show active agents and their state |
61
- | `aiur alerts [--needs-attention]` | Show structured alert feed JSON lines |
62
- | `aiur pause <id…>` / `resume <id…>` | Pause or resume agents by issue ID |
63
- | `aiur stop` | Stop the running session |
64
-
65
- ## Features
66
-
67
- - **Issue Monitoring** — Dispatch agents automatically via GitHub or Linear tickets.
68
- - **Shared Bus** — Agents auto-pub/sub to dependency events, unblocking work early.
69
- - **Optimize Effort** — Story points guide model sizes and skills. Supports Claude and Codex.
70
- - **Take the Wheel** — Drive at any moment via pre-warmed [Opencode](https://opencode.ai) sessions in configurable tmux panes.
71
- - **Smart Alerts** — Customize notifications and sounds when agents finish, stall, or need a decision.
72
- - **Live Dashboard** — Shareable web view of every agent, event, and progress bar.
73
-
74
- Background mode is headless inside Aiur: it skips the interactive agent-list
75
- pane, chat/prewarm panes, and dashboard unless you explicitly opt into a
76
- dashboard port. The launcher still keeps one detached tmux session as the BEAM
77
- lifetime holder. Re-running `aiur --bg` against a live session exits with an
78
- already-running hint; stale tmux state is cleaned up before restart.
55
+ | --- | --- |
56
+ | `aiur init` | Interactive setup wizard |
57
+ | `aiur` | Start the run in the foreground with the TUI |
58
+ | `aiur --bg` | Start a detached headless run, dashboard still served |
59
+ | `aiur status` | Daemon state, active agents, and the current concurrency cap |
60
+ | `aiur alerts --needs-attention` | Unresolved items waiting on you |
61
+ | `aiur pause <id…>` / `aiur resume <id…>` | Pause or resume agents by issue id |
62
+ | `aiur message <id> "<text>"` | Send text into a live agent session |
63
+ | `aiur set max-agents <n>` | Change the concurrency cap without restarting |
64
+ | `aiur --todo <id…> [--only]` | Queue tickets; `--only` dequeues the rest |
65
+ | `aiur stop` / `aiur restart` | Stop the session, or refresh the release and restart |
66
+
67
+ Add `--no-dashboard` to either mode to skip the web listener.
68
+
69
+ ### TUI
70
+
71
+ The foreground run opens a tmux board with one row per ticket, showing runtime,
72
+ turn count, backend, pinned model, and a state glyph. Press `enter` on a row to
73
+ open that agent's chat pane beside the board, `space` to pause or resume, `←`
74
+ and `→` to change the concurrency cap, and `?` for the full keymap.
75
+
76
+ ### Dashboard
77
+
78
+ Served on foreground and `--bg` runs at the host and port in your config,
79
+ `http://127.0.0.1:4000` by default. The launch output prints the real URL.
80
+
81
+ | Page | What it shows |
82
+ | --- | --- |
83
+ | Units | Fleet table, ticket backlog, provider rate meters |
84
+ | Commands | Durable decision inbox: answer, revise, retry delivery |
85
+ | Build Order | Ticket dependency graph and phases |
86
+ | Analytics | Throughput, complexity tiers, and spend over a chosen window |
87
+
88
+ Writable mode is the default and requires `AIUR_DASHBOARD_USERNAME` and
89
+ `AIUR_DASHBOARD_PASSWORD`, even on loopback. Set
90
+ `observability.dashboard_writable: false` for an unauthenticated read-only
91
+ loopback dashboard. Basic Auth is unencrypted, so put a TLS proxy in front of any
92
+ non-loopback bind.
93
+
94
+ ### Stream Deck
95
+
96
+ An Elgato Stream Deck + can drive the fleet: agent keys ranked by urgency, pause
97
+ and resume, a live log surface, and hold-to-dictate voice messages. A browser
98
+ emulator of the same layout is at `/streamdeck` on the dashboard.
99
+
100
+ > The physical sidecar is **experimental** and ships separately from this
101
+ > package, as a Linux x64 archive (glibc 2.28+, systemd user session). It is not
102
+ > installed by `npm install aiur-cli`. See the
103
+ > [Stream Deck guide](https://aiur.team/docs/guide/stream-deck).
104
+
105
+ ## Update notices
106
+
107
+ When a newer `aiur-cli` is published, a one-line notice is printed to stderr,
108
+ checked in the background at most once a day and never blocking your command.
109
+ Set `AIUR_NO_UPDATE_NOTIFIER=1` to silence it; it is skipped under CI.
110
+
111
+ Prerelease builds are published under the `nightly` dist-tag and never reach
112
+ `latest`, so a plain `npm install -g aiur-cli` always gives you a stable release.
113
+ To try one: `npm install -g aiur-cli@nightly`.
79
114
 
80
115
  ---
81
116
 
82
117
  _Command macro, delegate micro, maximize APM._
83
118
 
84
- [github.com/its-everdred/aiur](https://github.com/its-everdred/aiur)
119
+ [github.com/aiur-team/aiur](https://github.com/aiur-team/aiur)
package/bin/aiur.js CHANGED
@@ -49,8 +49,8 @@ function resolveReleaseDir() {
49
49
  if (!triple) {
50
50
  fail(
51
51
  `unsupported platform ${process.platform}/${process.arch}. ` +
52
- "Supported: darwin/linux on arm64/x64. " +
53
- "See https://github.com/its-everdred/aiur",
52
+ "Supported: linux (x64/arm64) and darwin (arm64). " +
53
+ "See https://github.com/aiur-team/aiur",
54
54
  );
55
55
  }
56
56
 
@@ -64,7 +64,7 @@ function resolveReleaseDir() {
64
64
  `platform package "${pkg}" is not installed. ` +
65
65
  "This usually means optional dependencies were skipped " +
66
66
  "(--no-optional, a shared lockfile, or a known npm install bug). " +
67
- "Reinstall aiur-cli, or see https://github.com/its-everdred/aiur",
67
+ "Reinstall aiur-cli, or see https://github.com/aiur-team/aiur",
68
68
  );
69
69
  }
70
70
  throw err;
@@ -203,228 +203,6 @@ function ensureOpencode() {
203
203
  );
204
204
  }
205
205
 
206
- // ---------------------------------------------------------------------------
207
- // Update notifier
208
- //
209
- // When a newer aiur-cli is published, surface a one-line "update available"
210
- // notice — npm-update-notifier style: every run reads a small cache file and
211
- // prints from it; the registry is only hit in a detached background worker,
212
- // at most once per interval, so the check never blocks or errors the command.
213
- // The notice always goes to stderr so piped stdout stays clean.
214
- //
215
- // This file is its own background worker: main() re-spawns it with
216
- // AIUR_UPDATE_NOTIFIER_WORKER=1 set, which short-circuits to runUpdateWorker().
217
- // Keeps the launcher dependency-free (a tiny semver compare + https.get).
218
- // ---------------------------------------------------------------------------
219
-
220
- const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h
221
- const REGISTRY_LATEST_URL = "https://registry.npmjs.org/aiur-cli/latest";
222
- // Unpublished/source builds carry this sentinel; never nag on a dev checkout.
223
- const DEV_VERSION = "0.0.0";
224
-
225
- function currentVersion() {
226
- try {
227
- const pkg = JSON.parse(
228
- fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"),
229
- );
230
- return typeof pkg.version === "string" ? pkg.version : null;
231
- } catch (_) {
232
- return null;
233
- }
234
- }
235
-
236
- // Minimal semver: parse major.minor.patch with an optional prerelease tag.
237
- // Enough for the launcher's own clean release versions — not a full spec impl.
238
- // The regex is fully anchored (and tolerates a +build suffix) so a registry- or
239
- // cache-supplied string with trailing junk — e.g. control/ANSI bytes meant to
240
- // hijack the terminal when the notice is printed — fails to parse and is treated
241
- // as not-newer rather than reaching printUpdateNotice verbatim.
242
- function parseSemver(value) {
243
- const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(
244
- String(value).trim(),
245
- );
246
- if (!match) return null;
247
- return { major: +match[1], minor: +match[2], patch: +match[3], pre: match[4] || "" };
248
- }
249
-
250
- // True when `a` is strictly newer than `b`. A release outranks a prerelease at
251
- // the same core version (1.0.0 > 1.0.0-rc.1); unparseable input is never newer.
252
- function semverGt(a, b) {
253
- const pa = parseSemver(a);
254
- const pb = parseSemver(b);
255
- if (!pa || !pb) return false;
256
- if (pa.major !== pb.major) return pa.major > pb.major;
257
- if (pa.minor !== pb.minor) return pa.minor > pb.minor;
258
- if (pa.patch !== pb.patch) return pa.patch > pb.patch;
259
- if (pa.pre === pb.pre) return false;
260
- if (!pa.pre) return true; // a is a release, b is a prerelease
261
- if (!pb.pre) return false; // a is a prerelease, b is a release
262
- return pa.pre > pb.pre; // both prereleases: lexical tiebreak
263
- }
264
-
265
- function cacheFile() {
266
- const base = process.env.XDG_CACHE_HOME
267
- ? path.join(process.env.XDG_CACHE_HOME, "aiur")
268
- : path.join(os.homedir(), ".aiur");
269
- return path.join(base, "update-check.json");
270
- }
271
-
272
- function readCache() {
273
- try {
274
- return JSON.parse(fs.readFileSync(cacheFile(), "utf8"));
275
- } catch (_) {
276
- return null;
277
- }
278
- }
279
-
280
- function writeCache(data) {
281
- try {
282
- const file = cacheFile();
283
- fs.mkdirSync(path.dirname(file), { recursive: true });
284
- // Write-then-rename so a reader never sees a half-written file: if the
285
- // detached worker is killed mid-write, or two workers race, the rename is
286
- // atomic on POSIX and a torn cache can't trigger a respawn-every-run loop.
287
- const tmp = `${file}.${process.pid}.tmp`;
288
- fs.writeFileSync(tmp, JSON.stringify(data));
289
- fs.renameSync(tmp, file);
290
- } catch (_) {
291
- // A missing-cache write must never break the run; silently skip.
292
- }
293
- }
294
-
295
- // Treat any non-falsy CI value except an explicit "false"/"0" as a CI run.
296
- function isCI() {
297
- const value = process.env.CI;
298
- return !!value && value !== "false" && value !== "0";
299
- }
300
-
301
- function notifierDisabled(current) {
302
- return (
303
- process.env.AIUR_NO_UPDATE_NOTIFIER === "1" ||
304
- isCI() ||
305
- !current ||
306
- current === DEV_VERSION
307
- );
308
- }
309
-
310
- function printUpdateNotice(current, latest) {
311
- process.stderr.write(
312
- `aiur: a new version is available — ${current} → ${latest}\n` +
313
- " update: npm install -g aiur-cli@latest\n",
314
- );
315
- }
316
-
317
- // Synchronous, fast, never-throwing: print from cache when a newer version is
318
- // known, then kick off a detached refresh if the cache is stale. The notice is
319
- // TTY-gated so non-interactive/piped invocations stay quiet.
320
- function maybeNotifyUpdate() {
321
- try {
322
- const current = currentVersion();
323
- if (notifierDisabled(current)) return;
324
-
325
- const cache = readCache();
326
- if (
327
- cache &&
328
- cache.latest &&
329
- process.stderr.isTTY &&
330
- semverGt(cache.latest, current)
331
- ) {
332
- printUpdateNotice(current, cache.latest);
333
- }
334
-
335
- const stale =
336
- !cache ||
337
- typeof cache.lastCheck !== "number" ||
338
- Date.now() - cache.lastCheck > UPDATE_CHECK_INTERVAL_MS;
339
- if (stale) spawnUpdateWorker();
340
- } catch (_) {
341
- // The version check is best-effort; never let it affect the command.
342
- }
343
- }
344
-
345
- function spawnUpdateWorker() {
346
- try {
347
- const child = spawn(process.execPath, [__filename], {
348
- detached: true,
349
- stdio: "ignore",
350
- env: { ...process.env, AIUR_UPDATE_NOTIFIER_WORKER: "1" },
351
- });
352
- child.unref();
353
- } catch (_) {
354
- // If we can't detach a worker, just skip — the next run retries.
355
- }
356
- }
357
-
358
- // Fetch the `latest` dist-tag's version. Bounded by a timeout and a response
359
- // cap; any error resolves to null so the worker stays silent when offline.
360
- function fetchLatestVersion(url, done) {
361
- let settled = false;
362
- const finish = (value) => {
363
- if (settled) return;
364
- settled = true;
365
- done(value);
366
- };
367
- try {
368
- const transport = url.startsWith("http://") ? require("node:http") : require("node:https");
369
- const req = transport.get(
370
- url,
371
- { headers: { Accept: "application/json" }, timeout: 5000 },
372
- (res) => {
373
- if (res.statusCode !== 200) {
374
- res.resume();
375
- return finish(null);
376
- }
377
- let body = "";
378
- res.setEncoding("utf8");
379
- res.on("data", (chunk) => {
380
- body += chunk;
381
- // Abort an oversized body. req.destroy() emits only "close" (no "end"
382
- // or "error"), so resolve here explicitly — otherwise the worker would
383
- // hang forever and never stamp the cache, respawning on every run.
384
- if (body.length > 1_000_000) {
385
- req.destroy();
386
- finish(null);
387
- }
388
- });
389
- res.on("end", () => {
390
- try {
391
- const version = JSON.parse(body).version;
392
- finish(typeof version === "string" ? version : null);
393
- } catch (_) {
394
- finish(null);
395
- }
396
- });
397
- },
398
- );
399
- // A timeout's req.destroy() likewise emits only "close", so resolve here too
400
- // rather than waiting on an event that never comes. "error" covers the
401
- // connection-failure case (offline, refused).
402
- req.on("timeout", () => {
403
- req.destroy();
404
- finish(null);
405
- });
406
- req.on("error", () => finish(null));
407
- } catch (_) {
408
- finish(null);
409
- }
410
- }
411
-
412
- // Detached worker entrypoint: refresh the cache, then exit. Always stamps
413
- // lastCheck (even on failure) so an offline machine doesn't respawn every run.
414
- function runUpdateWorker() {
415
- const previous = readCache();
416
- const url = process.env.AIUR_REGISTRY_URL || REGISTRY_LATEST_URL;
417
- fetchLatestVersion(url, (latest) => {
418
- writeCache({
419
- lastCheck: Date.now(),
420
- // Keep the last known `latest` when the fetch fails so a transient outage
421
- // doesn't drop a pending notice.
422
- latest: latest || (previous && previous.latest) || null,
423
- });
424
- process.exit(0);
425
- });
426
- }
427
-
428
206
  // `init` and `--version` run as foreground one-shots that never start the
429
207
  // tmux-backed UI, so their tmux/opencode provisioning is irrelevant — and tmux
430
208
  // may legitimately be absent on a machine that only ever runs `aiur init`.
@@ -438,14 +216,6 @@ function isForegroundOneShot(argv) {
438
216
  }
439
217
 
440
218
  function main() {
441
- // Detached refresh worker: do only the registry check, never the real launch.
442
- if (process.env.AIUR_UPDATE_NOTIFIER_WORKER === "1") {
443
- runUpdateWorker();
444
- return;
445
- }
446
-
447
- maybeNotifyUpdate();
448
-
449
219
  const releaseDir = resolveReleaseDir();
450
220
 
451
221
  if (!isForegroundOneShot(process.argv.slice(2))) {