@timqi/pier 0.0.22 → 0.0.24

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 (87) hide show
  1. package/README.md +4 -5
  2. package/dist/agent/config.js +3 -4
  3. package/dist/boards/boards.js +6 -2
  4. package/dist/channels/config.js +1 -7
  5. package/dist/core/reply.js +1 -1
  6. package/dist/db.js +7 -0
  7. package/dist/main.js +12 -10
  8. package/dist/service.js +2 -1
  9. package/dist/settings.js +0 -26
  10. package/dist/tasks/agent.js +1 -10
  11. package/dist/tasks/callbacks.js +1 -1
  12. package/dist/tasks/groups.js +2 -1
  13. package/dist/tasks/service.js +14 -4
  14. package/dist/tasks/tool.js +1 -1
  15. package/dist/tools-task.js +4 -5
  16. package/dist/web/auth.js +1 -22
  17. package/dist/web/fs.js +2 -2
  18. package/dist/web/instance.js +3 -9
  19. package/dist/web/public/assets/{activity-Bv8fc5Wj.js → activity-DRJDNEP9.js} +2 -2
  20. package/dist/web/public/assets/activity-DRJDNEP9.js.br +0 -0
  21. package/dist/web/public/assets/activity-DRJDNEP9.js.gz +0 -0
  22. package/dist/web/public/assets/boards-ZmKTkCZ-.js +1 -0
  23. package/dist/web/public/assets/boards-ZmKTkCZ-.js.br +0 -0
  24. package/dist/web/public/assets/boards-ZmKTkCZ-.js.gz +0 -0
  25. package/dist/web/public/assets/{code-BgAXjUH3.js → code-D1-CYID1.js} +1 -1
  26. package/dist/web/public/assets/explorer-De8NHWLj.js +5 -0
  27. package/dist/web/public/assets/explorer-De8NHWLj.js.br +0 -0
  28. package/dist/web/public/assets/explorer-De8NHWLj.js.gz +0 -0
  29. package/dist/web/public/assets/index-DP1-BW-q.css +2 -0
  30. package/dist/web/public/assets/index-DP1-BW-q.css.br +0 -0
  31. package/dist/web/public/assets/index-DP1-BW-q.css.gz +0 -0
  32. package/dist/web/public/assets/index-DuhZm2hI.js +83 -0
  33. package/dist/web/public/assets/index-DuhZm2hI.js.br +0 -0
  34. package/dist/web/public/assets/index-DuhZm2hI.js.gz +0 -0
  35. package/dist/web/public/assets/runs-C7BNc137.js +1 -0
  36. package/dist/web/public/assets/runs-C7BNc137.js.br +0 -0
  37. package/dist/web/public/assets/runs-C7BNc137.js.gz +0 -0
  38. package/dist/web/public/assets/settings-BcWcHc7F.js +6 -0
  39. package/dist/web/public/assets/settings-BcWcHc7F.js.br +0 -0
  40. package/dist/web/public/assets/settings-BcWcHc7F.js.gz +0 -0
  41. package/dist/web/public/assets/task-runs-CJz8WLzY.js +3 -0
  42. package/dist/web/public/assets/task-runs-CJz8WLzY.js.br +0 -0
  43. package/dist/web/public/assets/task-runs-CJz8WLzY.js.gz +0 -0
  44. package/dist/web/public/assets/tasks-DQnZGbBg.js +4 -0
  45. package/dist/web/public/assets/tasks-DQnZGbBg.js.br +0 -0
  46. package/dist/web/public/assets/tasks-DQnZGbBg.js.gz +0 -0
  47. package/dist/web/public/index.html +2 -10
  48. package/dist/web/public/index.html.br +0 -0
  49. package/dist/web/public/index.html.gz +0 -0
  50. package/dist/web/server.js +4 -4
  51. package/docs/deploy.md +3 -8
  52. package/package.json +2 -9
  53. package/skills/pier-tasks/SKILL.md +4 -2
  54. package/dist/web/public/assets/__vite-browser-external-2447137e-BvRk9kiK.js +0 -0
  55. package/dist/web/public/assets/activity-Bv8fc5Wj.js.br +0 -0
  56. package/dist/web/public/assets/activity-Bv8fc5Wj.js.gz +0 -0
  57. package/dist/web/public/assets/boards-BRKjgEYX.js +0 -1
  58. package/dist/web/public/assets/boards-BRKjgEYX.js.br +0 -0
  59. package/dist/web/public/assets/boards-BRKjgEYX.js.gz +0 -0
  60. package/dist/web/public/assets/explorer-Cq_Ertso.js +0 -5
  61. package/dist/web/public/assets/explorer-Cq_Ertso.js.br +0 -0
  62. package/dist/web/public/assets/explorer-Cq_Ertso.js.gz +0 -0
  63. package/dist/web/public/assets/ghostty-web-CZHJ0YFi.js +0 -13
  64. package/dist/web/public/assets/ghostty-web-CZHJ0YFi.js.br +0 -0
  65. package/dist/web/public/assets/ghostty-web-CZHJ0YFi.js.gz +0 -0
  66. package/dist/web/public/assets/index-BH7ir5I4.css +0 -2
  67. package/dist/web/public/assets/index-BH7ir5I4.css.br +0 -0
  68. package/dist/web/public/assets/index-BH7ir5I4.css.gz +0 -0
  69. package/dist/web/public/assets/index-BrBNzaid.js +0 -79
  70. package/dist/web/public/assets/index-BrBNzaid.js.br +0 -0
  71. package/dist/web/public/assets/index-BrBNzaid.js.gz +0 -0
  72. package/dist/web/public/assets/runs-CvdNDaw8.js +0 -1
  73. package/dist/web/public/assets/runs-CvdNDaw8.js.br +0 -0
  74. package/dist/web/public/assets/runs-CvdNDaw8.js.gz +0 -0
  75. package/dist/web/public/assets/settings-BaVwkUUD.js +0 -6
  76. package/dist/web/public/assets/settings-BaVwkUUD.js.br +0 -0
  77. package/dist/web/public/assets/settings-BaVwkUUD.js.gz +0 -0
  78. package/dist/web/public/assets/task-runs-B-eoldcU.js +0 -3
  79. package/dist/web/public/assets/task-runs-B-eoldcU.js.br +0 -0
  80. package/dist/web/public/assets/task-runs-B-eoldcU.js.gz +0 -0
  81. package/dist/web/public/assets/tasks-CT4LECPQ.js +0 -4
  82. package/dist/web/public/assets/tasks-CT4LECPQ.js.br +0 -0
  83. package/dist/web/public/assets/tasks-CT4LECPQ.js.gz +0 -0
  84. package/dist/web/public/assets/terminal-C0qFDFou.js +0 -2
  85. package/dist/web/public/assets/terminal-C0qFDFou.js.br +0 -0
  86. package/dist/web/public/assets/terminal-C0qFDFou.js.gz +0 -0
  87. package/dist/web/terminal.js +0 -369
package/README.md CHANGED
@@ -17,7 +17,6 @@ versioned from `0.0.1` on — earlier databases are not migrated. Read
17
17
  ## Requirements
18
18
 
19
19
  - Node 24 or newer (`node:sqlite` is used unflagged)
20
- - Linux: Python 3, `make` and a C/C++ compiler for `node-pty`
21
20
  - A provider account (Anthropic, OpenAI, …) — configure its API key or OAuth
22
21
  login from Console → Settings → Models after signing in
23
22
  - A user-writable global npm prefix if `pier update` should update a service
@@ -61,8 +60,8 @@ an existing Pi setup:
61
60
  PI_CODING_AGENT_DIR="$HOME/.pi/agent" pier serve
62
61
  ```
63
62
 
64
- Pier exports that variable for the SDK, so anything it starts inherits it — a
65
- shell in the Web Terminal included. A second Pier launched from there with its
63
+ Pier exports that variable for the SDK, so anything it starts inherits it — an
64
+ agent's shell included. A second Pier launched from there with its
66
65
  own `PIER_HOME` derives its own agent directory rather than adopting the first
67
66
  one's; setting `PI_CODING_AGENT_DIR` again on that command line still wins.
68
67
 
@@ -92,8 +91,8 @@ Console → Settings is the normal setup path:
92
91
  Below it, the same tab switches on the command-line tools Pier manages —
93
92
  `rtk`, `rg`, `fd`, `wt`, `jq`, or a tool of your own written as a
94
93
  [ubix](https://github.com/timqi/ubix) block. A switch installs the binary
95
- into `~/.pier/tools/bin`, which is first on the PATH every session, task and
96
- terminal inherits, and a task you can read keeps them current.
94
+ into `~/.pier/tools/bin`, which is first on the PATH every session and task
95
+ inherits, and a task you can read keeps them current.
97
96
 
98
97
  On first credential access, Pier imports an existing `auth.json` into its sealed
99
98
  store and renames the source to `auth.json.imported`. Literal provider keys left
@@ -147,9 +147,8 @@ export class PiConfigStore {
147
147
  validateProviderSetup(input);
148
148
  await this.#withWrite(async () => {
149
149
  const path = join(this.agentDir, "models.json");
150
- const existed = await pathExists(path);
151
- const raw = await readOptional(path);
152
- const parsed = raw.trim() ? parseModels(raw) : {};
150
+ const raw = await readNullable(path);
151
+ const parsed = raw?.trim() ? parseModels(raw) : {};
153
152
  if (!parsed)
154
153
  throw new Error("models.json must be valid JSON before configuring a provider");
155
154
  const providers = { ...parsed.providers };
@@ -198,7 +197,7 @@ export class PiConfigStore {
198
197
  throw new Error(`${path} changed while provider setup was being validated`, { cause: err });
199
198
  }
200
199
  try {
201
- if (existed)
200
+ if (raw !== null)
202
201
  await atomicWrite(path, raw, 0o600);
203
202
  else
204
203
  await fs.unlink(path);
@@ -124,14 +124,18 @@ export async function listBoards(dir) {
124
124
  }
125
125
  // One board's manifest says nothing about the next one's, so the scan waits
126
126
  // once for all of them rather than once per board.
127
- const boards = await Promise.all(entries.sort().map(async (slug) => {
127
+ const boards = await Promise.all(entries.map(async (slug) => {
128
128
  const manifest = await readManifest(dir, slug);
129
129
  if (!manifest)
130
130
  return null;
131
131
  const { title, description, sessions, public: isPublic, token } = manifest;
132
132
  return { slug, title, description, sessions, public: isPublic, token, updatedAt: await updatedAt(dir, slug) };
133
133
  }));
134
- return boards.filter((board) => board !== null);
134
+ // Freshest first: the board someone just wrote is the one they came to see.
135
+ // Slug breaks ties so equal mtimes still list in a stable order.
136
+ return boards
137
+ .filter((board) => board !== null)
138
+ .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt) || a.slug.localeCompare(b.slug));
135
139
  }
136
140
  /** Containment, not normalization: the resolved realpath must sit inside the
137
141
  * board's own site dir or nothing is served. */
@@ -93,13 +93,7 @@ export class ChannelStore {
93
93
  id: chat.id,
94
94
  name: chat.name,
95
95
  kind: chat.kind,
96
- enabled: true,
97
- requireMention: config.requireMention,
98
- requireBind: config.requireBind,
99
- topicMode: config.topicMode,
100
- cwd: config.cwd,
101
- model: config.model,
102
- thinking: config.thinking,
96
+ ...this.policy(platform, chat.id),
103
97
  });
104
98
  }
105
99
  this.save(platform, config);
@@ -213,7 +213,7 @@ export function splitReply(rawMarkdown, meta) {
213
213
  // Every surface that renders this goes through here, and every CommonMark
214
214
  // parser has some version of the CJK emphasis hole — so the repair belongs
215
215
  // once, at the seam, not per adapter.
216
- const markdown = cjkFriendly(rawMarkdown.replace(SILENT, "").trim());
216
+ const markdown = streamBody(rawMarkdown);
217
217
  const silence = silentReason(rawMarkdown);
218
218
  const m = BLOCK.exec(markdown);
219
219
  if (!m?.[1])
package/dist/db.js CHANGED
@@ -316,6 +316,13 @@ const MIGRATIONS = [
316
316
  CREATE INDEX task_runs_visible_time ON task_runs(queued_at DESC, id DESC)
317
317
  WHERE NOT (state = 'succeeded' AND json_extract(json, '$.matched') IS 0);
318
318
  `,
