@timqi/pier 0.1.14 → 0.2.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.
Files changed (126) hide show
  1. package/README.md +44 -22
  2. package/dist/agent/config.js +2 -2
  3. package/dist/agent/events.js +32 -3
  4. package/dist/agent/packages.js +6 -10
  5. package/dist/agent/pi.js +111 -68
  6. package/dist/agent/roles.js +103 -0
  7. package/dist/agent/types.js +78 -0
  8. package/dist/boards/boards.js +42 -32
  9. package/dist/channels/chains.js +0 -4
  10. package/dist/channels/commands.js +7 -12
  11. package/dist/channels/config.js +27 -1
  12. package/dist/channels/control.js +5 -16
  13. package/dist/channels/conversations.js +0 -5
  14. package/dist/channels/lark-api.js +20 -26
  15. package/dist/channels/lark-outbound.js +16 -29
  16. package/dist/channels/lark-panel.js +1 -1
  17. package/dist/channels/lark.js +61 -39
  18. package/dist/channels/lines.js +33 -1
  19. package/dist/channels/panel.js +17 -107
  20. package/dist/channels/routes.js +11 -21
  21. package/dist/channels/runtime.js +34 -25
  22. package/dist/channels/slack-outbound.js +6 -15
  23. package/dist/channels/slack-panel.js +1 -1
  24. package/dist/channels/slack-render.js +1 -1
  25. package/dist/channels/slack.js +63 -57
  26. package/dist/channels/types.js +1 -1
  27. package/dist/config-sync.js +42 -37
  28. package/dist/core/chain.js +202 -0
  29. package/dist/core/identity.js +34 -11
  30. package/dist/core/inbound-file.js +4 -3
  31. package/dist/core/reply.js +96 -80
  32. package/dist/core/router.js +111 -118
  33. package/dist/core/types.js +36 -77
  34. package/dist/db.js +33 -2
  35. package/dist/main.js +46 -37
  36. package/dist/settings.js +25 -18
  37. package/dist/tasks/agent.js +11 -5
  38. package/dist/tasks/callbacks.js +55 -5
  39. package/dist/tasks/cli.js +22 -11
  40. package/dist/tasks/definitions.js +30 -17
  41. package/dist/tasks/execution.js +2 -2
  42. package/dist/tasks/groups.js +5 -2
  43. package/dist/tasks/messages.js +7 -7
  44. package/dist/tasks/open-items.js +70 -0
  45. package/dist/tasks/operations.js +76 -39
  46. package/dist/tasks/outbox.js +19 -6
  47. package/dist/tasks/routes.js +19 -249
  48. package/dist/tasks/service.js +145 -34
  49. package/dist/tasks/store.js +119 -56
  50. package/dist/tasks/types.js +5 -0
  51. package/dist/tools.js +2 -2
  52. package/dist/web/auth.js +2 -2
  53. package/dist/web/config-sync.js +4 -5
  54. package/dist/web/config.js +9 -2
  55. package/dist/web/explorer.js +2 -2
  56. package/dist/web/fs.js +16 -1
  57. package/dist/web/instance.js +8 -5
  58. package/dist/web/packages.js +1 -1
  59. package/dist/web/passkeys.js +3 -3
  60. package/dist/web/providers.js +3 -3
  61. package/dist/web/public/assets/code-BZwvPani.js +2 -0
  62. package/dist/web/public/assets/code-BZwvPani.js.br +0 -0
  63. package/dist/web/public/assets/code-BZwvPani.js.gz +0 -0
  64. package/dist/web/public/assets/explorer-DDoVzMLQ.js +5 -0
  65. package/dist/web/public/assets/explorer-DDoVzMLQ.js.br +0 -0
  66. package/dist/web/public/assets/explorer-DDoVzMLQ.js.gz +0 -0
  67. package/dist/web/public/assets/highlight-5Bl8zZ8q.js +72 -0
  68. package/dist/web/public/assets/highlight-5Bl8zZ8q.js.br +0 -0
  69. package/dist/web/public/assets/highlight-5Bl8zZ8q.js.gz +0 -0
  70. package/dist/web/public/assets/{hljs-tWqyD59G.js → hljs-BRz6a8Dq.js} +2 -2
  71. package/dist/web/public/assets/hljs-BRz6a8Dq.js.br +0 -0
  72. package/dist/web/public/assets/hljs-BRz6a8Dq.js.gz +0 -0
  73. package/dist/web/public/assets/index-BpFzCwlo.js +7 -0
  74. package/dist/web/public/assets/index-BpFzCwlo.js.br +0 -0
  75. package/dist/web/public/assets/index-BpFzCwlo.js.gz +0 -0
  76. package/dist/web/public/assets/index-wAGxFFPj.css +2 -0
  77. package/dist/web/public/assets/index-wAGxFFPj.css.br +0 -0
  78. package/dist/web/public/assets/index-wAGxFFPj.css.gz +0 -0
  79. package/dist/web/public/assets/report-zjE8Bt8S.js +8 -0
  80. package/dist/web/public/assets/report-zjE8Bt8S.js.br +0 -0
  81. package/dist/web/public/assets/report-zjE8Bt8S.js.gz +0 -0
  82. package/dist/web/public/assets/settings-Cv7rbeqf.js +9 -0
  83. package/dist/web/public/assets/settings-Cv7rbeqf.js.br +0 -0
  84. package/dist/web/public/assets/settings-Cv7rbeqf.js.gz +0 -0
  85. package/dist/web/public/index.html +40 -109
  86. package/dist/web/public/index.html.br +0 -0
  87. package/dist/web/public/index.html.gz +0 -0
  88. package/dist/web/public/manifest.webmanifest +0 -2
  89. package/dist/web/push.js +1 -1
  90. package/dist/web/server.js +91 -116
  91. package/dist/web/session-state.js +5 -40
  92. package/docs/deploy.md +5 -3
  93. package/package.json +2 -1
  94. package/skills/pier-boards/SKILL.md +14 -4
  95. package/skills/pier-help/SKILL.md +57 -23
  96. package/skills/pier-tasks/SKILL.md +58 -18
  97. package/dist/channels/handoff.js +0 -94
  98. package/dist/web/public/assets/activity-Ds6fCHnb.js +0 -5
  99. package/dist/web/public/assets/activity-Ds6fCHnb.js.br +0 -0
  100. package/dist/web/public/assets/activity-Ds6fCHnb.js.gz +0 -0
  101. package/dist/web/public/assets/boards-BKCj6EwK.js +0 -1
  102. package/dist/web/public/assets/boards-BKCj6EwK.js.br +0 -0
  103. package/dist/web/public/assets/boards-BKCj6EwK.js.gz +0 -0
  104. package/dist/web/public/assets/explorer-DnTm975c.js +0 -4
  105. package/dist/web/public/assets/explorer-DnTm975c.js.br +0 -0
  106. package/dist/web/public/assets/explorer-DnTm975c.js.gz +0 -0
  107. package/dist/web/public/assets/hljs-tWqyD59G.js.br +0 -0
  108. package/dist/web/public/assets/hljs-tWqyD59G.js.gz +0 -0
  109. package/dist/web/public/assets/index-BQo-haPN.js +0 -85
  110. package/dist/web/public/assets/index-BQo-haPN.js.br +0 -0
  111. package/dist/web/public/assets/index-BQo-haPN.js.gz +0 -0
  112. package/dist/web/public/assets/index-DiHj0w1i.css +0 -2
  113. package/dist/web/public/assets/index-DiHj0w1i.css.br +0 -0
  114. package/dist/web/public/assets/index-DiHj0w1i.css.gz +0 -0
  115. package/dist/web/public/assets/runs-C_AthWcW.js +0 -1
  116. package/dist/web/public/assets/runs-C_AthWcW.js.br +0 -0
  117. package/dist/web/public/assets/runs-C_AthWcW.js.gz +0 -0
  118. package/dist/web/public/assets/settings-DDaAtFlc.js +0 -5
  119. package/dist/web/public/assets/settings-DDaAtFlc.js.br +0 -0
  120. package/dist/web/public/assets/settings-DDaAtFlc.js.gz +0 -0
  121. package/dist/web/public/assets/task-runs-0pvdITiV.js +0 -3
  122. package/dist/web/public/assets/task-runs-0pvdITiV.js.br +0 -0
  123. package/dist/web/public/assets/task-runs-0pvdITiV.js.gz +0 -0
  124. package/dist/web/public/assets/tasks-Bz29caHJ.js +0 -4
  125. package/dist/web/public/assets/tasks-Bz29caHJ.js.br +0 -0
  126. package/dist/web/public/assets/tasks-Bz29caHJ.js.gz +0 -0
@@ -1,13 +1,7 @@
1
1
  // What the workbench decided about a session, and nothing a transcript already