319
+ // 18 — opening a session lists every run it delegated, not the last hour's.
320
+ `
321
+ -- The run cards are the messages a session sent, so the transcript wants
322
+ -- all of them: the query is by the delegating session, which lived only in
323
+ -- the JSON — without this every session open was a full scan of task_runs.
324
+ CREATE INDEX task_runs_invoked_by ON task_runs(json_extract(json, '$.invokedBySessionId'), queued_at DESC);
325
+ `,
319
326
  ];
320
327
  /**
321
328
  * Several writes as one, or none. `BEGIN IMMEDIATE` because every writer here
package/dist/main.js CHANGED
@@ -42,14 +42,13 @@ import { registerConfigShareRoute, registerConfigSyncRoutes } from "./web/config
42
42
  import { PushStore, registerPushRoutes } from "./web/push.js";
43
43
  import { SessionStateStore } from "./web/session-state.js";
44
44
  import { createServer } from "./web/server.js";
45
- import { attachTerminal } from "./web/terminal.js";
46
45
  const log = logger("pier");
47
46
  // Pier owns the Pi runtime dir. Set before any SDK call resolves a path, so
48
47
  // everything Pi derives from its agent dir (auth.json, models.json, sessions,
49
48
  // bin) lands under PIER_HOME instead of ~/.pi.
50
49
  //
51
50
  // An operator override wins — but only a human's. Everything Pier spawns (the
52
- // Web Terminal, the agent's own shell) inherits this variable, so a second
51
+ // agent's own shell, task sessions) inherits this variable, so a second
53
52
  // Pier started from inside the first with its own PIER_HOME would adopt the
54
53
  // first one's agent dir and write its sessions, SYSTEM.md and models.json
55
54
  // there: two instances sharing a directory neither was told to share, and
@@ -58,8 +57,8 @@ const log = logger("pier");
58
57
  // again from this instance's own PIER_HOME.
59
58
  process.env.PI_CODING_AGENT_DIR = resolveAgentDir(process.env);
60
59
  process.env.PIER_AGENT_DIR = process.env.PI_CODING_AGENT_DIR;
61
- // Ahead of everything Pier spawns — sessions, tasks, the Web Terminal all
62
- // inherit this process's env. A tool switched on in the Console is Pier's
60
+ // Ahead of everything Pier spawns — sessions and tasks all inherit this
61
+ // process's env. A tool switched on in the Console is Pier's
63
62
  // copy at Pier's version, so it goes first, not last.
64
63
  prependPath(process.env);
65
64
  // First, and explicitly: every store below shares this one connection, and a
@@ -387,6 +386,15 @@ app.route("/", createServer({
387
386
  }));
388
387
  const port = Number(process.env.PORT ?? 3141);
389
388
  const hostname = process.env.HOST ?? "127.0.0.1";
389
+ // Read above, and dropped here so nothing else reads them: these three
390
+ // configure *this* process, and every command a turn runs inherits its env.
391
+ // `NODE_ENV=production` makes an agent's `npm install` skip devDependencies and
392
+ // silently changes what half the ecosystem builds; PORT and HOST would aim an
393
+ // agent's own dev server at Pier's socket. Deleted at runtime rather than only
394
+ // dropped from the unit, because an installed unit is rewritten by
395
+ // `pier service install --force` and by nothing else.
396
+ for (const leak of ["NODE_ENV", "PORT", "HOST"])
397
+ delete process.env[leak];
390
398
  const server = serve({ fetch: app.fetch, port, hostname }, () => {
391
399
  log.info(`workbench on http://${hostname}:${port}`);
392
400
  log.info(`pid ${process.pid}, node ${process.version}, home ${PIER_HOME}`);
@@ -396,11 +404,6 @@ const server = serve({ fetch: app.fetch, port, hostname }, () => {
396
404
  log.info(`agent dir ${process.env.PI_CODING_AGENT_DIR} (PI_CODING_AGENT_DIR)`);
397
405
  }
398
406
  });
399
- // The one WebSocket surface (see web/terminal.ts); `serve` above builds a
400
- // plain node:http server, which is the only shape with an upgrade event.
401
- const terminals = attachTerminal(server, auth, {
402
- initCommand: () => settings.get().terminalInitCommand,
403
- });
404
407
  // A crash and a clean stop must be distinguishable after the fact, and both
405
408
  // left nothing behind before this.
406
409
  process.on("uncaughtException", (err) => {
@@ -423,7 +426,6 @@ const shutdown = (stopTasks = true) => {
423
426
  // `systemctl restart` into a 90-second wait for SIGKILL.
424
427
  setTimeout(() => process.exit(0), 3000).unref();
425
428
  stopEviction();
426
- terminals.close(); // no shell outlives the workbench
427
429
  // The drain path leaves task runs alone: aborting them here would record
428
430
  // them cancelled and race their callbacks against dying channels, when the
429
431
  // boot-time interrupted marking is the recovery that was promised.
package/dist/service.js CHANGED
@@ -64,7 +64,8 @@ WorkingDirectory=%h
64
64
  # Absolute paths on purpose: systemd starts with a minimal PATH, and the node
65
65
  # that installed Pier is usually not on it.
66
66
  ExecStart=${quote(execPath, true)} ${quote(entry, true)}
67
- ${environment("NODE_ENV", "production")}
67
+ # No NODE_ENV: nothing in Pier reads it, and it is inherited by every command a
68
+ # turn runs — an agent's "npm install" would skip devDependencies.
68
69
  # Inherited by every command a turn runs, which is why it is here and not just
69
70
  # in the updater: an agent typing "npm test" on systemd's minimal PATH would be
70
71
  # told node does not exist on a machine that installed Pier with it.
package/dist/settings.js CHANGED
@@ -71,26 +71,6 @@ export function normalizeModelMenu(raw) {
71
71
  }
72
72
  return menu;
73
73
  }
74
- /**
75
- * One line, typed into a shell. Same reject-don't-repair contract: a command
76
- * silently truncated at a newline would run half of what the operator wrote,
77
- * in every project shell, with no sign of what was dropped.
78
- */
79
- export function normalizeTerminalInitCommand(raw) {
80
- if (typeof raw !== "string")
81
- return null;
82
- const text = raw.trim();
83
- if (!text)
84
- return "";
85
- if (text.length > 500)
86
- return null;
87
- // A tty reads these as keys, not text: an embedded newline is a second
88
- // command nobody would see in the field it was typed in.
89
- for (const ch of text)
90
- if (ch < " " || ch === "\u007f")
91
- return null;
92
- return text;
93
- }
94
74
  /**
95
75
  * Shape only — an unknown name is not an error here. This file must not know
96
76
  * what Pier bundles (that catalog is code, and importing it would drag the Pi
@@ -131,7 +111,6 @@ export class SettingsStore {
131
111
  publicUrl: this.#value("publicUrl") ?? "",
132
112
  modelMenu: this.#json("modelMenu", normalizeModelMenu, "a valid menu") ?? [],
133
113
  autoUpdate: this.#value("autoUpdate") === "1",
134
- terminalInitCommand: this.#value("terminalInitCommand") ?? "",
135
114
  extensions: this.#json("extensions", normalizeExtensions, "a list of names") ?? [],
136
115
  tools: this.#json("tools", normalizeTools, "a list of names") ?? [],
137
116
  customTools: this.#json("customTools", normalizeCustomTools, "a list of {name, spec}") ?? [],
@@ -170,11 +149,6 @@ export class SettingsStore {
170
149
  this.#set("modelMenu", JSON.stringify(menu));
171
150
  return this.get();
172
151
  }
173
- /** Same contract again: hand this `normalizeTerminalInitCommand`'s output. */
174
- setTerminalInitCommand(command) {
175
- this.#set("terminalInitCommand", command);
176
- return this.get();
177
- }
178
152
  setAutoUpdate(on) {
179
153
  this.#set("autoUpdate", on ? "1" : "0");
180
154
  return this.get();
@@ -9,7 +9,6 @@ import { runSource } from "./callbacks.js";
9
9
  import { TaskMessenger } from "./messages.js";
10
10
  import { TaskStore } from "./store.js";
11
11
  const MAX_ACTIVE_AGENTS = 4;
12
- const MAX_ACTIVE_PER_ROOT = 4;
13
12
  /** What a child cannot know unless told. Every session gets the chat-surface
14
13
  * contract (<pier>/AGENTS.md), task runs included — so the delegation prompt
15
14
  * says which of it does not apply here, and a supervised run how to reach the
@@ -180,7 +179,7 @@ export class AgentTaskRunner {
180
179
  return session;
181
180
  }
182
181
  async acquireSlot(run, signal) {
183
- while (this.active.size >= MAX_ACTIVE_AGENTS || this.activeForRoot(run.rootRunId) >= MAX_ACTIVE_PER_ROOT) {
182
+ while (this.active.size >= MAX_ACTIVE_AGENTS) {
184
183
  if (signal.aborted)
185
184
  throw new Error("cancelled");
186
185
  await new Promise((resolve, reject) => {
@@ -199,14 +198,6 @@ export class AgentTaskRunner {
199
198
  }
200
199
  this.active.add(run.id);
201
200
  }
202
- activeForRoot(rootRunId) {
203
- let count = 0;
204
- for (const id of this.active) {
205
- if (this.store.getRun(id)?.rootRunId === rootRunId)
206
- count += 1;
207
- }
208
- return count;
209
- }
210
201
  releaseSlot(id) {
211
202
  this.active.delete(id);
212
203
  for (const wake of this.slotWaiters)
@@ -53,7 +53,7 @@ export class TaskCallbacks {
53
53
  // one delivery, and the first one's name and model as the card's
54
54
  // caption would attribute every other result to it. The text names
55
55
  // each run; the card says nothing rather than something false.
56
- ...(runs.length === 1 ? { source: runSource(runs[0]) } : {}),
56
+ ...(runs.length === 1 ? { source: runSource(runs[0]), state: runs[0].state } : {}),
57
57
  },
58
58
  }),
59
59
  describe: (run) => `the result of "${run.context.definition.name}"`,
@@ -27,7 +27,8 @@ export class TaskGroups {
27
27
  save: (group) => { this.store.saveGroup(group); },
28
28
  changed,
29
29
  // A group names itself in the origin where a run names its run id, so the
30
- // engine's transcript proof works unchanged.
30
+ // engine's transcript proof works unchanged; the console's run route
31
+ // resolves that id to the group's members (web/ui/task-runs.ts).
31
32
  input: (groups) => ({
32
33
  text: this.text(groups[0]),
33
34
  origin: { kind: "task-callback", taskId: groups[0].id, runId: groups[0].id, sourceSessionId: null },
@@ -18,6 +18,15 @@ import { TaskStore } from "./store.js";
18
18
  import { handleTaskTool } from "./tool.js";
19
19
  import { isTerminal } from "./types.js";
20
20
  const log = logger("tasks");
21
+ /** The text a run was given, as the operator wrote it: a resume's follow-up,
22
+ * else the action's own prompt or script. Not `renderedPrompt` — that is the
23
+ * preamble and the input wrapper, which the delegating session did not send. */
24
+ const runPrompt = (run) => {
25
+ if (run.context.resumePrompt)
26
+ return run.context.resumePrompt;
27
+ const action = run.context.definition.action;
28
+ return action.type === "agent" ? action.prompt : action.type === "bash" ? action.script : null;
29
+ };
21
30
  export class TaskService {
22
31
  store;
23
32
  factory;
@@ -195,11 +204,11 @@ export class TaskService {
195
204
  recentMessages(since) {
196
205
  return this.messages.recent(since);
197
206
  }
207
+ /** Every run this session delegated: the card is the message that started
208
+ * it, and a message does not leave the transcript because the run ended. */
198
209
  backgroundRuns(sessionId) {
199
- const cutoff = Date.now() - 60 * 60 * 1000;
200
- return this.store.listRunsForSession(sessionId, 50)
201
- .filter((run) => run.background && (!isTerminal(run.state) || run.queuedAt >= cutoff))
202
- .slice(0, 20)
210
+ return this.store.listRunsForSession(sessionId, 200)
211
+ .filter((run) => run.background)
203
212
  .reverse()
204
213
  .map((run) => this.backgroundRun(run));
205
214
  }
@@ -364,6 +373,7 @@ export class TaskService {
364
373
  targetSessionId: run.targetSessionId,
365
374
  sessionMode: run.sessionMode,
366
375
  depth: run.depth,
376
+ prompt: runPrompt(run),
367
377
  queuedAt: run.queuedAt,
368
378
  startedAt: run.startedAt,
369
379
  finishedAt: run.finishedAt,
@@ -103,7 +103,7 @@ export function taskToolSpec(execute) {
103
103
  return {
104
104
  name: "task",
105
105
  label: "Pier Task",
106
- description: "Manage durable Pier tasks and subagents. Agent tasks run in a fresh session or a reused one. Run executes a stored task by task_id, a one-shot subagent from a prompt (shorthand: prompt + optional cwd/launch/name — cwd defaults to your own directory, relative paths resolve against it, name comes from the prompt) or from a full inline task draft, or a core-joined fan-out via tasks[] with join all|first. Get accepts run_id, group_id, or task_id for that task's recent runs. Every operation returns immediately: results, group joins, and decision replies arrive as callback messages. Use steer/follow_up/resume for child control and contact/reply for supervisor decisions. models lists the deployment's model menu (operator pins with intent notes, else the live catalog).",
106
+ description: "Manage durable Pier tasks and subagents. Agent tasks run in a fresh session or a reused one. create files a definition the operator sees in the Console — only for schedules or roles you will run again; a one-off is run with a prompt. Run executes a stored task by task_id, a one-shot subagent from a prompt (shorthand: prompt + optional cwd/launch/name — cwd defaults to your own directory, relative paths resolve against it, name comes from the prompt) or from a full inline task draft, or a core-joined fan-out via tasks[] with join all|first. Get accepts run_id, group_id, or task_id for that task's recent runs. Every operation returns immediately: results, group joins, and decision replies arrive as callback messages. Use steer/follow_up/resume for child control and contact/reply for supervisor decisions. models lists the deployment's model menu (operator pins with intent notes, else the live catalog).",
107
107
  parameters: Type.Object({
108
108
  operation: strEnum("list", "create", "update", "run", "get", "cancel", "steer", "follow_up", "resume", "contact", "reply", "models"),
109
109
  task_id: Type.Optional(Type.String()),
@@ -72,6 +72,7 @@ export function toolsTask(tasks) {
72
72
  const draft = {
73
73
  name: "tools: daily update",
74
74
  description: "Installs the CLI tools switched on in Settings → Agent and keeps them current.",
75
+ enabled: true,
75
76
  trigger: {
76
77
  type: "cron",
77
78
  expression: "17 4 * * *",
@@ -79,9 +80,9 @@ export function toolsTask(tasks) {
79
80
  },
80
81
  // PIER_HOME as cwd: the command belongs to the instance, not to a project.
81
82
  action: { type: "bash", script: command.script, cwd: PIER_HOME },
83
+ callback: { type: "none" },
82
84
  timeoutSeconds: 1800,
83
85
  };
84
- const draftShape = [draft.name, draft.description, true, draft.trigger, draft.action, { type: "none" }, draft.timeoutSeconds];
85
86
  // Archived is not "owned but edited": nothing can un-archive a task, so the
86
87
  // replacement is a new one and the old one keeps its history.
87
88
  const owned = tasks.list().filter((task) => task.creator === TOOLS_TASK_CREATOR && !task.archived);
@@ -95,13 +96,11 @@ export function toolsTask(tasks) {
95
96
  // Every field Pier owns, not just the command: a paused task, a renamed one
96
97
  // or one pointed at a callback still claims to be keeping the tools current
97
98
  // while the daily run never happens.
98
- const current = task &&
99
- JSON.stringify([task.name, task.description, task.enabled, task.trigger, task.action, task.callback, task.timeoutSeconds]);
100
- if (task && current !== JSON.stringify(draftShape)) {
99
+ if (task && Object.entries(draft).some(([key, value]) => JSON.stringify(task[key]) !== JSON.stringify(value))) {
101
100
  log.warn("the tools update task was edited — restoring the definition Pier owns");
102
101
  // Named as the owner: this is the one path allowed to write it back
103
102
  // (tasks/definitions.ts).
104
- await tasks.update(task.id, { ...draft, enabled: true, callback: { type: "none" } }, TOOLS_TASK_CREATOR);
103
+ await tasks.update(task.id, draft, TOOLS_TASK_CREATOR);
105
104
  }
106
105
  const id = task ? task.id : (await tasks.create(draft, TOOLS_TASK_CREATOR)).id;
107
106
  toolsTaskId = id;
package/dist/web/auth.js CHANGED
@@ -289,8 +289,7 @@ function noteFailure(client) {
289
289
  failures.set(client, { count: 1, resetAt: Date.now() + WINDOW_MS });
290
290
  }
291
291
  /** Browsers name the source of unsafe requests. Compare hosts rather than
292
- * schemes because TLS commonly terminates at the reverse proxy. Shared by HTTP
293
- * and WebSocket so the password boundary cannot disagree with itself. */
292
+ * schemes because TLS commonly terminates at the reverse proxy. */
294
293
  function originMatches(origin, host) {
295
294
  if (!origin)
296
295
  return true; // curl and other non-browser clients
@@ -316,26 +315,6 @@ function sameOrigin(c) {
316
315
  * surface that belongs to one browser (its push subscription) names it the
317
316
  * same way, rather than parsing the cookie a second way. */
318
317
  export const sessionIdOf = (c) => (getCookie(c, COOKIE) ?? "").split(".")[0] ?? "";
319
- /** The same cookie + Origin boundary for a WebSocket upgrade, where no Hono
320
- * context exists before the handshake completes. The session id comes back
321
- * with the verdict: a socket outlives the request that opened it, so it has
322
- * to know which row signing out would close it. */
323
- export function upgradeAuthorized(store, req) {
324
- const raw = req.headers.cookie
325
- ?.split(";")
326
- .map((part) => part.trim())
327
- .find((part) => part.startsWith(`${COOKIE}=`))
328
- ?.slice(COOKIE.length + 1);
329
- const session = store.check(raw);
330
- if (!session)
331
- return undefined;
332
- const remote = req.socket.remoteAddress ?? "";
333
- const forwardedHeader = req.headers["x-forwarded-host"];
334
- const forwarded = loopback(remote) && typeof forwardedHeader === "string"
335
- ? forwardedHeader.split(",").at(-1)?.trim()
336
- : undefined;
337
- return originMatches(req.headers.origin, forwarded || req.headers.host) ? session.id : undefined;
338
- }
339
318
  /** Every route, in one place — no per-route opt-in to forget on the next one. */
340
319
  export function requireAuth(store) {
341
320
  return async (c, next) => {
package/dist/web/fs.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Every path the Console reaches on disk resolves here: the browse/read/mkdir
2
- // routes, the session attachments server.ts serves, and the pty's cwd all ask
3
- // the same question, and asked it four different ways until this file existed.
2
+ // routes and the session attachments server.ts serves all ask the same
3
+ // question, and asked it several different ways until this file existed.
4
4
  //
5
5
  // A `root` is any directory the process can read — sessions work in worktrees
6
6
  // and siblings of their cwd, and an owner already past the Console password
@@ -2,7 +2,7 @@
2
2
  // layer-1 secrets control, the browser's error reports. Nothing here touches
3
3
  // a session; server.ts stays the session/event surface.
4
4
  import { logger } from "../log.js";
5
- import { normalizeModelMenu, normalizePublicUrl, normalizeTerminalInitCommand, } from "../settings.js";
5
+ import { normalizeModelMenu, normalizePublicUrl, } from "../settings.js";
6
6
  /** A rule that failed inside the transaction — its own class so the route can
7
7
  * tell it from a real fault. 400 when the request named something this Pier
8
8
  * does not have, 409 when it collided with the state as it stands. */
@@ -131,11 +131,11 @@ export function registerInstanceRoutes(app, deps) {
131
131
  app.put("/api/settings", async (c) => {
132
132
  const body = await c.req.json().catch(() => null);
133
133
  const fields = body
134
- ? [body.publicUrl, body.modelMenu, body.autoUpdate, body.terminalInitCommand, body.customTools, body.extension, body.tool]
134
+ ? [body.publicUrl, body.modelMenu, body.autoUpdate, body.customTools, body.extension, body.tool]
135
135
  : [];
136
136
  if (!fields.some((v) => v !== undefined)) {
137
137
  return c.json({
138
- error: "publicUrl, modelMenu, autoUpdate, terminalInitCommand, customTools, extension or tool required",
138
+ error: "publicUrl, modelMenu, autoUpdate, customTools, extension or tool required",
139
139
  }, 400);
140
140
  }
141
141
  // Everything is validated before anything is written, and everything is
@@ -163,12 +163,6 @@ export function registerInstanceRoutes(app, deps) {
163
163
  return refuse("autoUpdate must be a boolean");
164
164
  writes.push(() => settings.setAutoUpdate(autoUpdate));
165
165
  }
166
- if (body?.terminalInitCommand !== undefined) {
167
- const command = normalizeTerminalInitCommand(body.terminalInitCommand);
168
- if (command === null)
169
- return refuse("terminalInitCommand must be one line of at most 500 characters");
170
- writes.push(() => settings.setTerminalInitCommand(command));
171
- }
172
166
  /** The blocks this request declares, or null when it does not touch them.
173
167
  * Adding a tool is declaring it *and* switching it on, so a name declared
174
168
  * here is switchable in the same write. */
@@ -1,5 +1,5 @@
1
- import{B as e,C as t,H as n,I as r,L as i,N as a,k as o,m as s,q as c,v as l,z as u}from"./index-BrBNzaid.js";var d=(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},f=e=>e===null?`-`:r(Date.now()-e),p={light:{edge:`#a3a3a3`,callback:`#0891b2`,message:`#d97706`,scheduler:{fill:`#f5f3ff`,stroke:`#a78bfa`,text:`#5b21b6`},console:{fill:`#eff6ff`,stroke:`#93c5fd`,text:`#1d4ed8`},task:{fill:`#fffbeb`,stroke:`#fbbf24`,text:`#92400e`},process:{fill:`#f5f5f5`,stroke:`#a3a3a3`,text:`#525252`,dash:`4 3`},streaming:{fill:`#ffffff`,stroke:`#10b981`,text:`#262626`},idle:{fill:`#fafafa`,stroke:`#d4d4d4`,text:`#737373`}},dark:{edge:`#7a7a7a`,callback:`#3fc3dd`,message:`#e0a13a`,scheduler:{fill:`#2b2440`,stroke:`#7d63c9`,text:`#c9b8f5`},console:{fill:`#1f2a3d`,stroke:`#4a7fbf`,text:`#a8c8f0`},task:{fill:`#3a2f1c`,stroke:`#b8862a`,text:`#f0d49a`},process:{fill:`#262626`,stroke:`#5c5c5c`,text:`#a3a3a3`,dash:`4 3`},streaming:{fill:`#22302a`,stroke:`#10b981`,text:`#e5e5e5`},idle:{fill:`#212121`,stroke:`#4a4a4a`,text:`#9a9a9a`}}},m=()=>p[document.documentElement.dataset.theme===`dark`?`dark`:`light`];function h(r,p,h){let g=`sessions`,_=`active`,v={sessions:[],runs:[],messages:[]},y=``,b=e(async()=>{let e=_,t=await n(`/api/activity?scope=${e}`,`Failed to load activity`);if(!t.ok){y=``,r.replaceChildren(i(`p`,`p-4 text-[13px] text-red-600`,t.error));return}if(e!==_)return;let a=JSON.stringify(t.value);if(`${e}:${a}`===y)return;y=`${e}:${a}`;let o=t.value;v={...o,sessions:o.sessions.map(e=>({...e,title:c(e.title)}))},x()});function x(){let e=o(t([[`Sessions`,`sessions`],[`Relationships`,`dependencies`]],g,e=>{g=e,x()}),i(`span`,`ml-auto text-[11.5px] text-neutral-400`,`${v.sessions.length} sessions · ${v.runs.length} runs`),t([[`Current`,`active`],[`Last 24h`,`recent`]],_,e=>{_=e,x(),b()})),n=i(`div`,`min-h-0 flex-1 overflow-auto`);g===`sessions`?S(n):w(n),r.replaceChildren(e,n)}function S(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>
1
+ import{B as e,C as t,H as n,I as r,K as i,L as a,N as o,k as s,m as c,v as l,z as u}from"./index-DuhZm2hI.js";var d=(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},f=e=>e===null?`-`:r(Date.now()-e),p={light:{edge:`#a3a3a3`,callback:`#0891b2`,message:`#d97706`,scheduler:{fill:`#f5f3ff`,stroke:`#a78bfa`,text:`#5b21b6`},console:{fill:`#eff6ff`,stroke:`#93c5fd`,text:`#1d4ed8`},task:{fill:`#fffbeb`,stroke:`#fbbf24`,text:`#92400e`},process:{fill:`#f5f5f5`,stroke:`#a3a3a3`,text:`#525252`,dash:`4 3`},streaming:{fill:`#ffffff`,stroke:`#10b981`,text:`#262626`},idle:{fill:`#fafafa`,stroke:`#d4d4d4`,text:`#737373`}},dark:{edge:`#7a7a7a`,callback:`#3fc3dd`,message:`#e0a13a`,scheduler:{fill:`#2b2440`,stroke:`#7d63c9`,text:`#c9b8f5`},console:{fill:`#1f2a3d`,stroke:`#4a7fbf`,text:`#a8c8f0`},task:{fill:`#3a2f1c`,stroke:`#b8862a`,text:`#f0d49a`},process:{fill:`#262626`,stroke:`#5c5c5c`,text:`#a3a3a3`,dash:`4 3`},streaming:{fill:`#22302a`,stroke:`#10b981`,text:`#e5e5e5`},idle:{fill:`#212121`,stroke:`#4a4a4a`,text:`#9a9a9a`}}},m=()=>p[document.documentElement.dataset.theme===`dark`?`dark`:`light`];function h(r,p,h){let g=`sessions`,_=`active`,v={sessions:[],runs:[],messages:[]},y=``,b=e(async()=>{let e=_,t=await n(`/api/activity?scope=${e}`,`Failed to load activity`);if(!t.ok){y=``,r.replaceChildren(a(`p`,`p-4 text-[13px] text-red-600`,t.error));return}if(e!==_)return;let o=JSON.stringify(t.value);if(`${e}:${o}`===y)return;y=`${e}:${o}`;let s=t.value;v={...s,sessions:s.sessions.map(e=>({...e,title:i(e.title)}))},x()});function x(){let e=s(t([[`Sessions`,`sessions`],[`Relationships`,`dependencies`]],g,e=>{g=e,x()}),a(`span`,`ml-auto text-[11.5px] text-neutral-400`,`${v.sessions.length} sessions · ${v.runs.length} runs`),t([[`Current`,`active`],[`Last 24h`,`recent`]],_,e=>{_=e,x(),b()})),n=a(`div`,`min-h-0 flex-1 overflow-auto`);g===`sessions`?S(n):w(n),r.replaceChildren(e,n)}function S(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
2
  <th class="w-[50%] px-4 py-2 font-semibold md:w-[34%]">Session</th>
3
3
  <th class="hidden w-[38%] px-2 py-2 font-semibold md:table-cell">Project</th>
4
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 v.sessions){let t=document.createElement(`tr`);t.className=`cursor-pointer border-b border-neutral-100 transition-colors hover:bg-neutral-50`,t.onclick=()=>p(e.id);let r=i(`button`,`block w-full cursor-pointer truncate text-left font-medium text-neutral-800`,e.title??u(e.cwd));r.setAttribute(`type`,`button`),r.title=`${e.id}\n${e.cwd}`,r.onclick=t=>{t.stopPropagation(),p(e.id)},t.append(i(`td`,`px-4 py-2.5`,r,i(`div`,`truncate font-mono text-[11px] text-neutral-400 md:hidden`,e.cwd||`-`)),i(`td`,`hidden truncate px-2 py-2.5 font-mono text-[11.5px] text-neutral-500 md:table-cell`,e.cwd||`-`),i(`td`,`px-2 py-2.5`,C(e.state)),i(`td`,`px-2 py-2.5 font-mono text-[11.5px] text-neutral-400`,f(e.stateSince))),n.append(t)}t.append(n),e.append(t),v.sessions.length||e.append(i(`div`,`p-4`,l(_===`active`?`No active sessions.`:`No sessions in the last 24 hours.`)))}function C(e){return e===`streaming`?s(e,`bg-emerald-50 text-emerald-700 ring-emerald-200`,`animate-pulse bg-emerald-500`):s(e,`bg-neutral-100 text-neutral-600 ring-neutral-200`,`bg-neutral-300`)}function w(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 v.sessions)r(e.id,e.title??e.id.slice(0,10),`session`,e.state);for(let e of v.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 v.messages){if(!e.fromSessionId||!e.toSessionId||e.fromSessionId===e.toSessionId)continue;let t=v.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(i(`div`,`p-4`,l(_===`active`?`No active dependencies.`:`No dependencies in the last 24 hours.`)));return}let a=[...t.values()],o=new Set(n.map(e=>e.to)),s=new Map,c=a.filter(e=>!o.has(e.id)).map(e=>e.id);c.length||c.push(a[0].id);for(let e of c)s.set(e,0);for(;c.length;){let e=c.shift();for(let t of n)t.from!==e||s.has(t.to)||(s.set(t.to,s.get(e)+1),c.push(t.to))}for(let e of a)s.has(e.id)||s.set(e.id,0);let u=new Map;for(let e of a){let t=s.get(e.id);u.set(t,[...u.get(t)??[],e])}let f=Math.max(...u.keys())+1,g=Math.max(...[...u.values()].map(e=>e.length)),y=n.some(e=>e.from!==e.to&&s.get(e.from)===s.get(e.to))?40:0,b=56+f*180+(f-1)*110+y,x=56+g*46+(g-1)*30,S=new Map;for(let[e,t]of u)t.forEach((t,n)=>S.set(t.id,{x:28+e*290+90,y:28+n*76+23}));let C=d(`svg`,{width:String(b),height:String(x),viewBox:`0 0 ${b} ${x}`,class:`block`}),w=d(`defs`),T=m();for(let[e,t]of[[`activity-arrow`,T.edge],[`activity-arrow-cb`,T.callback],[`activity-arrow-msg`,T.message]]){let n=d(`marker`,{id:e,viewBox:`0 0 10 10`,refX:`9`,refY:`5`,markerWidth:`6`,markerHeight:`6`,orient:`auto-start-reverse`});n.append(d(`path`,{d:`M 0 0 L 10 5 L 0 10 z`,fill:t})),w.append(n)}C.append(w);let E=new Set;for(let e of n){let t=`${e.from}→${e.to}:${e.kind}`;if(E.has(t))continue;E.add(t);let n=S.get(e.from),r=S.get(e.to),i=d(`path`,{d: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}`})(),fill:`none`,stroke:e.kind===`callback`?T.callback:e.kind===`message`?T.message:T.edge,"stroke-width":`1.5`,"marker-end":e.kind===`callback`?`url(#activity-arrow-cb)`:e.kind===`message`?`url(#activity-arrow-msg)`:`url(#activity-arrow)`});e.kind===`callback`&&i.setAttribute(`stroke-dasharray`,`6 5`),e.kind===`message`&&i.setAttribute(`stroke-dasharray`,`2 5`),i.classList.add(`cursor-pointer`),i.onclick=()=>h(e.run.id),C.append(i)}let D=/[\u1100-\u11FF\u2E80-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6]/,O=(e,t)=>{let n=0;for(let r=0;r<e.length;r++)if(n+=D.test(e[r])?2:1,n>t)return`${e.slice(0,r)}…`;return e};for(let e of a){let t=S.get(e.id),n=e.kind===`session`?e.state===`streaming`?T.streaming:T.idle:T[e.kind],r=d(`g`,{transform:`translate(${t.x},${t.y})`});e.kind===`session`&&r.classList.add(`cursor-pointer`),r.onclick=()=>{e.kind===`session`&&p(e.id)};let i=d(`rect`,{x:`-90`,y:`-23`,width:`180`,height:`46`,rx:`8`,fill:n.fill,stroke:n.stroke,"stroke-width":e.kind===`session`&&e.state===`streaming`?`1.5`:`1`});n.dash&&i.setAttribute(`stroke-dasharray`,n.dash),r.append(i);let a=-78;if(e.kind===`session`){let t=d(`circle`,{cx:`-75`,cy:`0`,r:`3.5`,fill:n.stroke});if(e.state===`streaming`){let e=d(`animate`,{attributeName:`opacity`,values:`1;0.3;1`,dur:`1.6s`,repeatCount:`indefinite`});t.append(e)}r.append(t),a=-64}let o=d(`text`,{x:String(a),"text-anchor":`start`,"dominant-baseline":`middle`,"font-size":`11.5`,"font-weight":e.kind===`session`?`500`:`400`,fill:n.text});o.textContent=O(e.label,e.kind===`session`?22:25);let s=d(`title`);s.textContent=e.label===e.id?e.id:`${e.label}\n${e.id}`,r.append(o,s),C.append(r)}let k=e=>i(`span`,`inline-block h-2 w-2 rounded-full ${e}`),A=e=>i(`span`,`inline-block h-0 w-6 border-t-2 ${e}`);e.append(i(`div`,`w-max p-4`,C),i(`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`,i(`span`,`inline-flex items-center gap-2`,A(`border-solid border-neutral-400`),`task invocation`),i(`span`,`inline-flex items-center gap-2 text-cyan-700`,A(`border-dashed border-cyan-500`),`callback`),i(`span`,`inline-flex items-center gap-2 text-amber-700`,A(`border-dotted border-amber-500`),`supervisor / control`),i(`span`,`inline-flex items-center gap-1.5`,k(`bg-emerald-500`),`streaming`),i(`span`,`inline-flex items-center gap-1.5`,k(`bg-neutral-300`),`idle`)))}let T=a(r,e=>{e===`dependencies`?g=`dependencies`:e===`sessions`&&(g=`sessions`),b()},()=>{r.replaceChildren(),v={sessions:[],runs:[],messages:[]},y=``});return window.addEventListener(`pier:theme`,()=>{T.visible&&x()}),Object.assign(T,{refresh(){T.visible&&b()}})}export{h as createActivityView};
5
+ <th class="px-2 py-2 font-semibold">In state</th></tr></thead>`;let n=document.createElement(`tbody`);for(let e of v.sessions){let t=document.createElement(`tr`);t.className=`cursor-pointer border-b border-neutral-100 transition-colors hover:bg-neutral-50`,t.onclick=()=>p(e.id);let r=a(`button`,`block w-full cursor-pointer truncate text-left font-medium text-neutral-800`,e.title??u(e.cwd));r.setAttribute(`type`,`button`),r.title=`${e.id}\n${e.cwd}`,r.onclick=t=>{t.stopPropagation(),p(e.id)},t.append(a(`td`,`px-4 py-2.5`,r,a(`div`,`truncate font-mono text-[11px] text-neutral-400 md:hidden`,e.cwd||`-`)),a(`td`,`hidden truncate px-2 py-2.5 font-mono text-[11.5px] text-neutral-500 md:table-cell`,e.cwd||`-`),a(`td`,`px-2 py-2.5`,C(e.state)),a(`td`,`px-2 py-2.5 font-mono text-[11.5px] text-neutral-400`,f(e.stateSince))),n.append(t)}t.append(n),e.append(t),v.sessions.length||e.append(a(`div`,`p-4`,l(_===`active`?`No active sessions.`:`No sessions in the last 24 hours.`)))}function C(e){return e===`streaming`?c(e,`bg-emerald-50 text-emerald-700 ring-emerald-200`,`animate-pulse bg-emerald-500`):c(e,`bg-neutral-100 text-neutral-600 ring-neutral-200`,`bg-neutral-300`)}function w(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 v.sessions)r(e.id,e.title??e.id.slice(0,10),`session`,e.state);for(let e of v.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 v.messages){if(!e.fromSessionId||!e.toSessionId||e.fromSessionId===e.toSessionId)continue;let t=v.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(a(`div`,`p-4`,l(_===`active`?`No active dependencies.`:`No dependencies in the last 24 hours.`)));return}let i=[...t.values()],o=new Set(n.map(e=>e.to)),s=new Map,c=i.filter(e=>!o.has(e.id)).map(e=>e.id);c.length||c.push(i[0].id);for(let e of c)s.set(e,0);for(;c.length;){let e=c.shift();for(let t of n)t.from!==e||s.has(t.to)||(s.set(t.to,s.get(e)+1),c.push(t.to))}for(let e of i)s.has(e.id)||s.set(e.id,0);let u=new Map;for(let e of i){let t=s.get(e.id);u.set(t,[...u.get(t)??[],e])}let f=Math.max(...u.keys())+1,g=Math.max(...[...u.values()].map(e=>e.length)),y=n.some(e=>e.from!==e.to&&s.get(e.from)===s.get(e.to))?40:0,b=56+f*180+(f-1)*110+y,x=56+g*46+(g-1)*30,S=new Map;for(let[e,t]of u)t.forEach((t,n)=>S.set(t.id,{x:28+e*290+90,y:28+n*76+23}));let C=d(`svg`,{width:String(b),height:String(x),viewBox:`0 0 ${b} ${x}`,class:`block`}),w=d(`defs`),T=m();for(let[e,t]of[[`activity-arrow`,T.edge],[`activity-arrow-cb`,T.callback],[`activity-arrow-msg`,T.message]]){let n=d(`marker`,{id:e,viewBox:`0 0 10 10`,refX:`9`,refY:`5`,markerWidth:`6`,markerHeight:`6`,orient:`auto-start-reverse`});n.append(d(`path`,{d:`M 0 0 L 10 5 L 0 10 z`,fill:t})),w.append(n)}C.append(w);let E=new Set;for(let e of n){let t=`${e.from}→${e.to}:${e.kind}`;if(E.has(t))continue;E.add(t);let n=S.get(e.from),r=S.get(e.to),i=d(`path`,{d: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}`})(),fill:`none`,stroke:e.kind===`callback`?T.callback:e.kind===`message`?T.message:T.edge,"stroke-width":`1.5`,"marker-end":e.kind===`callback`?`url(#activity-arrow-cb)`:e.kind===`message`?`url(#activity-arrow-msg)`:`url(#activity-arrow)`});e.kind===`callback`&&i.setAttribute(`stroke-dasharray`,`6 5`),e.kind===`message`&&i.setAttribute(`stroke-dasharray`,`2 5`),i.classList.add(`cursor-pointer`),i.onclick=()=>h(e.run.id),C.append(i)}let D=/[\u1100-\u11FF\u2E80-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6]/,O=(e,t)=>{let n=0;for(let r=0;r<e.length;r++)if(n+=D.test(e[r])?2:1,n>t)return`${e.slice(0,r)}…`;return e};for(let e of i){let t=S.get(e.id),n=e.kind===`session`?e.state===`streaming`?T.streaming:T.idle:T[e.kind],r=d(`g`,{transform:`translate(${t.x},${t.y})`});e.kind===`session`&&r.classList.add(`cursor-pointer`),r.onclick=()=>{e.kind===`session`&&p(e.id)};let i=d(`rect`,{x:`-90`,y:`-23`,width:`180`,height:`46`,rx:`8`,fill:n.fill,stroke:n.stroke,"stroke-width":e.kind===`session`&&e.state===`streaming`?`1.5`:`1`});n.dash&&i.setAttribute(`stroke-dasharray`,n.dash),r.append(i);let a=-78;if(e.kind===`session`){let t=d(`circle`,{cx:`-75`,cy:`0`,r:`3.5`,fill:n.stroke});if(e.state===`streaming`){let e=d(`animate`,{attributeName:`opacity`,values:`1;0.3;1`,dur:`1.6s`,repeatCount:`indefinite`});t.append(e)}r.append(t),a=-64}let o=d(`text`,{x:String(a),"text-anchor":`start`,"dominant-baseline":`middle`,"font-size":`11.5`,"font-weight":e.kind===`session`?`500`:`400`,fill:n.text});o.textContent=O(e.label,e.kind===`session`?22:25);let s=d(`title`);s.textContent=e.label===e.id?e.id:`${e.label}\n${e.id}`,r.append(o,s),C.append(r)}let k=e=>a(`span`,`inline-block h-2 w-2 rounded-full ${e}`),A=e=>a(`span`,`inline-block h-0 w-6 border-t-2 ${e}`);e.append(a(`div`,`w-max p-4`,C),a(`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`,a(`span`,`inline-flex items-center gap-2`,A(`border-solid border-neutral-400`),`task invocation`),a(`span`,`inline-flex items-center gap-2 text-cyan-700`,A(`border-dashed border-cyan-500`),`callback`),a(`span`,`inline-flex items-center gap-2 text-amber-700`,A(`border-dotted border-amber-500`),`supervisor / control`),a(`span`,`inline-flex items-center gap-1.5`,k(`bg-emerald-500`),`streaming`),a(`span`,`inline-flex items-center gap-1.5`,k(`bg-neutral-300`),`idle`)))}let T=o(r,e=>{e===`dependencies`?g=`dependencies`:e===`sessions`&&(g=`sessions`),b()},()=>{r.replaceChildren(),v={sessions:[],runs:[],messages:[]},y=``});return window.addEventListener(`pier:theme`,()=>{T.visible&&x()}),Object.assign(T,{refresh(){T.visible&&b()}})}export{h as createActivityView};
@@ -0,0 +1 @@
1
+ import{G as e,H as t,L as n,N as r,O as i,P as a,R as o,V as s,W as c,h as l}from"./index-DuhZm2hI.js";var u=e=>e.public&&e.token?`/p/${e.slug}-${e.token}/`:`/boards/${e.slug}/`;function d(d,f){let p=[],m=``,h=n(`header`,`sticky top-0 z-30 flex h-10 items-center gap-3 border-b border-neutral-200 bg-white px-4`,n(`span`,`font-medium max-md:hidden`,`Boards`)),g=n(`div`,`px-4 py-5`);d.append(n(`div`,`min-h-0 flex-1 overflow-y-auto`,h,g));async function _(t,n){let r=await e(`/api/boards/${t.slug}`,{public:n},`PATCH`);if(r.ok)return t.token=(await r.json().catch(()=>({}))).token??``,x();m=await s(r,`Could not change ${t.slug}`),await S()}async function v(e){m=await c(`/api/boards/${e}`,`DELETE`,`Could not delete ${e}`)??``,await S()}function y(e){let t=n(`div`,`group flex flex-col gap-1 border-b border-neutral-200/70 px-1 py-2.5 last:border-b-0`),r=document.createElement(`a`);r.className=`truncate font-medium text-neutral-800 hover:text-indigo-700`,r.href=u(e),r.target=`_blank`,r.rel=`noreferrer`,r.textContent=e.title;let s=n(`div`,`flex items-center gap-2`,r,n(`span`,`flex-none font-mono text-[11.5px] text-neutral-400`,e.slug)),c=i(``,``,e.public,t=>{e.public=t,_(e,t)});c.className=`ml-auto flex flex-none cursor-pointer items-center gap-1.5 text-[11.5px] text-neutral-500`,c.append(n(`span`,``,`Public`));let d=l(`Delete`,`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-red-600 group-hover:opacity-100`);d.title=`Renames the folder on disk; nothing is erased`,d.onclick=()=>void v(e.slug);let p=a(`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-indigo-600 group-hover:opacity-100`,()=>`${location.origin}${u(e)}`);p.title=`Copy the board's link`,s.append(c,p,d),t.append(s);let m=n(`div`,`flex flex-wrap items-center gap-x-3 gap-y-1 text-[11.5px] text-neutral-500`);e.description&&m.append(n(`span`,`min-w-0 truncate`,e.description)),m.append(n(`span`,`flex-none text-neutral-400`,o(Date.parse(e.updatedAt))));for(let t of e.sessions){let e=l(t.slice(0,8),`flex-none cursor-pointer font-mono text-indigo-600 hover:underline`);e.title=`Open session ${t}`,e.onclick=()=>f(t),m.append(e)}if(e.public&&e.token){let t=n(`span`,`flex-none font-mono text-neutral-400`,u(e));t.title=`Public URL — anyone holding this link can read the board, no password`,m.append(t)}return t.append(m),t}let b=[{title:`Public`,hint:`Readable at /p/<slug>-<token>/ by anyone holding the link.`,wanted:!0},{title:`Private`,hint:`Reachable from the Console only.`,wanted:!1}];function x(){let e=n(`div`,`mx-auto flex max-w-3xl flex-col`);p.length===0&&e.append(n(`p`,`rounded-xl border border-dashed border-neutral-300 px-4 py-6 text-center text-[12.5px] text-neutral-500`,`No boards yet. Ask an agent to build one — it writes a folder under ~/.pier/boards.`));for(let{title:t,hint:r,wanted:i}of b){let a=p.filter(e=>e.public===i);if(a.length===0)continue;let o=n(`section`,`mb-6`);o.append(n(`h2`,`text-[13px] font-semibold text-neutral-700`,t)),o.append(n(`p`,`mb-1 text-[11.5px] leading-snug text-neutral-500`,r));let s=n(`div`,`rounded-xl border border-neutral-200 bg-white px-3`);s.append(...a.map(y)),o.append(s),e.append(o)}g.replaceChildren(...m?[n(`p`,`mb-3 text-[13px] text-red-600`,m)]:[],e)}async function S(){let e=await t(`/api/boards`,`Could not load boards`);if(!e.ok)return m=e.error,x();p=e.value,x(),m=``}let C=r(d,()=>void S());return window.addEventListener(`focus`,()=>{C.visible&&S()}),C}export{d as createBoardsView};
@@ -1 +1 @@
1
- import{L as e,u as t}from"./index-BrBNzaid.js";var n=2e4,r=e=>e.map((e,t)=>({nums:[t+1],text:e,tone:``}));function i(t,r){let i=t.length<=n?r:null,o=Math.max(2,...t.map(e=>e.nums.reduce((e,t)=>Math.max(e,String(t).length),0))),s=e(`div`,`py-2 font-mono text-[12px] leading-[1.5]`);for(let n of t){let t=e(`div`,`flex pl-2 pr-4 ${n.tone===`add`?`bg-emerald-50`:n.tone===`del`?`bg-red-50`:``}`);for(let r of n.nums){let n=e(`span`,`flex-none select-none pr-1.5 text-right text-neutral-300`,r===``?``:String(r));n.style.width=`calc(${o}ch + 0.375rem)`,t.append(n)}t.append(a(n,i)),s.append(t)}return s}function a(n,r){let i=`min-w-0 flex-1 whitespace-pre-wrap [overflow-wrap:anywhere]`;if(!n.mark||n.mark[0]>=n.mark[1]){let e=t(n.text,r);return e.className=i,e}let[a,o]=n.mark,s=t(n.text.slice(a,o),r);return s.className=`rounded-xs ${n.tone===`add`?`bg-emerald-200/80`:`bg-red-200/70`}`,e(`span`,i,t(n.text.slice(0,a),r),s,t(n.text.slice(o),r))}export{r as n,i as t};
1
+ import{L as e,d as t}from"./index-DuhZm2hI.js";var n=2e4,r=e=>e.map((e,t)=>({nums:[t+1],text:e,tone:``}));function i(t,r){let i=t.length<=n?r:null,o=Math.max(2,...t.map(e=>e.nums.reduce((e,t)=>Math.max(e,String(t).length),0))),s=e(`div`,`py-2 font-mono text-[12px] leading-[1.5]`);for(let n of t){let t=e(`div`,`flex pl-2 pr-4 ${n.tone===`add`?`bg-emerald-50`:n.tone===`del`?`bg-red-50`:``}`);for(let r of n.nums){let n=e(`span`,`flex-none select-none pr-1.5 text-right text-neutral-300`,r===``?``:String(r));n.style.width=`calc(${o}ch + 0.375rem)`,t.append(n)}t.append(a(n,i)),s.append(t)}return s}function a(n,r){let i=`min-w-0 flex-1 whitespace-pre-wrap [overflow-wrap:anywhere]`;if(!n.mark||n.mark[0]>=n.mark[1]){let e=t(n.text,r);return e.className=i,e}let[a,o]=n.mark,s=t(n.text.slice(a,o),r);return s.className=`rounded-xs ${n.tone===`add`?`bg-emerald-200/80`:`bg-red-200/70`}`,e(`span`,i,t(n.text.slice(0,a),r),s,t(n.text.slice(o),r))}export{r as n,i as t};
@@ -0,0 +1,5 @@
1
+ import{F as e,L as t,M as n,N as r,R as i,U as a,a as o,i as s,n as c,s as l,u}from"./index-DuhZm2hI.js";import{n as d,t as f}from"./code-D1-CYID1.js";var p=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())}`},m=e=>[e.subject,e.body,[[e.author,e.email?`<${e.email}>`:``].filter(Boolean).join(` `),e.at?p(e.at):``].filter(Boolean).join(` · `)].filter(Boolean).join(`
2
+
3
+ `),h=null;function g(){h&&(h.remove(),h=null,document.removeEventListener(`pointerdown`,g,!0),window.removeEventListener(`scroll`,g,!0))}function _(e,n){e.onmouseenter=()=>{g();let r=n();if(!r)return;h=t(`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`),h.textContent=r,document.body.append(h),document.addEventListener(`pointerdown`,g,!0),window.addEventListener(`scroll`,g,!0);let i=e.getBoundingClientRect();h.style.left=`${Math.max(8,Math.min(i.left,window.innerWidth-h.offsetWidth-8))}px`,h.style.top=i.bottom+6+h.offsetHeight>window.innerHeight?`${i.top-h.offsetHeight-6}px`:`${i.bottom+6}px`},e.onmouseleave=g}function v(e,n,r){let a=t(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-1 text-left hover:bg-neutral-100`,t(`span`,`w-3 flex-none text-indigo-600`,e.value===n?`✓`:``),t(`span`,`flex-none font-mono text-[11.5px] text-neutral-800`,e.label??e.value));return e.subject&&a.append(t(`span`,`min-w-0 truncate text-[11px] text-neutral-400`,e.subject)),e.author&&a.append(t(`span`,`ml-auto flex-none pl-1.5 text-[10.5px] text-neutral-400`,`${e.author} · ${e.at?i(e.at):``}`)),_(a,()=>m(e)),a.onclick=()=>r(e.value),a}function ee(e,n,r,i,a){let{base:c,head:l}=r,u=()=>l&&c===`${l}~1`?l:``,d=u()?`commit`:`compare`,f=n.commits[0],p=e=>[...e?[{value:``,label:`Working tree`,subject:`the files on disk, uncommitted changes included`}]:[],{...f,value:`HEAD`},...n.refs.map(e=>({value:e.name,subject:e.subject})),...n.commits.map(e=>({...e,value:e.hash}))],m=t(`div`,`flex w-[38rem] max-w-full flex-col`);function h(){let e=(e,n)=>{let r=t(`button`,`cursor-pointer rounded-full px-2.5 py-0.5 text-[11.5px] ${d===n?`bg-indigo-50 font-medium text-indigo-700`:`text-neutral-500 hover:bg-neutral-100`}`,e);return r.onclick=()=>{d=n,h()},r},r=(e,n,r,i)=>t(`div`,`flex min-w-0 flex-1 flex-col`,t(`div`,`flex-none px-2 pb-0.5 text-[10.5px] font-semibold uppercase tracking-wide text-neutral-400`,e),t(`div`,`min-h-0 flex-1 overflow-y-auto`,...n.map(e=>v(e,r,i)))),o=d===`compare`?t(`div`,`flex h-72 min-h-0 divide-x divide-neutral-200 pb-1`,r(`base`,p(!1),c,e=>{c=e,h(),i(c,l)}),r(`to`,p(!0),l,e=>{l=e,h(),i(c,l)})):t(`div`,`flex h-72 min-h-0 flex-col overflow-y-auto pb-1`,...n.commits.map(e=>v({value:e.hash,...e},u(),e=>{c=`${e}~1`,l=e,s(),i(c,l)}))),f=t(`div`,`flex-none border-t border-neutral-200`,v({value:a,label:`↺ ${a} ↔ Working tree`,subject:`back to the default: the main line's tip vs the files on disk`},c===a&&l===``?a:``,()=>{c=a,l=``,h(),i(c,l)}));m.replaceChildren(t(`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`)),o,...d===`compare`?[f]:[])}h(),o(e,m)}var te=/\.(png|jpe?g|gif|webp|avif|bmp|svg)$/i,ne={A:`text-emerald-600`,M:`text-amber-600`,D:`text-red-600`},re=30,y=`pier.filesPrefs`,b=50,x=()=>{try{let e=JSON.parse(localStorage.getItem(y)??`{}`);return e&&typeof e==`object`?e:{}}catch{return{}}},S=e=>{let t=e?x()[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},ie=(e,t)=>{let n=x();delete n[e],n[e]=t;let r=Object.keys(n);for(let e of r.slice(0,Math.max(0,r.length-b)))delete n[e];try{localStorage.setItem(y,JSON.stringify(n))}catch(e){console.warn(`Files: could not remember this folder`,e)}},C=(e,n=`indigo`)=>{let r=t(`button`,`min-w-0 cursor-pointer truncate rounded-md px-2 py-0.5 text-left font-mono text-[11.5px] ${n===`indigo`?`bg-indigo-50 font-medium text-indigo-700 hover:bg-indigo-100`:`bg-neutral-100 text-neutral-600 hover:bg-neutral-200`}`,e);return r.type=`button`,r};function w(i,o,s,p){let h=``,g=``,v={branch:null,refs:[],commits:[],worktrees:[]},y=`HEAD`,b=``,x=new Map,w=!0,T=!1,ae=[],E=new Set,D=null,O=null,k=t(`header`,`flex h-10 flex-none items-center gap-2 border-b border-neutral-200 bg-white px-3`),A=t(`div`,`flex-none border-b border-neutral-200`),j=t(`div`,`min-h-0 flex-1 overflow-y-auto py-1`),oe=t(`aside`,`flex w-64 flex-none flex-col border-r border-neutral-200 text-[12.5px] max-md:h-2/5 max-md:w-full max-md:border-b max-md:border-r-0`,A,j),M=t(`div`,`h-full min-w-0 overflow-auto`),N=t(`div`,`pointer-events-none absolute inset-y-0 right-0 z-20 hidden w-2`),se=t(`section`,`relative min-h-0 min-w-0 flex-1`,M,N);i.append(k,t(`div`,`flex min-h-0 flex-1 max-md:flex-col`,oe,se));let P=(e,t)=>`/api/explorer/${e}?${new URLSearchParams({root:h,...t})}`,F=(e,t)=>`/api/fs/${e}?${new URLSearchParams({root:h,...t})}`,I=(e,n=`text-neutral-400`)=>t(`p`,`px-4 py-3 text-[12.5px] ${n}`,e),L=e=>{let t=0;for(let n of x.keys())n.startsWith(`${e}/`)&&t++;return t};function R(e,t){let n=new Set(t.map(e=>e.name)),r=e?`${e}/`:``;for(let[e,i]of x){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 w&&v.branch&&(i=t.filter(e=>e.dir?L(`${r}${e.name}`)>0:x.has(`${r}${e.name}`))),i.sort((e,t)=>Number(t.dir)-Number(e.dir)||e.name.localeCompare(t.name)),i.map(e=>e.dir?ce(`${r}${e.name}`,e.name):le(`${r}${e.name}`,e.name))}async function z(e,t){try{let{entries:n}=await a(F(`ls`,{path:t}),`could not list this folder`);e.replaceChildren(...R(t,n)),e.childElementCount||e.append(I(w?`No changes.`:`Empty.`))}catch(n){let r=R(t,[]);r.length?e.replaceChildren(...r):e.replaceChildren(I(String(n),`text-red-600`))}}function ce(n,r){let i=L(n),a=[t(`span`,`truncate`,r)];i&&a.push(t(`span`,`ml-auto flex-none rounded bg-amber-100/80 px-1 font-mono text-[10px] text-amber-700`,String(i)));let{el:o,summary:s}=e(``,a);s.className+=` px-2 py-0.5 hover:bg-neutral-100`;let c=t(`div`,`pl-3`);o.append(c);let l=!1,u=()=>{l||(l=!0,c.append(I(`…`)),z(c,n))};return o.ontoggle=()=>{E[o.open?`add`:`delete`](n),o.open&&u()},(w&&v.branch&&!T||E.has(n))&&(o.open=!0,u()),o}function B(e){O?.classList.remove(`bg-indigo-50`,`text-indigo-700`),O=e,e.classList.add(`bg-indigo-50`,`text-indigo-700`)}function le(e,n){let r=x.get(e)?.status,i=t(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-0.5 pl-6 text-left hover:bg-neutral-100`,t(`span`,`truncate`,n));return r&&i.append(t(`span`,`ml-auto flex-none font-mono text-[10.5px] font-semibold ${ne[r]??`text-neutral-500`}`,r)),i.title=e,e===D&&B(i),i.onclick=()=>{B(i),D=e,de(e)},i}let V=()=>void z(j,``),H=(e,n)=>[...e?[t(`span`,`flex-none font-mono text-[10.5px] font-semibold text-emerald-600`,`+${e}`)]:[],...n?[t(`span`,`flex-none font-mono text-[10.5px] font-semibold text-red-600`,`−${n}`)]:[]];function U(e,r,i){let a=x.get(e),o=t(`div`,`sticky top-0 z-10 flex items-center gap-2 border-b border-neutral-200 bg-white px-4 py-1.5 font-mono text-[11.5px] text-neutral-500`,t(`span`,`truncate`,e));a&&(o.append(t(`span`,`flex-none font-semibold ${ne[a.status]??``}`,a.status)),o.append(...H(a.add,a.del)));let s=t(`div`,`ml-auto flex flex-none items-center gap-2`);if(i&&s.append(i),r){let t=document.createElement(`a`);t.className=`flex-none text-neutral-400 hover:text-indigo-700 hover:underline`,t.href=F(`file`,{path:e}),t.download=n(e),t.textContent=`Download`,s.append(t)}return o.append(s),o}function ue(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 W=_e();function G(){N.classList.add(`hidden`),N.replaceChildren(),W.set([],[])}let K=0,q=e=>e===K;function de(e){let t=++K;return x.has(e)?ve(e,t):J(e,t)}async function J(e,n){if(!q(n))return;G();let r=F(`file`,{path:e}),i=t(`div`,`min-w-0`);if(M.classList.remove(`flex`,`flex-col`),M.replaceChildren(U(e,!0),i),te.test(e)){let e=t(`img`,`max-w-full p-4`);e.src=r,e.onerror=()=>i.replaceChildren(I(`Could not load image.`,`text-red-600`)),i.append(e);return}if(e.toLowerCase().endsWith(`.pdf`)){let t=document.createElement(`iframe`);t.className=`h-full w-full`,t.src=r,M.replaceChildren(U(e,!0),t),M.classList.add(`flex`,`flex-col`);return}i.append(I(`…`));let a=await fetch(r);if(!q(n))return;if(!a.ok){let{error:e}=await a.json().catch(()=>({}));i.replaceChildren(I(e??`HTTP ${a.status}`,`text-red-600`));return}if(!(a.headers.get(`content-type`)??``).startsWith(`text/plain`)){i.replaceChildren(I(`Binary file — use Download.`));return}let o=(await a.text()).split(`
4
+ `);if(!q(n))return;o.at(-1)===``&&o.pop();let s=await u(e);q(n)&&i.replaceChildren(f(d(o),s))}function fe(e){let t=[],n=0,r=0,i=!1,a=e.split(`
5
+ `);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 pe(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 me={add:`bg-emerald-400`,del:`bg-red-400`,mixed:`bg-amber-400`};function he(e,n,r){N.replaceChildren(...e.map(e=>{let i=t(`button`,`pointer-events-auto absolute w-full cursor-pointer ${me[e.tone]}`);return i.style.top=`${e.start/n*100}%`,i.style.height=`${Math.max(.4,(e.end-e.start+1)/n*100)}%`,i.title=`line ${ge(r,e.start)}`,i.onclick=()=>r[e.start].scrollIntoView({block:`center`}),i})),N.classList.remove(`hidden`)}let ge=(e,t)=>e[t]?.querySelector(`span`)?.textContent?.trim()||String(t+1);function _e(){let e=[],n=[],r=-1,a=t(`span`,`flex-none text-[10.5px] text-neutral-400`),o=t=>{e.length&&(r=(r+t+e.length)%e.length,n[e[r].start].scrollIntoView({block:`center`}),a.textContent=`${r+1}/${e.length}`)},s=()=>e.length>0&&!i.classList.contains(`hidden`),c=(e,n,r,i)=>{let a=t(`button`,`icon-btn flex-none`,e);return a.onclick=()=>o(n),l(a,i,r,()=>o(n),s),a};return{el:t(`div`,`flex flex-none items-center gap-1`,a,c(`↑`,-1,`Previous change`,[`p`,`k`]),c(`↓`,1,`Next change`,[`n`,`j`])),set(t,i){e=t,n=i,r=-1,a.textContent=`${t.length} change${t.length===1?``:`s`}`}}}async function ve(e,t){G(),M.classList.remove(`flex`,`flex-col`);let n=x.get(e)?.status!==`D`;M.replaceChildren(U(e,n),I(`…`));try{let{diff:r}=await a(P(`diff`,{base:y,head:b,file:e,context:`99999`}),`could not read this diff`);if(!q(t))return;if(!r||/^Binary files /m.test(r))return J(e,t);let i=fe(r);ue(i);let o=await u(e);if(!q(t))return;let s=f(i,o),c=[...s.children],l=pe(i);W.set(l,c),M.replaceChildren(U(e,n,W.el),s),he(l,i.length,c)}catch(n){q(t)&&M.replaceChildren(U(e,!1),I(String(n),`text-red-600`))}}let Y=()=>b&&y===`${b}~1`?b:``,ye=()=>v.commits.find(e=>e.hash===Y())?.subject??``,be=()=>Y()?`${Y()} — ${ye()||`commit`}`:`${y} ↔ ${b||`working tree`}`,xe=(e,n,r,i)=>{let a=t(`button`,`flex h-6 w-6 flex-none cursor-pointer items-center justify-center rounded-md ${r?`bg-indigo-50 text-indigo-700`:`text-neutral-400 hover:bg-neutral-100 hover:text-neutral-700`}`);return a.innerHTML=e,a.title=n,a.onclick=i,a};function X(){let e=xe(`<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="h-3.5 w-3.5"><path d="M2.5 3h11l-4.25 5v4.5l-2.5 1.25V8L2.5 3z" stroke-linejoin="round"/></svg>`,`Show only files that differ`,w,()=>{w=!w,X(),V()}),n=xe(`<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="h-3.5 w-3.5"><path d="m4.5 2.5 3.5 3 3.5-3M4.5 13.5l3.5-3 3.5 3" stroke-linecap="round" stroke-linejoin="round"/></svg>`,T?`Restore folders`:`Collapse all folders`,T,()=>{if(T=!T,T)ae=[...E],E.clear();else for(let e of ae)E.add(e);X(),V()});if(!v.branch){A.replaceChildren(t(`div`,`flex items-center justify-end gap-1 p-1.5`,n));return}let r=C(be());if(Y()){let e=v.commits.find(e=>e.hash===Y());_(r,()=>e?m(e):``)}else r.title=`Choose what to diff`;r.onclick=async()=>{await Te(),ee(r,v,{base:y,head:b},(e,t)=>{y=e,b=t,Z()},$())};let[i,a]=[0,0];for(let e of x.values())i+=e.add,a+=e.del;A.replaceChildren(t(`div`,`flex flex-col gap-1 p-2`,r,t(`div`,`flex items-center gap-1.5 pl-1`,t(`span`,`text-[11px] text-neutral-400`,`${x.size} changed file${x.size===1?``:`s`}`),...H(i,a),t(`span`,`ml-auto`),e,n)))}let Se=()=>{g&&h&&ie(g,{cwd:h,base:y,head:b})},Ce=e=>{let t=e.replace(/~\d+$/,``);return!t||t===`HEAD`||v.refs.some(e=>e.name===t)||v.commits.some(e=>e.hash===t)};async function we(){if(x=new Map,!v.branch)return;let{files:e}=await a(P(`diff`,{base:y,head:b}),`could not read what changed`);for(let t of e)x.set(t.path,{status:t.status,add:t.add,del:t.del});if(x.size<=re)for(let e of x.keys()){let t=e.split(`/`);for(let e=1;e<t.length;e++)E.add(t.slice(0,e).join(`/`))}}async function Z(){Se();try{await we()}catch(e){X(),j.replaceChildren(I(String(e),`text-red-600`));return}X(),V(),D&&de(D)}function Q(){let e=C(h||`Choose a folder…`,`neutral`);e.className+=` max-w-72`,e.title=`Switch folder`,e.onclick=()=>c(e,v.worktrees.map(e=>({path:e.path,...e.branch?{hint:e.branch}:{}})),h||void 0,s);let n=t(`button`,`icon-btn`,`✕`);n.type=`button`,n.title=`Close Files`,n.setAttribute(`aria-label`,`Close Files`),n.onclick=p,n.classList.add(`ml-auto`),k.replaceChildren(e,t(`span`,`truncate font-mono text-[11.5px] text-neutral-400`,v.branch?`⎇ ${v.branch}`:`no git`),n)}async function Te(){if(h)try{v=await a(P(`git`,{}),`could not read this repository`),Q()}catch(e){M.replaceChildren(I(String(e),`text-red-600`))}}let $=()=>v.refs.some(e=>e.name===`main`)?`main`:v.refs.some(e=>e.name===`master`)?`master`:`HEAD`;async function Ee(){if(Q(),D=null,O=null,E.clear(),G(),M.classList.remove(`flex`,`flex-col`),M.replaceChildren(I(`Select a file.`)),!h){A.replaceChildren(),j.replaceChildren(),M.replaceChildren(I(`No folder yet — pick one from the chip above.`));return}try{v=await a(P(`git`,{}),`could not read this repository`)}catch(e){v={branch:null,refs:[],commits:[],worktrees:[]},M.replaceChildren(I(String(e),`text-red-600`))}let e=S(g),t=e&&e.cwd===h&&Ce(e.base)&&Ce(e.head)?e:null;y=t?.base??$(),b=t?.head??``,Q(),await Z()}return r(i,e=>{let t=o(),n=t?.id??``,r=e?.startsWith(`/`)?e:(n===g?h:``)||S(n)?.cwd||t?.cwd||``;if(r===h&&n===g&&j.childElementCount){Q(),Te().then(Z);return}g=n,h=r,Ee()},()=>{G(),M.classList.remove(`flex`,`flex-col`),M.replaceChildren(I(`Select a file.`))})}export{w as createExplorerView};