2
- // knows: a finished turn nobody looked at (`unread`), and the rail's working
3
- // set (`sort`). A session enters the set at the front when a human speaks to it
4
- // and it is not already in; members never move relative to each other until one
5
- // is pushed out. Remembered, not derived: any order recomputed from activity
6
- // jumps on every message. `cwd` and `project_sort` are columns nothing reads.
7
- import { pierDb, transact } from "../db.js";
8
- /** Small enough that every member is where a hand can see it: a set as deep as
9
- * the page would count a row nobody scrolled to as "already up there". */
10
- export const WORKING_SET = 5;
2
+ // knows: a finished turn nobody looked at (`unread`). `cwd` and `project_sort`
3
+ // are columns nothing reads.
4
+ import { pierDb } from "../db.js";
11
5
  export class SessionStateStore {
12
6
  #db;
13
7
  constructor(db = pierDb()) {
@@ -21,37 +15,8 @@ export class SessionStateStore {
21
15
  this.#db.prepare(`INSERT INTO session_state(session_id, unread) VALUES (?, ?)
22
16
  ON CONFLICT(session_id) DO UPDATE SET unread = excluded.unread`).run(sessionId, unread ? 1 : 0);
23
17
  }
24
- /** Already in the working set: nothing moves. Answers whether the order
25
- * changed, so the caller re-lists only when there is something to see. */
26
- promote(sessionId) {
27
- // `session_id` breaks a tie migration 20 can leave behind (several rows at -1).
28
- const ranked = this.#db.prepare("SELECT session_id AS id FROM session_state WHERE sort IS NOT NULL ORDER BY sort, session_id").all().map((r) => r.id);
29
- if (ranked.includes(sessionId))
30
- return false;
31
- const kept = [sessionId, ...ranked].slice(0, WORKING_SET);
32
- const evicted = ranked.filter((id) => !kept.includes(id));
33
- const rank = this.#db.prepare(`INSERT INTO session_state(session_id, sort) VALUES (?, ?)
34
- ON CONFLICT(session_id) DO UPDATE SET sort = excluded.sort`);
35
- const drop = this.#db.prepare("UPDATE session_state SET sort = NULL WHERE session_id = ?");
36
- // Every rank rewritten: a half-renumbered set is an order nobody would recognize.
37
- transact(this.#db, () => {
38
- kept.forEach((id, i) => rank.run(id, i));
39
- for (const id of evicted)
40
- drop.run(id);
41
- });
42
- return true;
43
- }
44
- /** For ghosts: a created-and-never-messaged session cannot be resumed, and
45
- * its row would otherwise hold a working-set slot while 404ing forever. */
46
- forget(sessionId) {
47
- this.#db.prepare("DELETE FROM session_state WHERE session_id = ?").run(sessionId);
48
- }
49
18
  flags() {
50
- const rows = this.#db.prepare(`SELECT session_id AS id, unread, sort
51
- FROM session_state WHERE unread = 1 OR sort IS NOT NULL`).all();
52
- return new Map(rows.map((r) => [r.id, {
53
- unread: r.unread === 1,
54
- ...(r.sort === null ? {} : { rank: r.sort }),
55
- }]));
19
+ const rows = this.#db.prepare("SELECT session_id AS id FROM session_state WHERE unread = 1").all();
20
+ return new Map(rows.map((r) => [r.id, { unread: true }]));
56
21
  }
57
22
  }
package/docs/deploy.md CHANGED
@@ -66,7 +66,7 @@ journalctl --user -u pier | grep 'client:' # browser-side errors
66
66
 
67
67
  Every line is `area: message` — `core`, `agent`, `tasks`, `slack`,
68
68
  `lark`, `channels`, `auth`, `boards`, `client`, `db`, `drain`, `secrets`,
69
- `vault`, `socket`, `settings`, `credentials`, `packages`, `config-sync`,
69
+ `vault`, `socket`, `settings`, `credentials`, `packages`, `passkeys`, `config-sync`,
70
70
  `update`, `tools`, `push`, `web`, `web.providers`, `pier`. Level: a syslog priority prefix under
71
71
  `$JOURNAL_STREAM`, a level word in a terminal. `client:` is posted back by
72
72
  signed-in workbench tabs (`ui/report.ts`): script errors, unhandled rejections,
@@ -166,8 +166,9 @@ pier update # installs the latest release, then hard-stops/restarts Pi
166
166
  pier update --check # only says whether one exists
167
167
  ```
168
168
 
169
- The footer asks `registry.npmjs.org` at boot and at most every 30 minutes and
170
- shows `v0.0.1 → 0.0.2` when newer exists; a failed check is silent.
169
+ Pier asks `registry.npmjs.org` at boot and at most every 30 minutes; the
170
+ version in the Settings header turns into a badge when a newer one exists
171
+ (`Pier <version> is out`); a failed check is silent.
171
172
 
172
173
  From a checkout:
173
174
 
@@ -268,6 +269,7 @@ Loopback bind; reach it over a tunnel, not a wider bind:
268
269
  ... "VACUUM INTO '…'"`, not `cp` (WAL can miss the latest commits).
269
270
  - `~/.pier/master.key` — seals the database's credentials and the vault's `auto` rows.
270
271
  - `~/.pier/boards/`.
272
+ - `~/.pier/home` — the conversation's memory: `MEMORY.md`, `memory/` daily notes.
271
273
  - `~/.pier/db/backups/` — the automatic pre-update and pre-migration copies.
272
274
  - `~/.pier/pi` — Pi's session history (unless `PI_CODING_AGENT_DIR` names
273
275
  another directory).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timqi/pier",
3
- "version": "0.1.14",
3
+ "version": "0.2.0",
4
4
  "description": "A self-hosted workspace for coding agents: web workbench and IM channels in front of Pi sessions",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": "github:timqi/pier",
@@ -14,6 +14,7 @@
14
14
  "files": [
15
15
  "dist",
16
16
  "!dist/**/*.test.js",
17
+ "!dist/**/*.testkit.js",
17
18
  "skills",
18
19
  "docs/deploy.md"
19
20
  ],
@@ -15,11 +15,11 @@ Before editing, read the board's README if present; change sources and rebuild.
15
15
  Create `<boards folder>/<slug>/board.json` and `site/index.html`:
16
16
 
17
17
  ```json
18
- {"title":"Weekly digest","description":"What changed and needs a decision","sessions":["<session id>"],"public":false}
18
+ {"title":"Weekly digest","description":"What changed and needs a decision","public":false}
19
19
  ```
20
20
 
21
21
  Slug: `[a-z0-9][a-z0-9-]{0,63}`, meaningful without random suffixes. Description
22
- is Console list text; append your session id, preserving existing provenance.
22
+ is one line on what the board is for.
23
23
 
24
24
  `public: true` removes the password. Set it only when this request asks for a
25
25
  public/shareable board; otherwise keep it private. Never publish personal data
@@ -34,7 +34,17 @@ Return one bare URL using the configured instance address:
34
34
  Never return both URLs, link labels or filesystem paths. Without a configured
35
35
  address, return the path and point to Console → Settings; never guess a host.
36
36
  For publish-only requests, set visibility/token and return the URL; skip layout
37
- checks and narration. Console → Boards can also publish.
37
+ checks and narration.
38
+
39
+ ## Answering the user
40
+
41
+ - List: read every `<boards folder>/*/board.json` (skip names with
42
+ `.deleted-`); public boards first with `/p/<slug>-<token>/`, then private
43
+ with `/boards/<slug>/`; one row each, title and how long ago `site/` changed.
44
+ - Publish or unpublish: set `public` in `board.json` by the token rule above,
45
+ keep every other field, and answer with the resulting URL.
46
+ - When publishing, say: "anyone with the link can read it".
47
+ - Delete: rename the directory to `<slug>.deleted-<unix ms>`; never remove it.
38
48
 
39
49
  ## Page
40
50
 
@@ -53,7 +63,7 @@ viewport metadata, a descriptive title and:
53
63
  - Put decisions and evidence first; fold supplementary detail with `<details>`.
54
64
  End with data date, checkable sources and refresh instructions, not a recap.
55
65
  - No secrets, credentials, internal hostnames or private paths, even in folds:
56
- private boards can become public with one toggle.
66
+ private boards can become public with one edit.
57
67
  - Assets belong under `site/`, using relative URLs except the shared stylesheet.
58
68
  Illustrate freely with inline SVG or images you draw, render or save into
59
69
  `site/`: CSP allows `img-src 'self' data:`. No CDN, external fonts, analytics
@@ -14,26 +14,40 @@ truth.
14
14
 
15
15
  ## Sessions and persistence
16
16
 
17
- - One durable session per conversation: a web chat, a Slack or Lark thread.
18
- The mapping survives restarts — the next message
17
+ - One durable session per conversation: a web session, a task run's, a Slack
18
+ or Lark thread; Pier's own conversation is a chain of them (§Pier). The
19
+ mapping survives restarts — the next message
19
20
  lands in the same transcript with its context intact.
20
21
  - Idle sessions leave memory but keep their transcript; they resume
21
22
  transparently on the next message. Never promise that a restart or a pause
22
23
  wipes context.
23
- - A fresh start is explicit: a new thread (its panel drafts the session) or the
24
- web UI. The old transcript remains readable from the web workbench.
24
+ - A fresh start is explicit: a new thread (its panel drafts the session) or
25
+ `/new` in Pier's conversation (web or the home chat). The old transcript
26
+ remains readable from the web workbench.
25
27
  - The web workbench can also rewind to an earlier user turn and re-prompt;
26
28
  IM surfaces cannot.
27
- - A web session can be continued in Slack or Lark — the web session menu's
28
- "Continue in Lark/Slack…" opens a thread for it — or pulled from a thread
29
- that has no session yet, through its panel's "Continue web session…".
30
- Replies then land on both surfaces; a session already answering a chat
31
- cannot be moved.
32
29
  - A long session does not hit a wall: when the context fills, Pi compacts it
33
30
  automatically — older turns become a summary. The transcript on disk keeps
34
31
  everything, but detail can leave *your* context, so a very old turn is worth
35
- re-reading rather than recalling. The web session header shows context used
36
- and how much is left.
32
+ re-reading rather than recalling. The web session header shows context used.
33
+
34
+ ## Pier
35
+
36
+ - Pier is one conversation, on the web and in the home chat (below): the
37
+ status chip opens the status panel — what is running over the open items;
38
+ the rest is in ⌘K. Its current session, the head, runs in `$PIER_HOME/home`,
39
+ which holds memory only: `MEMORY.md` and daily notes in `memory/`.
40
+ - After an idle hour, or past 60K tokens, the next message starts a fresh
41
+ session, seeded with `$PIER_HOME/home`'s memory, the open items, its runs
42
+ and the last exchanges.
43
+ - It dispatches: real work is a task run — a worker, or a feature lead that
44
+ designs with the user; once the user finalizes, a new build lead builds it
45
+ with workers — each its own session, taking messages directly. Results come
46
+ back to the head as callbacks.
47
+ - `/status` alone in the composer answers with the open items — each problem,
48
+ its stage and its runs — with no model call; the web's status panel shows
49
+ the same list under what is running or queued. `/new` starts
50
+ a fresh session now, `/stop` stops the reply in progress.
37
51
 
38
52
  ## Files and images the user sends
39
53
 
@@ -52,20 +66,40 @@ truth.
52
66
  into the running turn as a steer.
53
67
  - From an IM chat, every mid-turn message steers the running turn directly —
54
68
  no `!` needed, and a leading `!` is just content.
55
- - `/stop` from an IM chat aborts the current turn outright; the web has a Stop
56
- button.
69
+ - `/stop` (or `%stop`) aborts the current turn outright in IM and in Pier's
70
+ web conversation; every web session also has a Stop button.
57
71
 
58
72
  ## In-chat commands and the settings panel
59
73
 
60
- - `/settings` — or an addressed message with no text at all (a bare mention,
61
- an empty DM) — opens a panel; Slack takes the same words bare (`stop`,
62
- `settings`, `bind <code>`).
63
- - In a thread with no session yet the panel is a draft: directory, model &
64
- reasoning (the operator's pinned models, one pick sets both), and Start
65
- creates the session. `s <text>` as a thread's first message (Lark also
66
- `/s <text>`) opens that draft with the text as a pending question, which
67
- Start runs as the first message; a bare `s`, or `s <text>` inside a thread,
68
- is an ordinary message.
74
+ - A chat command is the whole message `/<word>` or `%<word>` on every surface
75
+ (`%` because Slack's client eats an unregistered `/`); there are no bare
76
+ command words, and a message that is only a mention is dropped. An unknown
77
+ `/word` is a message. The commands (this list mirrors
78
+ `docs/design/11-im-conversation.md` §Chat commands):
79
+ - `/status` — the head's open items; prose in a thread.
80
+ - `/new` — the next head now; refused while the head is replying; prose in
81
+ a thread.
82
+ - `/stop` — aborts the head's turn (children untouched), or the thread's
83
+ session.
84
+ - `/skills` — one `<name> — <description>` line per skill of the session
85
+ asked, head or thread.
86
+ - `/settings` — the panel on a thread; prose in the home chat.
87
+ - `/s <text>` — on a thread root, the draft with `<text>` as its question;
88
+ prose inside a thread and in the home chat.
89
+ - `/bind <code>` — in a DM, binds the sender.
90
+ - `/<word> <text>` (or `%`) runs a skill when `word` is a prefix of its name or
91
+ of the part after any `-` (`/tasks`, `/ta` → `pier-tasks`); several matches
92
+ are refused with their names, nothing sent; a one-letter word never matches.
93
+ - The home chat: at most one DM per instance, set in the Console's chat row.
94
+ Every message there, top-level or in a thread, reaches Pier; its replies,
95
+ callbacks, seeds and command answers render in the DM's main flow.
96
+ `/settings` and `/s <text>` are prose there; while that adapter is live the
97
+ head sends no Web Push. Groups stay thread-per-session.
98
+ - Elsewhere `/settings` opens a panel. In a thread with no session yet it is a
99
+ draft: directory, model & reasoning (the operator's pinned models, one pick
100
+ sets both), and Start creates the session. `/s <text>` as a thread's first
101
+ message opens that draft with the text as a pending question, which Start
102
+ runs as the first message.
69
103
  - In a thread with a session the panel reads it out (resuming an idle one) and
70
104
  offers "Model & reasoning"; Stop aborts a running turn. A session's directory
71
105
  is fixed at creation, so another directory means another thread.
@@ -103,7 +137,7 @@ truth.
103
137
  mention, require the sender to be bound, or both. Dropped messages are
104
138
  logged, never answered.
105
139
  - Binding: the operator issues a code in the Console; the user DMs the bot
106
- `/bind <code>` (Slack: `bind <code>`). Codes expire after ~10 minutes.
140
+ `/bind <code>` (or `%bind <code>`). Codes expire after ~10 minutes.
107
141
  - An unbound DM sender is told how to bind at most once per 10 minutes;
108
142
  their other messages are dropped. "The bot ignores my DMs" usually means
109
143
  not bound.
@@ -5,10 +5,9 @@ description: Subagents and scheduled tasks with `pier task`. Read before delegat
5
5
 
6
6
  # Pier tasks
7
7
 
8
- `pier task --help` lists the five commands and their flags. Each prints one
9
- JSON receipt (`--model ?` one pin per line), exit 0; a refusal is a `task:`
10
- line, exit 1 (`--model` with no or several hits lists the menu under it); a
11
- bad flag is `task:` plus the usage, exit 2. `--prompt -` reads stdin.
8
+ `pier task --help` lists the nine commands and their flags. Each prints one
9
+ JSON receipt, exit 0; a refusal is a `task:` line, exit 1; a bad flag is
10
+ `task:` plus the usage, exit 2. `--prompt -` reads stdin.
12
11
 
13
12
  ## Delegate, then end your turn
14
13
 
@@ -28,11 +27,11 @@ drops the result; `--callback-session <id>` delivers elsewhere.
28
27
 
29
28
  | Flags | Run |
30
29
  | --- | --- |
31
- | `--task-id <id>` | a saved definition, as is |
30
+ | `--task-id <id>` | a saved definition, as is (run now) |
32
31
  | `--session <id> --prompt …` | continue an idle session (it keeps its cwd and model) |
33
32
  | `--run <id> --prompt …` | existing run: running → steer; `--after` → after its turn; finished → resume (`--callback*` apply only then). The receipt's `delivery` says which |
34
33
  | `--member --prompt … --member …` | batch: flags before the first `--member` are defaults, ≥2 members, `--join all` (default) or `first`; the callback is the group's |
35
- | `--bash <script>` | a command, not an agent: its stdout is the result, and `--prompt`/`--model`/`--thinking`/`--session` beside it are refused |
34
+ | `--bash <script>` | a command, not an agent: its stdout is the result, and `--prompt`/`--model`/`--thinking`/`--role`/`--design`/`--session` beside it are refused |
36
35
 
37
36
  `--bash` is for a command whose output needs no model **and** runs too long to
38
37
  hold your turn; a quick one belongs in your own shell, where `&` and `wait`
@@ -46,11 +45,26 @@ members by hand.
46
45
 
47
46
  ## Model choice
48
47
 
49
- Default: your model. Harder reasoning: `--thinking` first
50
- (`off/minimal/low/medium/high/xhigh/max`). `--model <name>` is matched
51
- against the operator's menu (substring of provider, id or note — "let gpt
52
- review it" is `--model gpt`); none or several hits lists the pins, pick one.
53
- `--model ?` prints the menu, one pin per line. Never name a model id from memory.
48
+ Default: your model. `--model hardest | balanced | cheap` are tiers the
49
+ operator pinned on the menu; thinking follows the pin, `--thinking`
50
+ overrides (`off/minimal/low/medium/high/xhigh/max`).
51
+
52
+ | Work | `--model` |
53
+ | --- | --- |
54
+ | lead; design, architecture | `hardest` |
55
+ | coding a feature or a fix; integration | `balanced` |
56
+ | research, summaries, lookups, transcripts, bulk mechanical edits | `cheap` |
57
+
58
+ A tier follows the change's difficulty, not the task's kind: a review takes the
59
+ builder's tier, `hardest` only when the diff touches a seam
60
+ (`src/core/types.ts`, `src/channels/types.ts`, `src/tasks/types.ts`, `src/db.ts`
61
+ migrations, auth/vault/secrets) or the builder's result reports a risk or an
62
+ unverified part; a model the user names overrides both.
63
+
64
+ Any other name is a substring of provider or id ("let gpt review it" is
65
+ `--model gpt`); none or several hits lists the pins. `--model ?` prints the
66
+ menu, the tier in front of each pinned line; an unassigned tier refuses with
67
+ the menu. Never name a model id from memory.
54
68
 
55
69
  ## Cancel · recover
56
70
 
@@ -62,21 +76,47 @@ chars per run, a group's members included) or lost to compaction. `--group`
62
76
  caps each member at 2 000, so a long member is recovered with `--run`.
63
77
  **Never to check progress**: the refusal reveals no state.
64
78
 
79
+ `pier task runs` — the runs you launched, in flight and finished in the last
80
+ 24h. For orientation, never to wait on a result.
81
+
82
+ `--role lead` on a fresh `--prompt` run launches a feature lead: a long-lived
83
+ child in the feature's worktree that builds with workers; `--design` beside it
84
+ makes it a design lead, which designs with the user until they finalize. It is
85
+ the one delegated run that may delegate, never to a lead; only
86
+ its reply to the last result owed it reaches its supervisor.
87
+
65
88
  ## Saved definitions
66
89
 
67
90
  ```sh
68
91
  pier task save --name nightly --bash "make check" --cwd /repo --cron "0 3 * * *" --tz UTC
69
92
  ```
70
93
 
71
- Only for schedules or roles run more than once; `--task-id` updates. A
72
- schedule's results reach nobody unless `--callback-session <id>` names a
73
- session; `pier task list` shows definitions, never runs.
94
+ Only for schedules or roles run more than once; `--task-id` updates, restating
95
+ every flag. Results reach Pier;
96
+ `--callback-session <id>` pins a session, `--callback-session none` silences.
97
+ `pier task list` shows definitions with `nextRun` and `lastRun`, never runs.
98
+
99
+ | Command | Does |
100
+ | --- | --- |
101
+ | `pier task pause --task-id <id>` | schedule off; still runs on demand |
102
+ | `pier task resume --task-id <id>` | schedule on |
103
+ | `pier task run --task-id <id>` | run now |
104
+ | `pier task archive --task-id <id>` | retired for good |
105
+
106
+ ## Answering the user about the schedule
107
+
108
+ - Run `pier task list`; the schedule is its cron and watch definitions.
109
+ - One line each: name, trigger (cron + zone, or watch every N s), next run,
110
+ last run's state and age. Paused: say so in place of next run.
111
+ - One-shots and manual tasks are runs, not schedule.
112
+ - Older runs: `pier task runs`; a finished result's full text: `recover`.
74
113
 
75
114
  ## Limits
76
115
 
77
- - A delegated run does not delegate: `pier task` is refused inside a run
78
- someone waits on — ask in your result; your supervisor runs it.
116
+ - A worker does not delegate: `pier task` is refused in a session a delegated
117
+ run created (a lead's aside), in its run and after it, and in any run someone
118
+ waits on — say what needs another agent; the supervisor runs it.
79
119
  - 6 agent runs execute at once instance-wide, `--bash` runs taking none of
80
- those slots; the rest queue until cancelled
81
- or a restart marks them `interrupted` (callbacks still fire).
120
+ those slots; the rest queue until cancelled or a restart marks them
121
+ `interrupted` (callbacks still fire).
82
122
  - During a restart drain new runs are refused: retry after.
@@ -1,94 +0,0 @@
1
- // Web ↔ IM handoff: the only code that binds an existing session to a thread it
2
- // did not create — pushed from the web (a new thread, the only root message
3
- // Pier ever posts; post before row, since a row for a thread that does not
4
- // exist is worse than a message with no row) or pulled from a thread's panel.
5
- import { sessionLabel } from "../core/identity.js";
6
- import { chatOf, isChannelPlatform, } from "./types.js";
7
- export class HandoffError extends Error {
8
- status;
9
- constructor(status, message) {
10
- super(message);
11
- this.status = status;
12
- }
13
- }
14
- const platformName = (platform) => platform[0].toUpperCase() + platform.slice(1);
15
- export function createHandoff(deps) {
16
- const { store, runtime, conversations, factory, router, hub, publicUrl, log } = deps;
17
- /** Where a session already answers, for the refusal: the chat's name, else its id. */
18
- const nameOf = (key) => {
19
- const chatId = chatOf(key.conversationId);
20
- return (isChannelPlatform(key.channelId) && store.chat(key.channelId, chatId)?.name) || chatId;
21
- };
22
- /** The guards both directions share: on disk, and nobody's yet. */
23
- const claimable = async (sessionId) => {
24
- const summary = await factory.find(sessionId);
25
- if (!summary) {
26
- throw new HandoffError(404, `Session ${sessionId.slice(0, 8)} has no transcript yet — send it one message first.`);
27
- }
28
- const bound = conversations.keyOf(sessionId);
29
- if (bound)
30
- throw new HandoffError(409, `Already answers in ${bound.channelId} · ${nameOf(bound)}.`);
31
- return summary;
32
- };
33
- /** The one binding. Loaded on the web right now: the next turn answers in
34
- * the thread without waiting for the thread to speak. Not loaded: the row
35
- * is enough. */
36
- const bind = (key, sessionId) => {
37
- conversations.set(key, sessionId);
38
- const live = router.sessionOf({ channelId: "web", conversationId: sessionId });
39
- if (live)
40
- router.attach(key, live);
41
- hub.emitWorkspace({ type: "sessions-changed" });
42
- log(`session ${sessionId} continued in ${key.channelId}:${key.conversationId}`);
43
- };
44
- return {
45
- // DMs only: a group's thread belongs to the group, and a session pushed
46
- // into one answers everyone there.
47
- targets: () => runtime.running().flatMap((platform) => store.get(platform).chats
48
- .filter((chat) => chat.enabled && chat.kind === "dm")
49
- .map((chat) => ({ platform, chatId: chat.id, name: chat.name }))),
50
- async continueIn({ sessionId, platform, chatId }) {
51
- if (!runtime.running().includes(platform)) {
52
- throw new HandoffError(409, `${platformName(platform)} is not running — enable it in Settings → Channels.`);
53
- }
54
- const chat = store.chat(platform, chatId);
55
- if (!chat?.enabled)
56
- throw new HandoffError(chat ? 409 : 404, "That chat is not enabled for the bot.");
57
- if (chat.kind !== "dm")
58
- throw new HandoffError(409, "Only a direct message can continue a web session.");
59
- const summary = await claimable(sessionId);
60
- const base = publicUrl();
61
- const note = {
62
- title: sessionLabel(summary),
63
- url: base ? `${base}/app/#/session/${encodeURIComponent(sessionId)}` : "",
64
- };
65
- let conversationId;
66
- try {
67
- conversationId = await runtime.openThread(platform, chatId, note);
68
- }
69
- catch (err) {
70
- throw new HandoffError(502, String(err));
71
- }
72
- bind({ channelId: platform, conversationId }, sessionId);
73
- return { conversationId };
74
- },
75
- async unbound(limit) {
76
- const bound = conversations.boundSessions();
77
- const owned = deps.taskSessions();
78
- const ranks = deps.workingSet();
79
- const rank = (id) => ranks.get(id)?.rank ?? Infinity;
80
- return (await factory.list())
81
- .filter((s) => !bound.has(s.id) && !owned.has(s.id))
82
- .sort((a, b) => rank(a.id) - rank(b.id) || b.createdAt - a.createdAt)
83
- .slice(0, limit);
84
- },
85
- async continueHere(key, sessionId) {
86
- // The panel hides the button once a row exists, but a stale panel or a
87
- // message that raced the tap must not orphan the thread's session.
88
- if (conversations.get(key))
89
- throw new HandoffError(409, "This thread already has a session.");
90
- await claimable(sessionId);
91
- bind(key, sessionId);
92
- },
93
- };
94
- }
@@ -1,5 +0,0 @@
1
- import{$ as e,C as t,F as n,H as r,K as i,O as a,Q as o,X as s,Y as c,ot as l,t as u,tt as d,u as f}from"./index-BQo-haPN.js";var p=[[`path`,{d:`M3 5h.01`}],[`path`,{d:`M3 12h.01`}],[`path`,{d:`M3 19h.01`}],[`path`,{d:`M8 5h13`}],[`path`,{d:`M8 12h13`}],[`path`,{d:`M8 19h13`}]],m=[[`path`,{d:`m10.586 5.414-5.172 5.172`}],[`path`,{d:`m18.586 13.414-5.172 5.172`}],[`path`,{d:`M6 12h12`}],[`circle`,{cx:`12`,cy:`20`,r:`2`}],[`circle`,{cx:`12`,cy:`4`,r:`2`}],[`circle`,{cx:`20`,cy:`12`,r:`2`}],[`circle`,{cx:`4`,cy:`12`,r:`2`}]],h=(e,t={})=>{let n=document.createElementNS(`http://www.w3.org/2000/svg`,e);for(let[e,r]of Object.entries(t))n.setAttribute(e,r);return n},g=e=>e===null?`-`:c(Date.now()-e),_={invocation:{stroke:`stroke-neutral-400`,fill:`fill-neutral-400`},callback:{stroke:`stroke-cyan-500`,fill:`fill-cyan-500`,dash:`6 5`},message:{stroke:`stroke-amber-500`,fill:`fill-amber-500`,dash:`2 5`}},v={scheduler:`border-violet-200 bg-violet-50 text-violet-700`,console:`border-indigo-200 bg-indigo-50 text-indigo-700`,task:`border-amber-200 bg-amber-50 text-amber-700`,process:`border-dashed border-neutral-300 bg-neutral-50 text-neutral-500`,streaming:`border-emerald-200 bg-white font-medium text-neutral-800`,idle:`border-neutral-200 bg-white font-medium text-neutral-600`},y=300,b=4;function x(e,t){let n,r=null,i=!1,a=!1,o=()=>{clearTimeout(n),n=setTimeout(()=>{i||a||!r||r.dataset.closing!==void 0||(f(),r=null)},120)};e.onpointerenter=s=>{s.pointerType!==`touch`&&(i=!0,clearTimeout(n),!(r?.isConnected&&r.dataset.closing===void 0)&&(n=setTimeout(()=>{!e.isConnected||!i||(r=u(e,t),r.onpointerenter=()=>{a=!0,clearTimeout(n)},r.onpointerleave=()=>{a=!1,o()})},y)))},e.onpointerleave=()=>{i=!1,o()},e.onpointerdown=()=>clearTimeout(n)}function S(e){let t,n=!1;e.classList.add(`cursor-grab`),e.onpointerdown=r=>{n=!1,!(r.button!==0||r.pointerType===`touch`||r.target.closest(`[data-node], path`))&&(t={x:r.clientX,y:r.clientY,left:e.scrollLeft,top:e.scrollTop},e.setPointerCapture(r.pointerId),e.classList.add(`cursor-grabbing`,`select-none`))},e.onpointermove=r=>{if(!t)return;let i=r.clientX-t.x,a=r.clientY-t.y;(Math.abs(i)>b||Math.abs(a)>b)&&(n=!0),e.scrollLeft=t.left-i,e.scrollTop=t.top-a};let r=()=>{t=void 0,e.classList.remove(`cursor-grabbing`,`select-none`)};e.onpointerup=r,e.onpointercancel=r,e.addEventListener(`click`,e=>{n&&(e.stopPropagation(),e.preventDefault())},!0)}function C(c,u,f){let y=`sessions`,b=`active`,C={sessions:[],runs:[],messages:[]},w=``,T=e(async()=>{let e=b,t=await d(`/api/activity?scope=${e}`,`Failed to load activity`);if(!t.ok){w=``,c.replaceChildren(s(`p`,`p-4 text-[13px] text-red-600`,t.error));return}if(e!==b)return;let n=JSON.stringify(t.value);if(`${e}:${n}`===w)return;w=`${e}:${n}`;let r=t.value;C={...r,sessions:r.sessions.map(e=>({...e,title:l(e.title)}))},E()});function E(){let e=r(n([[`Sessions`,`sessions`,p],[`Relationships`,`dependencies`,m]],y,e=>{y=e,E()}),s(`span`,`ml-auto text-[11.5px] text-neutral-400`,`${C.sessions.length} sessions · ${C.runs.length} runs`),n([[`Current`,`active`],[`Last 24h`,`recent`]],b,e=>{b=e,E(),T()})),t=s(`div`,`automation-list min-h-0 flex-1 overflow-auto`);y===`sessions`?D(t):k(t),c.replaceChildren(e,t)}function D(e){let t=document.createElement(`table`);t.className=`w-full table-fixed text-left text-[12.5px]`,t.innerHTML=`<thead class="sticky top-0 bg-neutral-50 text-[10.5px] uppercase tracking-wide text-neutral-400 shadow-[inset_0_-1px_0_var(--color-neutral-200)]"><tr>
2
- <th class="w-[50%] px-4 py-2 font-semibold md:w-[34%]">Session</th>
3
- <th class="hidden w-[38%] px-2 py-2 font-semibold md:table-cell">Project</th>
4
- <th class="px-2 py-2 font-semibold md:w-[14%]">State</th>
5
- <th class="px-2 py-2 font-semibold">In state</th></tr></thead>`;let n=document.createElement(`tbody`);for(let e of C.sessions){let t=document.createElement(`tr`);t.className=`cursor-pointer border-b border-neutral-100 transition-colors hover:bg-neutral-50`,t.onclick=()=>u(e.id);let r=s(`button`,`block w-full cursor-pointer truncate text-left font-medium text-neutral-800`,e.title??o(e.cwd));r.setAttribute(`type`,`button`),r.title=`${e.id}\n${e.cwd}`,r.onclick=t=>{t.stopPropagation(),u(e.id)},t.append(s(`td`,`px-4 py-2.5`,r,s(`div`,`truncate font-mono text-[11px] text-neutral-400 md:hidden`,e.cwd||`-`)),s(`td`,`hidden truncate px-2 py-2.5 font-mono text-[11.5px] text-neutral-500 md:table-cell`,e.cwd||`-`),s(`td`,`px-2 py-2.5`,O(e.state)),s(`td`,`px-2 py-2.5 font-mono text-[11.5px] text-neutral-400`,g(e.stateSince))),n.append(t)}t.append(n),e.append(t),C.sessions.length||e.append(s(`div`,`p-4`,a(b===`active`?`No active sessions.`:`No sessions in the last 24 hours.`)))}function O(e){return e===`streaming`?t(e,`bg-emerald-50 text-emerald-700 ring-emerald-200`,`animate-pulse bg-emerald-500`):t(e,`bg-neutral-100 text-neutral-600 ring-neutral-200`,`bg-neutral-300`)}function k(e){let t=new Map,n=[],r=(e,n,r,i)=>{t.has(e)||t.set(e,{id:e,label:n,kind:r,state:i})};for(let e of C.sessions)r(e.id,e.title??e.id.slice(0,10),`session`,e.state);for(let e of C.runs){let t=e.invokedBySessionId??`scheduler`,i=e.targetSessionId??(e.context.definition.action.type===`bash`?`process:${e.id}`:`task:${e.id}`);r(t,t===`scheduler`?`Scheduler`:t.slice(0,10),t===`scheduler`?`scheduler`:`session`),r(i,e.targetSessionId?e.targetSessionId.slice(0,10):e.context.definition.action.type===`bash`?`Bash process`:`Task`,e.targetSessionId?`session`:e.context.definition.action.type===`bash`?`process`:`task`),n.push({from:t,to:i,run:e,kind:`invocation`}),e.callbackSessionId&&(r(e.callbackSessionId,e.callbackSessionId.slice(0,10),`session`),n.push({from:i,to:e.callbackSessionId,run:e,kind:`callback`}))}for(let e of C.messages){if(!e.fromSessionId||!e.toSessionId||e.fromSessionId===e.toSessionId)continue;let t=C.runs.find(t=>t.id===e.runId);if(t){for(let t of[e.fromSessionId,e.toSessionId])r(t,t===`console`?`Console`:t.slice(0,10),t===`console`?`console`:`session`);n.push({from:e.fromSessionId,to:e.toSessionId,run:t,kind:`message`})}}if(!t.size){e.append(s(`div`,`p-4`,a(b===`active`?`No active dependencies.`:`No dependencies in the last 24 hours.`)));return}let i=[...t.values()],o=new Set(n.map(e=>e.to)),c=new Map,l=i.filter(e=>!o.has(e.id)).map(e=>e.id);l.length||l.push(i[0].id);for(let e of l)c.set(e,0);for(;l.length;){let e=l.shift();for(let t of n)t.from!==e||c.has(t.to)||(c.set(t.to,c.get(e)+1),l.push(t.to))}for(let e of i)c.has(e.id)||c.set(e.id,0);let d=new Map;for(let e of i){let t=c.get(e.id);d.set(t,[...d.get(t)??[],e])}let p=Math.max(...d.keys())+1,m=Math.max(...[...d.values()].map(e=>e.length)),g=n.some(e=>e.from!==e.to&&c.get(e.from)===c.get(e.to))?40:0,y=56+p*180+(p-1)*110+g,w=56+m*46+(m-1)*30,T=new Map;for(let[e,t]of d)t.forEach((t,n)=>T.set(t.id,{x:28+e*290+90,y:28+n*76+23}));let E=s(`div`,`relative`);E.style.width=`${y}px`,E.style.height=`${w}px`;let D=h(`svg`,{width:String(y),height:String(w),viewBox:`0 0 ${y} ${w}`,class:`absolute inset-0`}),O=h(`defs`);for(let[e,t]of Object.entries(_)){let n=h(`marker`,{id:`activity-arrow-${e}`,viewBox:`0 0 10 10`,refX:`9`,refY:`5`,markerWidth:`6`,markerHeight:`6`,orient:`auto-start-reverse`});n.append(h(`path`,{d:`M 0 0 L 10 5 L 0 10 z`,class:t.fill})),O.append(n)}D.append(O);let k=new Set;for(let e of n){let t=`${e.from}→${e.to}:${e.kind}`;if(k.has(t))continue;k.add(t);let n=T.get(e.from),r=T.get(e.to),i=n.x===r.x?`M ${n.x+90} ${n.y} C ${n.x+90+46} ${n.y}, ${r.x+90+46} ${r.y}, ${r.x+90} ${r.y}`:(()=>{let e=r.x>n.x,t=n.x+(e?90:-90),i=r.x+(e?-90:90),a=Math.max(36,Math.abs(i-t)*.45)*(e?1:-1);return`M ${t} ${n.y} C ${t+a} ${n.y}, ${i-a} ${r.y}, ${i} ${r.y}`})(),a=_[e.kind],o=h(`path`,{d:i,fill:`none`,class:`cursor-pointer ${a.stroke}`,"stroke-width":`1.5`,"marker-end":`url(#activity-arrow-${e.kind})`});a.dash&&o.setAttribute(`stroke-dasharray`,a.dash),o.onclick=()=>f(e.run.id),D.append(o)}E.append(D);for(let e of i){let t=T.get(e.id),n=e.kind===`session`?e.state===`streaming`?v.streaming:v.idle:v[e.kind],r=s(e.kind===`session`?`button`:`div`,`absolute flex items-center gap-2 rounded-xl border px-3 text-left text-[11.5px] shadow-xs ${n} ${e.kind===`session`?`cursor-pointer hover:bg-neutral-50`:`cursor-default`}`,...e.kind===`session`?[s(`span`,`inline-block h-1.5 w-1.5 flex-none rounded-full ${e.state===`streaming`?`animate-pulse bg-emerald-500`:`bg-neutral-300`}`)]:[],s(`span`,`min-w-0 truncate`,e.label));if(r.dataset.node=e.id,r.style.left=`${t.x-90}px`,r.style.top=`${t.y-23}px`,r.style.width=`180px`,r.style.height=`46px`,r.title=e.label===e.id?e.id:`${e.label}\n${e.id}`,e.kind===`session`){r.setAttribute(`type`,`button`),r.onclick=()=>u(e.id);let t=C.sessions.find(t=>t.id===e.id);t&&t.createdAt!==null&&x(r,{...t,createdAt:t.createdAt})}E.append(r)}S(e);let A=e=>s(`span`,`inline-block h-2 w-2 rounded-full ${e}`),j=e=>s(`span`,`inline-block h-0 w-6 border-t-2 ${e}`);e.append(s(`div`,`w-max p-4`,E),s(`div`,`flex flex-wrap gap-x-5 gap-y-1.5 border-t border-neutral-200 bg-neutral-50/60 px-4 py-2 text-[11px] text-neutral-500`,s(`span`,`inline-flex items-center gap-2`,j(`border-solid border-neutral-400`),`task invocation`),s(`span`,`inline-flex items-center gap-2 text-cyan-700`,j(`border-dashed border-cyan-500`),`callback`),s(`span`,`inline-flex items-center gap-2 text-amber-700`,j(`border-dotted border-amber-500`),`control message`),s(`span`,`inline-flex items-center gap-1.5`,A(`bg-emerald-500`),`streaming`),s(`span`,`inline-flex items-center gap-1.5`,A(`bg-neutral-300`),`idle`)))}let A=i(c,e=>{e===`dependencies`?y=`dependencies`:e===`sessions`&&(y=`sessions`),T()},()=>{c.replaceChildren(),C={sessions:[],runs:[],messages:[]},w=``});return Object.assign(A,{refresh(){A.visible&&T()}})}export{C as createActivityView};
@@ -1 +0,0 @@
1
- import{E as e,K as t,M as n,O as r,P as i,V as a,X as o,Z as s,at as c,et as l,it as u,q as d,tt as f,w as p}from"./index-BQo-haPN.js";var m=e=>e.public&&e.token?`/p/${e.slug}-${e.token}/`:`/boards/${e.slug}/`;function h(h,g){let _=[],v=``,y=o(`header`,`pagehead sticky top-[-8px] z-30 max-md:hidden`,n(`Boards`)),b=o(`div`,`px-4 pb-5 pt-1`);h.append(o(`div`,`min-h-0 flex-1 overflow-y-auto`,y,b));async function x(e,t){let n=await c(`/api/boards/${e.slug}`,{public:t},`PATCH`);if(n.ok)return e.token=(await n.json().catch(()=>({}))).token??``,T();v=await l(n,`Could not change ${e.slug}`),await E()}async function S(e){v=await u(`/api/boards/${e}`,`DELETE`,`Could not delete ${e}`)??``,await E()}function C(e){let t=o(`div`,`group flex flex-col gap-1 py-2.5`),n=document.createElement(`a`);n.className=`min-w-0 flex-1 truncate text-[13px] font-medium text-neutral-800 hover:text-indigo-700 max-md:basis-full`,n.href=m(e),n.target=`_blank`,n.rel=`noreferrer`,n.textContent=e.title;let r=o(`div`,`flex flex-wrap items-center gap-x-2 gap-y-1`,n),c=a(``,``,e.public,t=>{e.public=t,x(e,t)});c.className=`flex flex-none cursor-pointer items-center gap-1.5 text-[11.5px] text-neutral-500`,c.append(o(`span`,``,`Public`));let l=p(`Delete`,i());l.title=`Renames the folder on disk; nothing is erased`,l.onclick=()=>void S(e.slug);let u=d(i(`hover:text-indigo-600`),()=>`${location.origin}${m(e)}`);u.title=`Copy the board's link`,r.append(o(`div`,`ml-auto flex flex-none items-center gap-2`,c,u,l)),t.append(r);let f=o(`div`,`flex flex-wrap items-center gap-x-3 gap-y-1 text-[11.5px] text-neutral-500`);f.append(o(`span`,`min-w-0 break-all font-mono text-neutral-400`,e.slug)),e.description&&f.append(o(`span`,`min-w-0 truncate`,e.description)),f.append(o(`span`,`flex-none text-neutral-400`,s(Date.parse(e.updatedAt))));for(let t of e.sessions){let e=p(t.slice(0,8),`flex-none cursor-pointer font-mono text-indigo-600 hover:underline`);e.title=`Open session ${t}`,e.onclick=()=>g(t),f.append(e)}if(e.public&&e.token){let t=o(`span`,`min-w-0 break-all font-mono text-neutral-400`,m(e));t.title=`Public URL — anyone holding this link can read the board, no password`,f.append(t)}return t.append(f),t}let w=[{title:`Public`,hint:`Readable at /p/<slug>-<token>/ by anyone holding the link.`,wanted:!0},{title:`Private`,hint:`Opened from here; the address that opens is a temporary link, good for 8 hours.`,wanted:!1}];function T(){let t=o(`div`,`mx-auto flex w-full max-w-3xl flex-col gap-4`);_.length===0&&t.append(r(`No boards yet. Ask an agent to build one — it writes a folder under ~/.pier/boards.`));for(let{title:n,hint:r,wanted:i}of w){let a=_.filter(e=>e.public===i);a.length!==0&&t.append(e(n,r,o(`div`,`flex flex-col divide-y divide-neutral-200/70`,...a.map(C))))}b.replaceChildren(...v?[o(`p`,`mb-3 text-[13px] text-red-600`,v)]:[],t)}async function E(){let e=await f(`/api/boards`,`Could not load boards`);if(!e.ok)return v=e.error,T();_=e.value,T(),v=``}let D=t(h,()=>void E());return window.addEventListener(`focus`,()=>{D.visible&&E()}),D}export{h as createBoardsView};
@@ -1,4 +0,0 @@
1
- import{G as e,J as t,K as n,M as r,O as i,X as a,Z as o,a as s,ct as c,f as l,g as u,h as d,ht as f,l as p,lt as m,m as h,mt as ee,nt as g,p as _,u as te,v as ne,w as re,y as ie}from"./index-BQo-haPN.js";var ae=[[`path`,{d:`M12 5v14`}],[`path`,{d:`m19 12-7 7-7-7`}]],oe=[[`path`,{d:`M12 22v-6`}],[`path`,{d:`M12 8V2`}],[`path`,{d:`M4 12H2`}],[`path`,{d:`M10 12H8`}],[`path`,{d:`M16 12h-2`}],[`path`,{d:`M22 12h-2`}],[`path`,{d:`m15 19-3-3-3 3`}],[`path`,{d:`m15 5-3 3-3-3`}]],se=[[`path`,{d:`M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z`}]],ce=[[`path`,{d:`M15 6a9 9 0 0 0-9 9V3`}],[`circle`,{cx:`18`,cy:`6`,r:`3`}],[`circle`,{cx:`6`,cy:`18`,r:`3`}]],v=[[`path`,{d:`M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8`}],[`path`,{d:`M3 3v5h5`}]],y=e=>{let t=new Date(e),n=e=>String(e).padStart(2,`0`);return`${t.getFullYear()}-${n(t.getMonth()+1)}-${n(t.getDate())} ${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`},le=e=>[e.subject,e.body,[[e.author,e.email?`<${e.email}>`:``].filter(Boolean).join(` `),e.at?y(e.at):``].filter(Boolean).join(` · `)].filter(Boolean).join(`
2
-
3
- `),b=null;function x(){b&&(b.remove(),b=null,document.removeEventListener(`pointerdown`,x,!0),window.removeEventListener(`scroll`,x,!0))}function ue(e,t){e.onmouseenter=()=>{x();let n=t();if(!n)return;b=a(`div`,`pointer-events-none fixed z-50 max-w-96 whitespace-pre-wrap rounded-lg bg-neutral-800/95 px-3 py-2 font-sans text-[11.5px] leading-snug text-neutral-100 shadow-lg`),b.textContent=n,document.body.append(b),document.addEventListener(`pointerdown`,x,!0),window.addEventListener(`scroll`,x,!0);let r=e.getBoundingClientRect();b.style.left=`${Math.max(8,Math.min(r.left,window.innerWidth-b.offsetWidth-8))}px`,b.style.top=r.bottom+6+b.offsetHeight>window.innerHeight?`${r.top-b.offsetHeight-6}px`:`${r.bottom+6}px`},e.onmouseleave=x}function S(e,t,n){let r=a(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-1 text-left hover:bg-neutral-100`,c(ee,`h-3 w-3 text-indigo-600 ${e.value===t?``:`invisible`}`),a(`span`,`inline-flex flex-none items-center gap-1.5 font-mono text-[11.5px] text-neutral-800`,e.label??e.value));return e.subject&&r.append(a(`span`,`min-w-0 truncate text-[11px] text-neutral-400`,e.subject)),e.author&&r.append(a(`span`,`ml-auto flex-none pl-1.5 text-[10.5px] text-neutral-400`,`${e.author} · ${e.at?o(e.at):``}`)),ue(r,()=>le(e)),r.onclick=()=>n(e.value),r}function de(e,t,n,r,i){let{base:o,head:s}=n,u=()=>s&&o===`${s}~1`?s:``,d=u()?`commit`:`compare`,f=t.commits[0],p=e=>[...e?[{value:``,label:`Working tree`,subject:`the files on disk, uncommitted changes included`}]:[],{...f,value:`HEAD`},...t.refs.map(e=>({value:e.name,subject:e.subject})),...t.commits.map(e=>({...e,value:e.hash}))],m=a(`div`,`flex w-[38rem] max-w-full flex-col`);function h(){let e=(e,t)=>{let n=a(`button`,`cursor-pointer rounded-full px-2.5 py-0.5 text-[11.5px] ${d===t?`bg-indigo-50 font-medium text-indigo-700`:`text-neutral-500 hover:bg-neutral-100`}`,e);return n.onclick=()=>{d=t,h()},n},n=(e,t,n,r)=>a(`div`,`flex min-w-0 flex-1 flex-col`,a(`div`,`flex-none px-2 pb-0.5 text-[10.5px] font-semibold uppercase tracking-wide text-neutral-400`,e),a(`div`,`min-h-0 flex-1 overflow-y-auto`,...t.map(e=>S(e,n,r)))),l=d===`compare`?a(`div`,`flex h-72 min-h-0 divide-x divide-neutral-200 pb-1`,n(`base`,p(!1),o,e=>{o=e,h(),r(o,s)}),n(`to`,p(!0),s,e=>{s=e,h(),r(o,s)})):a(`div`,`flex h-72 min-h-0 flex-col overflow-y-auto pb-1`,...t.commits.map(e=>S({value:e.hash,...e},u(),e=>{o=`${e}~1`,s=e,te(),r(o,s)}))),f=a(`div`,`flex-none border-t border-neutral-200`,S({value:i,label:`${i} ↔ Working tree`,subject:`back to the default: the main line's tip vs the files on disk`},o===i&&s===``?i:``,()=>{o=i,s=``,h(),r(o,s)}));f.querySelector(`button > span`).prepend(c(v)),m.replaceChildren(a(`div`,`flex flex-none items-center gap-1 border-b border-neutral-200 px-2 py-1.5`,e(`Compare two points`,`compare`),e(`One commit`,`commit`)),l,...d===`compare`?[f]:[])}h(),l(e,m)}var fe=/\.(png|jpe?g|gif|webp|avif|bmp|svg)$/i,C={A:`text-emerald-600`,M:`text-amber-600`,D:`text-red-600`},pe=30,w=`pier.filesPrefs`,T=50,E=()=>{try{let e=JSON.parse(localStorage.getItem(w)??`{}`);return e&&typeof e==`object`?e:{}}catch{return{}}},me=e=>{let t=e?E()[e]:void 0;return typeof t?.cwd==`string`&&typeof t.base==`string`&&typeof t.head==`string`?{cwd:t.cwd,base:t.base,head:t.head}:null},he=(e,t)=>{let n=E();delete n[e],n[e]=t;let r=Object.keys(n);for(let e of r.slice(0,Math.max(0,r.length-T)))delete n[e];try{localStorage.setItem(w,JSON.stringify(n))}catch(e){console.warn(`Files: could not remember this folder`,e)}},ge=`flex min-w-0 flex-none items-center gap-1.5 rounded-full px-2.5 py-1 font-mono text-[11.5px]`,_e=(e,t=`indigo`)=>re(e,`${ge} cursor-pointer truncate text-left ${t===`indigo`?`bg-indigo-50 font-medium text-indigo-700 hover:bg-indigo-100`:`bg-neutral-100 text-neutral-600 hover:bg-neutral-200`}`);function D(o,l,ee,te){let v=``,y=``,b={branch:null,refs:[],commits:[],worktrees:[]},x=`HEAD`,S=``,w=new Map,T=!0,E=!1,D=[],O=new Set,k=null,A=null,j=null,ve=a(`header`,`pagehead flex-nowrap`),M=a(`div`,`${u} flex-col items-stretch gap-1.5 px-3 py-2.5`),N=a(`div`,`min-h-0 flex-1 overflow-y-auto px-1.5 py-1.5`),ye=a(`aside`,`${ie} w-64 flex-none text-[12.5px] leading-5 max-md:h-1/2 max-md:w-full`,M,N),P=a(`div`,`${u} flex-wrap gap-2 px-4 py-2.5 font-mono text-[11.5px] text-neutral-500`),F=a(`div`,`h-full min-w-0 overflow-auto`),I=a(`div`,`pointer-events-none absolute inset-y-1 right-0.5 z-20 hidden w-2`),be=a(`div`,`relative min-h-0 flex-1`,F,I),xe=a(`section`,`${ie} min-h-0 min-w-0 flex-1`,P,be);o.append(ve,a(`div`,`flex min-h-0 flex-1 gap-3 px-4 pb-4 pt-1 max-md:flex-col`,ye,xe));let L=(e,t)=>`/api/explorer/${e}?${new URLSearchParams({root:v,...t})}`,R=(e,t)=>`/api/fs/${e}?${new URLSearchParams({root:v,...t})}`,z=(e,t=`text-neutral-400`)=>a(`p`,`px-3 py-2 text-[12.5px] ${t}`,e),B=e=>a(`div`,`flex h-full items-center justify-center p-6`,i(e)),Se=()=>T&&b.branch!==null,Ce=e=>{let t=0;for(let n of w.keys())n.startsWith(`${e}/`)&&t++;return t};function we(e,t){let n=new Set(t.map(e=>e.name)),r=e?`${e}/`:``;for(let[e,i]of w){if(i.status!==`D`||!e.startsWith(r))continue;let a=e.slice(r.length).split(`/`)[0];n.has(a)||(n.add(a),t.push({name:a,dir:e.slice(r.length).includes(`/`)}))}let i=t;return Se()&&(i=t.filter(e=>e.dir?Ce(`${r}${e.name}`)>0:w.has(`${r}${e.name}`))),i.sort((e,t)=>Number(t.dir)-Number(e.dir)||e.name.localeCompare(t.name)),i.map(e=>e.dir?Ee(`${r}${e.name}`,e.name):De(`${r}${e.name}`,e.name))}async function Te(e,t){try{let{entries:n}=await g(R(`ls`,{path:t}),`could not list this folder`);e.replaceChildren(...we(t,n)),e.childElementCount||e.append(z(Se()?`No changes.`:`Empty.`))}catch(n){let r=we(t,[]);r.length?e.replaceChildren(...r):e.replaceChildren(z(String(n),`text-red-600`))}}function Ee(e,n){let r=Ce(e),i=[a(`span`,`truncate`,n)];r&&i.push(a(`span`,`ml-auto flex-none rounded-full bg-amber-100/80 px-1.5 font-mono text-[10px] font-semibold text-amber-700`,String(r)));let{el:o,summary:s}=t(``,i);s.className+=` rounded-lg px-2 py-1 transition-colors hover:bg-neutral-100`;let c=a(`div`,`pl-3`);o.append(c);let l=!1,u=()=>{l||(l=!0,c.append(z(`…`)),Te(c,e))};return o.ontoggle=()=>{O[o.open?`add`:`delete`](e),o.open&&u()},((b.branch?T:!e.includes(`/`))&&!E||O.has(e))&&(o.open=!0,u()),o}function V(e){A?.classList.remove(`bg-indigo-50`,`font-medium`,`text-indigo-700`),A=e,e.classList.add(`bg-indigo-50`,`font-medium`,`text-indigo-700`)}function De(e,t){let n=w.get(e)?.status,r=a(`button`,`flex w-full cursor-pointer items-center gap-1.5 rounded-lg py-1 pl-6 pr-2 text-left transition-colors hover:bg-neutral-100`,a(`span`,`truncate`,t));return n&&r.append(a(`span`,`ml-auto flex-none font-mono text-[10.5px] font-semibold ${C[n]??`text-neutral-500`}`,n)),r.title=e,e===k&&V(r),e===j&&(j=null,k=e,V(r),J(e)),r.onclick=()=>{V(r),k=e,J(e)},r}let H=()=>void Te(N,``),U=(e,t)=>[...e?[a(`span`,`flex-none font-mono text-[10.5px] font-semibold text-emerald-600`,`+${e}`)]:[],...t?[a(`span`,`flex-none font-mono text-[10.5px] font-semibold text-red-600`,`−${t}`)]:[]];function W(t,n=!1,r){if(P.classList.toggle(`hidden`,!t),P.classList.toggle(`flex`,!!t),!t)return P.replaceChildren();let i=w.get(t),o=[a(`span`,`min-w-0 truncate`,t)];i&&(o.push(a(`span`,`flex-none font-semibold ${C[i.status]??``}`,i.status)),o.push(...U(i.add,i.del)));let s=a(`div`,`ml-auto flex flex-none items-center gap-2`);if(r&&s.append(r),n){let n=document.createElement(`a`);n.className=`btn flex-none text-[12px]`,n.href=R(`file`,{path:t}),n.download=e(t),n.textContent=`Download`,s.append(n)}P.replaceChildren(...o,s)}function Oe(e){let t=0;for(;t<e.length;){if(e[t].tone!==`del`){t++;continue}let n=t;for(;t<e.length&&e[t].tone===`del`;)t++;let r=t;for(;t<e.length&&e[t].tone===`add`;)t++;let i=Math.min(r-n,t-r);for(let t=0;t<i;t++){let i=e[n+t],a=e[r+t],o=Math.min(i.text.length,a.text.length),s=0;for(;s<o&&i.text[s]===a.text[s];)s++;let c=0;for(;c<o-s&&i.text[i.text.length-1-c]===a.text[a.text.length-1-c];)c++;s+c!==0&&(i.mark=[s,i.text.length-c],a.mark=[s,a.text.length-c])}}}let G=Ie();function K(){I.classList.add(`hidden`),I.replaceChildren(),G.set([],[])}let ke=0,q=e=>e===ke;function J(e){let t=++ke;return w.has(e)?Le(e,t):Ae(e,t)}async function Ae(e,t){if(!q(t))return;K();let n=R(`file`,{path:e}),r=a(`div`,`min-w-0`);if(F.classList.remove(`flex`,`flex-col`),W(e,!0),F.replaceChildren(r),fe.test(e)){let e=a(`img`,`max-w-full p-4`);e.src=n,e.onerror=()=>r.replaceChildren(z(`Could not load image.`,`text-red-600`)),r.append(e);return}if(e.toLowerCase().endsWith(`.pdf`)){let e=document.createElement(`iframe`);e.className=`h-full w-full`,e.src=n,F.replaceChildren(e),F.classList.add(`flex`,`flex-col`);return}r.append(z(`…`));let i=await fetch(n);if(!q(t))return;if(!i.ok){let{error:e}=await i.json().catch(()=>({}));r.replaceChildren(z(e??`HTTP ${i.status}`,`text-red-600`));return}if(!(i.headers.get(`content-type`)??``).startsWith(`text/plain`)){r.replaceChildren(z(`Binary file — use Download.`));return}let o=h(await i.text());if(!q(t))return;let s=await d(e);q(t)&&r.replaceChildren(_(o,s))}function je(e){let t=[],n=0,r=0,i=!1,a=e.split(`
4
- `);a.at(-1)===``&&a.pop();for(let e of a){let a=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(e);if(a){i&&t.push({nums:[``,``],text:`⋯`,tone:``}),[n,r]=[Number(a[1]),Number(a[2])],i=!0;continue}!i||e.startsWith(`\\`)||(e.startsWith(`+`)?t.push({nums:[``,r++],text:e.slice(1),tone:`add`}):e.startsWith(`-`)?t.push({nums:[n++,``],text:e.slice(1),tone:`del`}):t.push({nums:[n++,r++],text:e.slice(1),tone:``}))}return t}function Me(e){let t=[];for(let n=0;n<e.length;n++){let r=e[n].tone;if(!r)continue;let i=t.at(-1);i&&i.end===n-1?(i.end=n,(r===`add`&&i.tone===`del`||r===`del`&&i.tone===`add`)&&(i.tone=`mixed`)):t.push({start:n,end:n,tone:r})}return t}let Ne={add:`bg-emerald-400`,del:`bg-red-400`,mixed:`bg-amber-400`};function Pe(e,t,n){I.replaceChildren(...e.map(e=>{let r=a(`button`,`map-mark pointer-events-auto absolute left-1/2 w-1 -translate-x-1/2 cursor-pointer rounded-full ${Ne[e.tone]}`);return r.style.top=`${e.start/t*100}%`,r.style.height=`${Math.max(.4,(e.end-e.start+1)/t*100)}%`,r.title=`line ${Fe(n,e.start)}`,r.onclick=()=>n[e.start].scrollIntoView({block:`center`}),r})),I.classList.remove(`hidden`)}let Fe=(e,t)=>e[t]?.querySelector(`span`)?.textContent?.trim()||String(t+1);function Ie(){let e=[],t=[],n=-1,r=a(`span`,`flex-none text-[10.5px] text-neutral-400`),i=i=>{e.length&&(n=(n+i+e.length)%e.length,t[e[n].start].scrollIntoView({block:`center`}),r.textContent=`${n+1}/${e.length}`)},l=()=>e.length>0&&!o.classList.contains(`hidden`),u=(e,t,n,r)=>{let o=a(`button`,`icon-btn flex-none`,c(e));return o.onclick=()=>i(t),s(o,r,n,()=>i(t),l),o};return{el:a(`div`,`flex flex-none items-center gap-1`,r,u(f,-1,`Previous change`,[`p`,`k`]),u(ae,1,`Next change`,[`n`,`j`])),set(i,a){e=i,t=a,n=-1,r.textContent=`${i.length} change${i.length===1?``:`s`}`}}}async function Le(e,t){K(),F.classList.remove(`flex`,`flex-col`);let n=w.get(e)?.status!==`D`;W(e,n),F.replaceChildren(z(`…`));try{let{diff:r}=await g(L(`diff`,{base:x,head:S,file:e,context:`99999`}),`could not read this diff`);if(!q(t))return;if(!r||/^Binary files /m.test(r))return Ae(e,t);let i=je(r);Oe(i);let a=await d(e);if(!q(t))return;let o=_(i,a),s=[...o.children],c=Me(i);G.set(c,s),W(e,n,G.el),F.replaceChildren(o),Pe(c,i.length,s)}catch(n){if(!q(t))return;W(e),F.replaceChildren(z(String(n),`text-red-600`))}}let Y=()=>S&&x===`${S}~1`?S:``,Re=()=>b.commits.find(e=>e.hash===Y())?.subject??``,ze=()=>Y()?`${Y()} — ${Re()||`commit`}`:`${x} ↔ ${S||`working tree`}`,Be=(e,t,n,r)=>{let i=a(`button`,`icon-btn ${n?`bg-indigo-50 text-indigo-700 hover:bg-indigo-100 hover:text-indigo-700`:``}`);return i.append(c(e)),i.title=t,i.setAttribute(`aria-label`,t),i.setAttribute(`aria-pressed`,String(n)),i.onclick=r,i};function X(){let e=Be(se,`Show only files that differ`,T,()=>{T=!T,X(),H()}),t=Be(oe,E?`Restore folders`:`Collapse all folders`,E,()=>{if(E=!E,E)D=[...O],O.clear();else for(let e of D)O.add(e);X(),H()});if(!b.branch){M.replaceChildren(a(`div`,`flex items-center gap-1.5`,a(`span`,`min-w-0 flex-1 text-[11px] leading-snug text-neutral-400`,`Not a git repository — nothing to compare.`),t));return}let n=re(ze(),`${ne} font-mono`);if(Y()){let e=b.commits.find(e=>e.hash===Y());ue(n,()=>e?le(e):``)}else n.title=`Choose what to diff`;n.onclick=async()=>{await We(),de(n,b,{base:x,head:S},(e,t)=>{x=e,S=t,Z()},$())};let[r,i]=[0,0];for(let e of w.values())r+=e.add,i+=e.del;M.replaceChildren(n,a(`div`,`flex items-center gap-1.5 pl-0.5`,a(`span`,`text-[11px] text-neutral-400`,`${w.size} changed file${w.size===1?``:`s`}`),...U(r,i),a(`span`,`ml-auto`),e,t))}let Ve=()=>{y&&v&&he(y,{cwd:v,base:x,head:S})},He=e=>{let t=e.replace(/~\d+$/,``);return!t||t===`HEAD`||b.refs.some(e=>e.name===t)||b.commits.some(e=>e.hash===t)};async function Ue(){if(w=new Map,!b.branch)return;let{files:e}=await g(L(`diff`,{base:x,head:S}),`could not read what changed`);for(let t of e)w.set(t.path,{status:t.status,add:t.add,del:t.del});if(w.size<=pe)for(let e of w.keys()){let t=e.split(`/`);for(let e=1;e<t.length;e++)O.add(t.slice(0,e).join(`/`))}}async function Z(){Ve();try{await Ue()}catch(e){X(),N.replaceChildren(z(String(e),`text-red-600`));return}X(),H(),k&&J(k)}function Q(){let t=_e(``,`neutral`);t.className+=` max-w-72`,t.append(a(`span`,`truncate max-md:hidden`,v||`Choose a folder…`),a(`span`,`truncate md:hidden`,v?e(v):`Choose a folder…`)),t.title=v?`${v} — switch folder`:`Choose a folder`,t.onclick=()=>p(t,b.worktrees.map(e=>({path:e.path,...e.branch?{hint:e.branch}:{}})),v||void 0,ee);let n=a(`button`,`icon-btn ml-auto`,c(m));n.type=`button`,n.title=`Close Files`,n.setAttribute(`aria-label`,`Close Files`),n.onclick=te;let i=b.branch?[a(`span`,`${ge} bg-neutral-100 text-neutral-600`,c(ce),a(`span`,`truncate`,b.branch))]:[];ve.replaceChildren(r(`Files`),t,...i,n)}async function We(){if(v)try{b=await g(L(`git`,{}),`could not read this repository`),Q()}catch(e){F.replaceChildren(B(String(e)))}}let $=()=>b.refs.some(e=>e.name===`main`)?`main`:b.refs.some(e=>e.name===`master`)?`master`:`HEAD`;async function Ge(e){if(Q(),k=null,A=null,O.clear(),j=e||null,e){T=!1;let t=e.split(`/`);for(let e=1;e<t.length;e++)O.add(t.slice(0,e).join(`/`))}if(K(),F.classList.remove(`flex`,`flex-col`),W(),F.replaceChildren(B(`Select a file.`)),!v){M.replaceChildren(),N.replaceChildren(),F.replaceChildren(B(`No folder yet — pick one from the chip above.`));return}try{b=await g(L(`git`,{}),`could not read this repository`)}catch(e){b={branch:null,refs:[],commits:[],worktrees:[]},F.replaceChildren(B(String(e)))}let t=me(y),n=t&&t.cwd===v&&He(t.base)&&He(t.head)?t:null;x=n?.base??$(),S=n?.head??``,Q(),await Z()}return n(o,(e,t)=>{let n=l(),r=n?.id??``,i=new URLSearchParams(t).get(`select`)??void 0,a=e?.startsWith(`/`)?e:(r===y?v:``)||me(r)?.cwd||n?.cwd||``;if(a===v&&r===y&&N.childElementCount&&(!i||i===k)){Q(),We().then(Z);return}y=r,v=a,Ge(i)},()=>{K(),F.classList.remove(`flex`,`flex-col`),W(),F.replaceChildren(B(`Select a file.`))})}export{D as createExplorerView};