@timqi/pier 0.1.4 → 0.1.5

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 (111) hide show
  1. package/README.md +4 -4
  2. package/dist/agent/events.js +12 -3
  3. package/dist/agent/listing.js +10 -2
  4. package/dist/agent/packages.js +3 -13
  5. package/dist/agent/pi.js +13 -40
  6. package/dist/channels/attach.js +2 -2
  7. package/dist/channels/commands.js +10 -8
  8. package/dist/channels/config.js +4 -6
  9. package/dist/channels/control.js +60 -14
  10. package/dist/channels/conversations.js +60 -13
  11. package/dist/channels/handoff.js +90 -0
  12. package/dist/channels/lark-api.js +7 -0
  13. package/dist/channels/lark-outbound.js +19 -0
  14. package/dist/channels/lark-panel.js +41 -26
  15. package/dist/channels/lark-render.js +2 -1
  16. package/dist/channels/lark.js +35 -26
  17. package/dist/channels/lines.js +1 -1
  18. package/dist/channels/panel.js +324 -115
  19. package/dist/channels/receipts.js +25 -12
  20. package/dist/channels/routes.js +21 -4
  21. package/dist/channels/runtime.js +21 -7
  22. package/dist/channels/slack-outbound.js +10 -0
  23. package/dist/channels/slack-panel.js +81 -40
  24. package/dist/channels/slack-render.js +3 -1
  25. package/dist/channels/slack.js +29 -14
  26. package/dist/channels/types.js +1 -2
  27. package/dist/cli.js +13 -5
  28. package/dist/core/identity.js +47 -12
  29. package/dist/core/reply.js +4 -1
  30. package/dist/core/router.js +20 -3
  31. package/dist/db.js +6 -1
  32. package/dist/main.js +42 -9
  33. package/dist/settings.js +3 -12
  34. package/dist/socket.js +10 -6
  35. package/dist/tasks/cli.js +17 -7
  36. package/dist/tasks/operations.js +24 -12
  37. package/dist/tools.js +1 -3
  38. package/dist/web/public/assets/{activity-NLS8W9yl.js → activity-CrybM-E8.js} +1 -1
  39. package/dist/web/public/assets/activity-CrybM-E8.js.br +0 -0
  40. package/dist/web/public/assets/activity-CrybM-E8.js.gz +0 -0
  41. package/dist/web/public/assets/{boards-DleegoiC.js → boards-Cw7_6J6L.js} +1 -1
  42. package/dist/web/public/assets/boards-Cw7_6J6L.js.br +0 -0
  43. package/dist/web/public/assets/boards-Cw7_6J6L.js.gz +0 -0
  44. package/dist/web/public/assets/{explorer-vJvV1Sx0.js → explorer-DV066dUD.js} +1 -1
  45. package/dist/web/public/assets/explorer-DV066dUD.js.br +0 -0
  46. package/dist/web/public/assets/explorer-DV066dUD.js.gz +0 -0
  47. package/dist/web/public/assets/index-BrNHu2qj.js +85 -0
  48. package/dist/web/public/assets/index-BrNHu2qj.js.br +0 -0
  49. package/dist/web/public/assets/index-BrNHu2qj.js.gz +0 -0
  50. package/dist/web/public/assets/index-DLszkDUV.css +2 -0
  51. package/dist/web/public/assets/index-DLszkDUV.css.br +0 -0
  52. package/dist/web/public/assets/index-DLszkDUV.css.gz +0 -0
  53. package/dist/web/public/assets/{runs-C5FveZ75.js → runs-DDagTNaM.js} +1 -1
  54. package/dist/web/public/assets/runs-DDagTNaM.js.br +0 -0
  55. package/dist/web/public/assets/runs-DDagTNaM.js.gz +0 -0
  56. package/dist/web/public/assets/settings-BEdSeXpm.js +5 -0
  57. package/dist/web/public/assets/settings-BEdSeXpm.js.br +0 -0
  58. package/dist/web/public/assets/settings-BEdSeXpm.js.gz +0 -0
  59. package/dist/web/public/assets/{task-runs-CJJ2j5Ks.js → task-runs-BnMack9t.js} +1 -1
  60. package/dist/web/public/assets/task-runs-BnMack9t.js.br +0 -0
  61. package/dist/web/public/assets/task-runs-BnMack9t.js.gz +0 -0
  62. package/dist/web/public/assets/{tasks-R2Pv0Abg.js → tasks-BkW7YShZ.js} +1 -1
  63. package/dist/web/public/assets/tasks-BkW7YShZ.js.br +0 -0
  64. package/dist/web/public/assets/tasks-BkW7YShZ.js.gz +0 -0
  65. package/dist/web/public/index.html +2 -2
  66. package/dist/web/public/index.html.br +0 -0
  67. package/dist/web/public/index.html.gz +0 -0
  68. package/dist/web/push.js +4 -10
  69. package/dist/{extensions/web → websearch}/artifacts.js +2 -2
  70. package/dist/websearch/cli.js +73 -0
  71. package/dist/websearch/run.js +273 -0
  72. package/docs/deploy.md +3 -3
  73. package/package.json +2 -3
  74. package/skills/pier-help/SKILL.md +22 -10
  75. package/skills/pier-tasks/SKILL.md +17 -8
  76. package/skills/pier-web/SKILL.md +50 -0
  77. package/dist/channels/telegram-api.js +0 -86
  78. package/dist/channels/telegram-panel.js +0 -97
  79. package/dist/channels/telegram-render.js +0 -69
  80. package/dist/channels/telegram.js +0 -421
  81. package/dist/extensions/index.js +0 -10
  82. package/dist/extensions/web/index.js +0 -9
  83. package/dist/extensions/web/tools.js +0 -265
  84. package/dist/web/public/assets/activity-NLS8W9yl.js.br +0 -0
  85. package/dist/web/public/assets/activity-NLS8W9yl.js.gz +0 -0
  86. package/dist/web/public/assets/boards-DleegoiC.js.br +0 -0
  87. package/dist/web/public/assets/boards-DleegoiC.js.gz +0 -0
  88. package/dist/web/public/assets/explorer-vJvV1Sx0.js.br +0 -0
  89. package/dist/web/public/assets/explorer-vJvV1Sx0.js.gz +0 -0
  90. package/dist/web/public/assets/index-Bs-gol9o.css +0 -2
  91. package/dist/web/public/assets/index-Bs-gol9o.css.br +0 -0
  92. package/dist/web/public/assets/index-Bs-gol9o.css.gz +0 -0
  93. package/dist/web/public/assets/index-Cyp2DKKF.js +0 -85
  94. package/dist/web/public/assets/index-Cyp2DKKF.js.br +0 -0
  95. package/dist/web/public/assets/index-Cyp2DKKF.js.gz +0 -0
  96. package/dist/web/public/assets/runs-C5FveZ75.js.br +0 -0
  97. package/dist/web/public/assets/runs-C5FveZ75.js.gz +0 -0
  98. package/dist/web/public/assets/settings-nJHIBbAz.js +0 -5
  99. package/dist/web/public/assets/settings-nJHIBbAz.js.br +0 -0
  100. package/dist/web/public/assets/settings-nJHIBbAz.js.gz +0 -0
  101. package/dist/web/public/assets/task-runs-CJJ2j5Ks.js.br +0 -0
  102. package/dist/web/public/assets/task-runs-CJJ2j5Ks.js.gz +0 -0
  103. package/dist/web/public/assets/tasks-R2Pv0Abg.js.br +0 -0
  104. package/dist/web/public/assets/tasks-R2Pv0Abg.js.gz +0 -0
  105. /package/dist/{extensions/web → websearch}/anthropic.js +0 -0
  106. /package/dist/{extensions/web → websearch}/content.js +0 -0
  107. /package/dist/{extensions/web → websearch}/http.js +0 -0
  108. /package/dist/{extensions/web → websearch}/json.js +0 -0
  109. /package/dist/{extensions/web → websearch}/language.js +0 -0
  110. /package/dist/{extensions/web → websearch}/openai.js +0 -0
  111. /package/dist/{extensions/web → websearch}/provider.js +0 -0
@@ -30,7 +30,8 @@ added by Pier, not typed by them. It appears only on a change — new speaker, a
30
30
  time only, like \`[14:23]\`. Use that \`id\` to mention someone; never ask for
31
31
  their own. \`place\` is \`<platform>:<conversation>\` (Slack:
32
32
  \`slack:<channel>/<thread_ts>\`), said once per session: the channel and thread a
33
- script takes.
33
+ script takes. Where no tool of yours takes that platform's ids, the header
34
+ carries neither and reads \`[name time platform]\`.
34
35
  `;
35
36
  /** Deployment facts an agent cannot discover: a guessed path is wrong wherever
36
37
  * `PIER_HOME` moved and fails as "nothing is configured"; GPT models carry
@@ -102,6 +103,8 @@ export const quietLabel = (silence) => silence ? `stayed silent — ${silence}`
102
103
  export const isSilentReply = (reply) => !reply.text.trim() && reply.suggestions.length === 0;
103
104
  /** How a reasoning level is spelled wherever a human reads it. */
104
105
  export const thinkingLabel = (level) => level === "xhigh" ? "Extra high" : level[0].toUpperCase() + level.slice(1);
106
+ /** `max` characters, the last one an ellipsis when something was cut. */
107
+ export const cut = (text, max) => (text.length > max ? `${text.slice(0, max - 1)}…` : text);
105
108
  /** 1200 → "1.2K", 12_000 → "12K" — absolute token counts read badly inline. */
106
109
  export const compact = (n) => {
107
110
  if (n < 1000)
@@ -52,6 +52,7 @@ export class Router {
52
52
  hub;
53
53
  resolve;
54
54
  sessionIdOf;
55
+ chatKeyOf;
55
56
  byKey = new Map();
56
57
  bySession = new Map();
57
58
  /** Resolves in flight: two surfaces asking at once must share one session
@@ -70,10 +71,14 @@ export class Router {
70
71
  /** The durable chat → session mapping, read before opening: a chat and the
71
72
  * workbench asking for one transcript must share one lock and one object.
72
73
  * Undefined for a chat that has none yet. */
73
- sessionIdOf = () => undefined) {
74
+ sessionIdOf = () => undefined,
75
+ /** The inverse: the durable chat of a session an alias is opening, so the
76
+ * chat is the delivery key from the first turn (wired in main.ts). */
77
+ chatKeyOf = () => undefined) {
74
78
  this.hub = hub;
75
79
  this.resolve = resolve;
76
80
  this.sessionIdOf = sessionIdOf;
81
+ this.chatKeyOf = chatKeyOf;
77
82
  }
78
83
  registerChannel(channel) {
79
84
  this.channels.set(channel.id, channel);
@@ -233,7 +238,11 @@ export class Router {
233
238
  // triggers, so the answer has a visible cause. The hub carries it whole.
234
239
  if (payload.type === "system-input") {
235
240
  const channel = this.channels.get(key.channelId);
236
- channel?.notify(key.conversationId, { text: digest(payload.text), origin: payload.origin })
241
+ channel?.notify(key.conversationId, {
242
+ text: digest(payload.text),
243
+ origin: payload.origin,
244
+ at: payload.at,
245
+ })
237
246
  .catch((err) => {
238
247
  log.error(`notify ${key.channelId} failed`, err);
239
248
  this.hub.emit(session.id, {
@@ -263,6 +272,13 @@ export class Router {
263
272
  }),
264
273
  };
265
274
  this.bySession.set(session.id, attached);
275
+ // The durable chat outranks the alias that happened to open the session
276
+ // first (a restart, the web speaking first), same rule as `reached`.
277
+ const chat = isAlias(key) ? this.chatKeyOf(session.id) : undefined;
278
+ if (chat) {
279
+ this.byKey.set(keyOf(chat), session);
280
+ attached.key = chat;
281
+ }
266
282
  }
267
283
  /** An adapter's send is several platform calls (chunks, then attachments),
268
284
  * so two answers left to overlap interleave in the chat. Per conversation:
@@ -570,7 +586,8 @@ export class Router {
570
586
  // A chat is named so the agent can hand it to a script (skills/pier-slack);
571
587
  // an alias names nothing a shell could reach.
572
588
  const where = isAlias(msg.key) ? undefined : keyOf(msg.key);
573
- const prompt = withPrefix(this.senders.next(session.id, msg.sender, Date.now(), where), text);
589
+ const opaque = this.channels.get(msg.key.channelId)?.opaqueIds;
590
+ const prompt = withPrefix(this.senders.next(session.id, msg.sender, Date.now(), where, opaque), text);
574
591
  log.debug(`${action} ${keyOf(msg.key)} → session ${session.id} (${String(prompt.length)} chars)`);
575
592
  // A rejected call surfaces on the event stream, never as a throw across the seam.
576
593
  session[action](prompt).catch((err) => {
package/dist/db.js CHANGED
@@ -288,7 +288,6 @@ const MIGRATIONS = [
288
288
  -- every migration. A name filed in the vault first wins (DO NOTHING); the
289
289
  -- channel copy is dropped either way.
290
290
  WITH moved(platform, path, name) AS (VALUES
291
- ('telegram', '$.token', 'TELEGRAM_TOKEN'),
292
291
  ('slack', '$.token', 'SLACK_TOKEN'), ('slack', '$.appToken', 'SLACK_APP_TOKEN'),
293
292
  ('lark', '$.token', 'LARK_APP_ID'), ('lark', '$.appToken', 'LARK_APP_SECRET'))
294
293
  INSERT INTO vault(name, value, updated_at)
@@ -300,6 +299,12 @@ const MIGRATIONS = [
300
299
  `,
301
300
  // 25 — the mid-run decision channel is gone; a control message is steer or follow_up.
302
301
  `DELETE FROM task_messages WHERE json_extract(json, '$.kind') IN ('progress', 'decision', 'reply');`,
302
+ // 26 — a session created from the panel is re-created from its own launch
303
+ // record (channels/conversations.ts); the index serves the session → thread lookups.
304
+ `
305
+ ALTER TABLE conversations ADD COLUMN launch TEXT;
306
+ CREATE INDEX conversations_session ON conversations(session_id);
307
+ `,
303
308
  ];
304
309
  /** `BEGIN IMMEDIATE`: taking the write lock up front turns a race with another
305
310
  * Pier process into a wait, where deferred would be SQLITE_BUSY halfway through. */
package/dist/main.js CHANGED
@@ -15,16 +15,17 @@ import { defaultBoardsDir, registerBoardRoutes } from "./boards/boards.js";
15
15
  import { ChannelStore } from "./channels/config.js";
16
16
  import { createControl } from "./channels/control.js";
17
17
  import { ConversationStore, resolveConversation } from "./channels/conversations.js";
18
+ import { createHandoff } from "./channels/handoff.js";
18
19
  import { registerChannelRoutes } from "./channels/routes.js";
19
20
  import { ChannelRuntime } from "./channels/runtime.js";
20
21
  import { EventHub } from "./core/hub.js";
21
22
  import { splitSpeaker } from "./core/identity.js";
22
23
  import { pierDb } from "./db.js";
23
24
  import { deliverLedger, drainForRestart, RestartLedger } from "./drain.js";
24
- import { BUNDLED } from "./extensions/index.js";
25
25
  import { surfacePrompt } from "./core/reply.js";
26
26
  import { Router } from "./core/router.js";
27
27
  import { acquireInstanceLock } from "./lock.js";
28
+ import { parseWebParams, runWeb } from "./websearch/run.js";
28
29
  import { logger } from "./log.js";
29
30
  import { registerTaskRoutes } from "./tasks/routes.js";
30
31
  import { TaskService } from "./tasks/service.js";
@@ -106,7 +107,7 @@ const router = new Router(hub, (key) => {
106
107
  return factory.resume(key.conversationId);
107
108
  }
108
109
  return resolveIm(key);
109
- }, (key) => conversations.get(key));
110
+ }, (key) => conversations.get(key), (id) => conversations.keyOf(id));
110
111
  const stopEviction = router.startIdleEviction();
111
112
  tasks = new TaskService(new TaskStore(db), factory, router, hub, {
112
113
  modelMenu: () => settings.get().modelMenu,
@@ -123,9 +124,34 @@ const packages = new PiPackageStore(piConfig, { version: currentVersion(), setti
123
124
  // At boot, not lazily: the answer waits for the next Console open (update.ts).
124
125
  packages.watchUpdates();
125
126
  channelStore = new ChannelStore(db, vault);
126
- const control = createControl({ router, factory, conversations, store: channelStore });
127
- const channels = new ChannelRuntime(channelStore, router, control);
128
- resolveIm = resolveConversation(conversations, factory, control.launchFor, (message) => logger("channels").warn(message));
127
+ const sessionState = new SessionStateStore(db);
128
+ const control = createControl({
129
+ router, factory, conversations, store: channelStore,
130
+ modelMenu: () => settings.get().modelMenu,
131
+ });
132
+ // The panel pulls through the handoff and the handoff posts through the
133
+ // runtime: the runtime's half is reached lazily so both can be built.
134
+ const handoff = createHandoff({
135
+ store: channelStore,
136
+ runtime: {
137
+ running: () => channels.running(),
138
+ openThread: (platform, chatId, note) => channels.openThread(platform, chatId, note),
139
+ },
140
+ conversations,
141
+ factory,
142
+ router,
143
+ hub,
144
+ publicUrl: () => settings.get().publicUrl,
145
+ taskSessions: () => tasks.taskSessions(),
146
+ workingSet: () => sessionState.flags(),
147
+ log: (m) => logger("channels").info(m),
148
+ });
149
+ const channels = new ChannelRuntime(channelStore, router, control, handoff);
150
+ resolveIm = resolveConversation(conversations, factory, control.launchFor, (key, message) => {
151
+ logger("channels").warn(`${key.channelId}:${key.conversationId} ${message}`);
152
+ void channels.notify(key.channelId, key.conversationId, message)
153
+ .catch((err) => log.error(`could not tell ${key.channelId} about its re-routed session`, err));
154
+ });
129
155
  // Channels connect once tokens are readable; a refused unlock must not take
130
156
  // down the web surface, which is where the operator repairs it. The chats a
131
157
  // previous restart cut off are told as soon as channels are up (drain.ts).
@@ -248,10 +274,9 @@ registerConfigSyncRoutes(app, {
248
274
  run: configurationSync.run,
249
275
  });
250
276
  registerTaskRoutes(app, tasks, { factory, router });
251
- registerChannelRoutes(app, channelStore, channels);
277
+ registerChannelRoutes(app, channelStore, channels, handoff);
252
278
  registerVaultRoutes(app, { vault, doctor: () => secrets.doctor() });
253
279
  registerBoardRoutes(app);
254
- const sessionState = new SessionStateStore(db);
255
280
  registerPushRoutes(app, {
256
281
  store: new PushStore(db, secrets),
257
282
  hub,
@@ -279,7 +304,7 @@ app.route("/", createServer({
279
304
  names: MANAGED.map((tool) => tool.name),
280
305
  onToolsChanged: toolsUpdate.changed,
281
306
  validateCustomTools: (raw) => {
282
- const validated = normalizeCustomTools(raw, BUNDLED.map((ext) => ext.name));
307
+ const validated = normalizeCustomTools(raw);
283
308
  return validated ? { tools: validated } : { error: CUSTOM_TOOL_RULES };
284
309
  },
285
310
  secrets,
@@ -290,7 +315,7 @@ app.route("/", createServer({
290
315
  backgroundRuns: (id) => tasks.backgroundRuns(id),
291
316
  activeBackgroundRunCounts: () => tasks.activeBackgroundRunCounts(),
292
317
  taskSessions: () => tasks.taskSessions(),
293
- channelOf: (id) => conversations.channelOf(id),
318
+ channelOf: (id) => conversations.keyOf(id)?.channelId,
294
319
  }));
295
320
  const port = Number(process.env.PORT ?? 3141);
296
321
  const hostname = process.env.HOST ?? "127.0.0.1";
@@ -300,6 +325,14 @@ servePier({
300
325
  // no public URL is set, since nothing in the process can discover one.
301
326
  fileUrl: (name) => `${settings.get().publicUrl || `http://127.0.0.1:${String(port)}`}/#/settings/vault?name=${name}`,
302
327
  task: (params, callerSessionId) => tasks.handle(params, callerSessionId),
328
+ // Progress and cost go to the log: the CLI's answer is the text alone.
329
+ web: async (params, callerSessionId) => {
330
+ const parsed = parseWebParams(params);
331
+ const note = (text) => log.info(`web ${callerSessionId}: ${text}`);
332
+ const result = await runWeb(parsed, await factory.webContext(router.modelOf(callerSessionId)), note);
333
+ note(`done ${JSON.stringify(result.details)}`);
334
+ return result;
335
+ },
303
336
  // Live in the router, or on disk: the same two places a callback target is looked for.
304
337
  knows: async (id) => router.stateOf(id) !== undefined || (await factory.find(id)) !== undefined,
305
338
  });
package/dist/settings.js CHANGED
@@ -63,12 +63,8 @@ export function normalizeModelRef(raw) {
63
63
  return null;
64
64
  return { provider: provider.trim(), id: id.trim() };
65
65
  }
66
- /** Shape only: the catalog is code behind the Pi SDK, and an unknown name is
67
- * ignored there, so a downgrade cannot lose a setting it cannot explain. */
68
- export function normalizeExtensions(raw) {
69
- return normalizeNames(raw);
70
- }
71
- /** Shape only, for the same reason: tools.ts owns the catalog. */
66
+ /** Shape only: tools.ts owns the catalog, and an unknown name is ignored
67
+ * there, so a downgrade cannot lose a setting it cannot explain. */
72
68
  export function normalizeTools(raw) {
73
69
  return normalizeNames(raw);
74
70
  }
@@ -98,11 +94,10 @@ export class SettingsStore {
98
94
  modelMenu: this.#json("modelMenu", normalizeModelMenu, "a valid menu") ?? [],
99
95
  ...(titleModel ? { titleModel } : {}),
100
96
  autoUpdate: this.#value("autoUpdate") === "1",
101
- extensions: this.#json("extensions", normalizeExtensions, "a list of names") ?? [],
102
97
  skillsOff: this.#json("skillsOff", normalizeNames, "a list of names") ?? [],
103
98
  tools: this.#json("tools", normalizeTools, "a list of names") ?? [],
104
99
  // `"drop"`: a row the bundled catalog has since taken is redundant, not malformed.
105
- customTools: this.#json("customTools", (raw) => normalizeCustomTools(raw, [], "drop"), "a list of {name, toml}") ?? [],
100
+ customTools: this.#json("customTools", (raw) => normalizeCustomTools(raw, "drop"), "a list of {name, toml}") ?? [],
106
101
  };
107
102
  }
108
103
  /** A malformed row is named, not silently served as the empty value (§5). */
@@ -141,10 +136,6 @@ export class SettingsStore {
141
136
  this.#set("autoUpdate", on ? "1" : "0");
142
137
  return this.get();
143
138
  }
144
- setExtensions(names) {
145
- this.#set("extensions", JSON.stringify(names));
146
- return this.get();
147
- }
148
139
  setSkillsOff(names) {
149
140
  this.#set("skillsOff", JSON.stringify(names));
150
141
  return this.get();
package/dist/socket.js CHANGED
@@ -28,17 +28,21 @@ const ROUTES = {
28
28
  answer(500, { error: String(err) });
29
29
  }
30
30
  },
31
- // 422, not 400: the request was well-formed; what the operation refused is
32
- // the caller's to read.
33
- async "/task"({ task }, { params }, sessionId, answer) {
31
+ "/task": operation("task"),
32
+ "/web": operation("web"),
33
+ };
34
+ /** A CLI verb's params under the caller's session. 422, not 400: the request
35
+ * was well-formed; what the operation refused is the caller's to read. */
36
+ function operation(name) {
37
+ return async (hosts, { params }, sessionId, answer) => {
34
38
  try {
35
- answer(200, { result: await task(params, sessionId) });
39
+ answer(200, { result: await hosts[name](params, sessionId) });
36
40
  }
37
41
  catch (err) {
38
42
  answer(422, { error: err instanceof Error ? err.message : String(err) });
39
43
  }
40
- },
41
- };
44
+ };
45
+ }
42
46
  export function servePier(hosts, path = PIER_SOCK) {
43
47
  const server = createServer((req, res) => void handle(hosts, req, res).catch((err) => {
44
48
  log.error(`${req.method ?? "?"} ${req.url ?? "?"} failed`, err);
package/dist/tasks/cli.js CHANGED
@@ -17,10 +17,10 @@ const OPTIONS = {
17
17
  * command accepts are read off it, so the two cannot drift. */
18
18
  const COMMANDS = {
19
19
  run: {
20
- usage: "run [--prompt <text|->] [--run <id> [--after]] [--task-id <id>] [--session <id>]\n" +
20
+ usage: "run [--prompt <text|-> | --bash <script>] [--run <id> [--after]] [--task-id <id>] [--session <id>]\n" +
21
21
  " [--model <name|?>] [--thinking <level>] [--cwd <dir>] [--name <text>] [--timeout <seconds>]\n" +
22
22
  " [--callback origin|none|steer] [--callback-session <id>] [--join all|first] [--member <flags…>]…",
23
- help: "a prompt on a run: a new one (--prompt | --task-id | --session … --prompt), a batch (--member), or an existing one (--run)",
23
+ help: "a new run (--prompt | --bash | --task-id | --session … --prompt), a batch (--member), or a prompt on an existing one (--run)",
24
24
  },
25
25
  save: {
26
26
  usage: "save [--task-id <id>] --name <text> (--prompt <text|-> | --bash <script>)\n" +
@@ -35,7 +35,7 @@ const COMMANDS = {
35
35
  const USAGE = [
36
36
  "usage: pier task <command> … — subagents and scheduled tasks (skills/pier-tasks)",
37
37
  ...Object.values(COMMANDS).map(({ usage, help }) => ` ${usage}\n ${help}`),
38
- "`--prompt -` reads stdin. The receipt is one line of JSON, exit 0; a refusal is one `task:` line, exit 1.",
38
+ "`--prompt -` reads stdin. The receipt is one line of JSON (`--model ?` one pin per line), exit 0; a refusal is one `task:` line, exit 1.",
39
39
  ].join("\n");
40
40
  /** Flags that belong to the batch, never to one member. */
41
41
  const GROUP_FLAGS = ["join", "callback", "callback-session"];
@@ -103,8 +103,9 @@ export async function runTaskCli(argv, post, io = processIo) {
103
103
  }
104
104
  const { status, body } = await post(params);
105
105
  if (status === 200) {
106
- // Compact: the reader is a model, and the ids are what it keeps.
107
- io.stdout(JSON.stringify(body.result));
106
+ // Compact: the reader is a model, and the ids are what it keeps; an answer
107
+ // the server already wrote in lines (`--model ?`) is printed as it came.
108
+ io.stdout(typeof body.result === "string" ? body.result : JSON.stringify(body.result));
108
109
  return 0;
109
110
  }
110
111
  io.stderr(`task: ${body.error ?? `socket answered ${String(status)}`}`);
@@ -141,12 +142,21 @@ function build(name, parsed, io) {
141
142
  const launch = launchOf(v);
142
143
  const timeoutSeconds = seconds("timeout", v.timeout);
143
144
  if (v["task-id"] !== undefined) {
144
- const extra = flagsOf(own).find((flag) => ["prompt", "session", "cwd", "model", "thinking", "name", "timeout"].includes(flag));
145
+ const extra = flagsOf(own).find((flag) => ["prompt", "bash", "session", "cwd", "model", "thinking", "name", "timeout"].includes(flag));
145
146
  if (extra)
146
147
  refuse(`--${extra} does not apply to a saved definition (--task-id)`);
147
148
  return { task_id: v["task-id"] };
148
149
  }
149
- const prompt = text(v.prompt) ?? refuse("a new run needs --prompt or --task-id");
150
+ if (v.bash !== undefined) {
151
+ if (v.prompt !== undefined)
152
+ refuse("a run takes exactly one of --prompt or --bash");
153
+ if (launch)
154
+ refuse("--model/--thinking apply to a prompt, not --bash");
155
+ if (v.session !== undefined)
156
+ refuse("--session continues an agent session; --bash is a command, not a turn");
157
+ return compact({ name: v.name, timeoutSeconds, action: compact({ type: "bash", script: v.bash, cwd: v.cwd }) });
158
+ }
159
+ const prompt = text(v.prompt) ?? refuse("a new run needs --prompt, --bash or --task-id");
150
160
  if (v.session === undefined)
151
161
  return compact({ prompt, cwd: v.cwd, launch, name: v.name, timeoutSeconds });
152
162
  if (v.cwd !== undefined)
@@ -129,8 +129,11 @@ export async function handleTask(host, definitions, store, raw, callerSessionId)
129
129
  }
130
130
  if (input.operation === "run") {
131
131
  // `--model ?`: the menu instead of a run, the one lookup the common case never pays.
132
- if (record(input.launch)?.model === "?")
133
- return host.models();
132
+ if (record(input.launch)?.model === "?") {
133
+ const { source, models } = await host.models();
134
+ const head = source === "menu" ? "the operator's menu" : "the live catalog (no model pinned)";
135
+ return `${head} — --model takes a provider/id or a unique substring of one:\n${menuLines(models)}`;
136
+ }
134
137
  const callbackMode = callbackModeOf(input);
135
138
  if (Array.isArray(input.tasks)) {
136
139
  // Core-joined fan-out: members run detached, one aggregated callback.
@@ -245,8 +248,8 @@ function inlineDraft(input) {
245
248
  return { prompt, cwd, launch, name, timeoutSeconds };
246
249
  }
247
250
  /** A label for the Console, not an identifier. */
248
- function nameFromPrompt(prompt) {
249
- const line = prompt.split("\n")
251
+ function nameFrom(text) {
252
+ const line = text.split("\n")
250
253
  .map((l) => l.replace(/^[\s#>*-]+/, "").replace(/[*_`]/g, "").replace(/\s+/g, " ").trim())
251
254
  .find(Boolean) ?? "subagent";
252
255
  return line.length > 60 ? `${line.slice(0, 59).trimEnd()}…` : line;
@@ -257,7 +260,6 @@ function nameFromPrompt(prompt) {
257
260
  function resolveModel(name, menu) {
258
261
  const needle = name.trim().toLowerCase();
259
262
  const full = (pin) => `${pin.provider}/${pin.id}`;
260
- const line = (pin) => `${full(pin)}${pin.thinking ? ` · ${pin.thinking}` : ""}${pin.note ? ` — ${pin.note}` : ""}`;
261
263
  const exact = menu.find((pin) => full(pin).toLowerCase() === needle);
262
264
  const hits = exact ? [exact] : menu.filter((pin) => `${full(pin)} ${pin.note ?? ""}`.toLowerCase().includes(needle));
263
265
  if (hits.length === 1)
@@ -265,9 +267,11 @@ function resolveModel(name, menu) {
265
267
  const slash = name.indexOf("/");
266
268
  if (!hits.length && slash > 0 && slash < name.length - 1)
267
269
  return { model: { provider: name.slice(0, slash), id: name.slice(slash + 1) } };
268
- const lines = (hits.length ? hits : menu).map(line).join("\n") || "(no model is pinned or available)";
269
- throw new Error(`model "${name}" matches ${String(hits.length)} of the menu:\n${lines}`);
270
+ throw new Error(`model "${name}" matches ${String(hits.length)} of the menu:\n${menuLines(hits.length ? hits : menu)}`);
270
271
  }
272
+ /** One pin per line, the only shape a menu is ever printed in. */
273
+ const menuLines = (menu) => menu.map((pin) => `${pin.provider}/${pin.id}${pin.thinking ? ` · ${pin.thinking}` : ""}${pin.note ? ` — ${pin.note}` : ""}`).join("\n")
274
+ || "(no model is pinned or available)";
271
275
  /** A `prompt` shorthand becomes a fresh Agent action in the caller's own
272
276
  * directory; everything the caller did spell out passes through to parseDraft. */
273
277
  async function expandDraft(definitions, menu, raw, callerSessionId) {
@@ -282,14 +286,22 @@ async function expandDraft(definitions, menu, raw, callerSessionId) {
282
286
  }
283
287
  const action = record(draft.action);
284
288
  const session = record(action?.session);
285
- if (action?.type === "agent" && session?.mode === "fresh" && (session.cwd === undefined || (typeof session.cwd === "string" && !isAbsolute(session.cwd)))) {
289
+ const absolute = async (cwd) => {
286
290
  const base = await definitions.sessionCwd(callerSessionId);
287
291
  if (!base)
288
- throw new Error(`cwd ${session.cwd === undefined ? "omitted" : `"${session.cwd}" is relative`} and the calling session has no working directory; give an absolute path`);
289
- draft = { ...draft, action: { ...action, session: { ...session, cwd: resolve(base, session.cwd ?? ".") } } };
292
+ throw new Error(`cwd ${cwd === undefined ? "omitted" : `"${String(cwd)}" is relative`} and the calling session has no working directory; give an absolute path`);
293
+ return resolve(base, typeof cwd === "string" ? cwd : ".");
294
+ };
295
+ const relative = (cwd) => cwd === undefined || (typeof cwd === "string" && !isAbsolute(cwd));
296
+ if (action?.type === "agent" && session?.mode === "fresh" && relative(session.cwd)) {
297
+ draft = { ...draft, action: { ...action, session: { ...session, cwd: await absolute(session.cwd) } } };
298
+ }
299
+ if (action?.type === "bash" && relative(action.cwd)) {
300
+ draft = { ...draft, action: { ...action, cwd: await absolute(action.cwd) } };
290
301
  }
291
- if (draft.name === undefined && typeof action?.prompt === "string")
292
- draft = { ...draft, name: nameFromPrompt(action.prompt) };
302
+ const label = action?.prompt ?? action?.script;
303
+ if (draft.name === undefined && typeof label === "string")
304
+ draft = { ...draft, name: nameFrom(label) };
293
305
  const launch = record(action?.launch);
294
306
  if (typeof launch?.model === "string") {
295
307
  const { model, thinking } = resolveModel(launch.model, await menu());
package/dist/tools.js CHANGED
@@ -80,14 +80,12 @@ export const CUSTOM_TOOL_RULES = `each custom tool needs a name (letters, digits
80
80
  * `install_dir` anywhere, and a managed name twice is a switch whose meaning
81
81
  * depends on which ran last. Key validity is ubix's to report. */
82
82
  export function normalizeCustomTools(raw,
83
- /** Names this file cannot see: the bundled extensions, handed in by main.ts. */
84
- reserved = [],
85
83
  /** `"drop"` when reading a stored row: the catalog may have grown into that
86
84
  * name after it was written, and the bundled row installs the same binary.
87
85
  * The declaration stays in the row for a Pier that stops bundling it. */
88
86
  managedName = "reject") {
89
87
  // Case-insensitively: one filename on a case-insensitive filesystem.
90
- const taken = new Set([...MANAGED.map((tool) => tool.name), ...reserved, "ubix"].map((name) => name.toLowerCase()));
88
+ const taken = new Set([...MANAGED.map((tool) => tool.name), "ubix"].map((name) => name.toLowerCase()));
91
89
  if (!Array.isArray(raw) || raw.length > MAX_CUSTOM)
92
90
  return null;
93
91
  const tools = [];
@@ -1,4 +1,4 @@
1
- import{E as e,G as t,H as n,J as r,K as i,M as a,R as o,S as s,Y as c,Z as l,nt as u}from"./index-Cyp2DKKF.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?`-`:t(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(t,p,h){let g=`sessions`,_=`active`,v={sessions:[],runs:[],messages:[]},y=``,b=c(async()=>{let e=_,n=await l(`/api/activity?scope=${e}`,`Failed to load activity`);if(!n.ok){y=``,t.replaceChildren(i(`p`,`p-4 text-[13px] text-red-600`,n.error));return}if(e!==_)return;let r=JSON.stringify(n.value);if(`${e}:${r}`===y)return;y=`${e}:${r}`;let a=n.value;v={...a,sessions:a.sessions.map(e=>({...e,title:u(e.title)}))},x()});function x(){let e=o(a([[`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`),a([[`Current`,`active`],[`Last 24h`,`recent`]],_,e=>{_=e,x(),b()})),n=i(`div`,`automation-list min-h-0 flex-1 overflow-auto`);g===`sessions`?S(n):w(n),t.replaceChildren(e,n)}function S(t){let n=document.createElement(`table`);n.className=`w-full table-fixed text-left text-[12.5px]`,n.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{E as e,G as t,H as n,J as r,K as i,M as a,R as o,S as s,Y as c,Z as l,nt as u}from"./index-BrNHu2qj.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?`-`:t(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(t,p,h){let g=`sessions`,_=`active`,v={sessions:[],runs:[],messages:[]},y=``,b=c(async()=>{let e=_,n=await l(`/api/activity?scope=${e}`,`Failed to load activity`);if(!n.ok){y=``,t.replaceChildren(i(`p`,`p-4 text-[13px] text-red-600`,n.error));return}if(e!==_)return;let r=JSON.stringify(n.value);if(`${e}:${r}`===y)return;y=`${e}:${r}`;let a=n.value;v={...a,sessions:a.sessions.map(e=>({...e,title:u(e.title)}))},x()});function x(){let e=o(a([[`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`),a([[`Current`,`active`],[`Last 24h`,`recent`]],_,e=>{_=e,x(),b()})),n=i(`div`,`automation-list min-h-0 flex-1 overflow-auto`);g===`sessions`?S(n):w(n),t.replaceChildren(e,n)}function S(t){let n=document.createElement(`table`);n.className=`w-full table-fixed text-left text-[12.5px]`,n.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>
@@ -1 +1 @@
1
- import{A as e,C as t,E as n,H as r,K as i,L as a,T as o,U as s,X as c,Z as l,et as u,q as d,tt as f}from"./index-Cyp2DKKF.js";var p=e=>e.public&&e.token?`/p/${e.slug}-${e.token}/`:`/boards/${e.slug}/`;function m(m,h){let g=[],_=``,v=i(`header`,`pagehead sticky top-[-8px] z-30 max-md:hidden`,e(`Boards`)),y=i(`div`,`px-4 pb-5 pt-1`);m.append(i(`div`,`min-h-0 flex-1 overflow-y-auto`,v,y));async function b(e,t){let n=await f(`/api/boards/${e.slug}`,{public:t},`PATCH`);if(n.ok)return e.token=(await n.json().catch(()=>({}))).token??``,w();_=await c(n,`Could not change ${e.slug}`),await T()}async function x(e){_=await u(`/api/boards/${e}`,`DELETE`,`Could not delete ${e}`)??``,await T()}function S(e){let n=i(`div`,`group flex flex-col gap-1 py-2.5`),r=document.createElement(`a`);r.className=`min-w-0 flex-1 truncate text-[13px] font-medium text-neutral-800 hover:text-indigo-700 max-md:basis-full`,r.href=p(e),r.target=`_blank`,r.rel=`noreferrer`,r.textContent=e.title;let o=i(`div`,`flex flex-wrap items-center gap-x-2 gap-y-1`,r),c=a(``,``,e.public,t=>{e.public=t,b(e,t)});c.className=`flex flex-none cursor-pointer items-center gap-1.5 text-[11.5px] text-neutral-500`,c.append(i(`span`,``,`Public`));let l=t(`Delete`,`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-red-600 group-hover:opacity-100 pointer-coarse:opacity-100`);l.title=`Renames the folder on disk; nothing is erased`,l.onclick=()=>void x(e.slug);let u=s(`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-indigo-600 group-hover:opacity-100 pointer-coarse:opacity-100`,()=>`${location.origin}${p(e)}`);u.title=`Copy the board's link`,o.append(i(`div`,`ml-auto flex flex-none items-center gap-2`,c,u,l)),n.append(o);let f=i(`div`,`flex flex-wrap items-center gap-x-3 gap-y-1 text-[11.5px] text-neutral-500`);f.append(i(`span`,`min-w-0 break-all font-mono text-neutral-400`,e.slug)),e.description&&f.append(i(`span`,`min-w-0 truncate`,e.description)),f.append(i(`span`,`flex-none text-neutral-400`,d(Date.parse(e.updatedAt))));for(let n of e.sessions){let e=t(n.slice(0,8),`flex-none cursor-pointer font-mono text-indigo-600 hover:underline`);e.title=`Open session ${n}`,e.onclick=()=>h(n),f.append(e)}if(e.public&&e.token){let t=i(`span`,`min-w-0 break-all font-mono text-neutral-400`,p(e));t.title=`Public URL — anyone holding this link can read the board, no password`,f.append(t)}return n.append(f),n}let C=[{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 w(){let e=i(`div`,`mx-auto flex w-full max-w-3xl flex-col gap-4`);g.length===0&&e.append(n(`No boards yet. Ask an agent to build one — it writes a folder under ~/.pier/boards.`));for(let{title:t,hint:n,wanted:r}of C){let a=g.filter(e=>e.public===r);a.length!==0&&e.append(o(t,n,i(`div`,`flex flex-col divide-y divide-neutral-200/70`,...a.map(S))))}y.replaceChildren(..._?[i(`p`,`mb-3 text-[13px] text-red-600`,_)]:[],e)}async function T(){let e=await l(`/api/boards`,`Could not load boards`);if(!e.ok)return _=e.error,w();g=e.value,w(),_=``}let E=r(m,()=>void T());return window.addEventListener(`focus`,()=>{E.visible&&T()}),E}export{m as createBoardsView};
1
+ import{A as e,C as t,E as n,H as r,K as i,L as a,T as o,U as s,X as c,Z as l,et as u,q as d,tt as f}from"./index-BrNHu2qj.js";var p=e=>e.public&&e.token?`/p/${e.slug}-${e.token}/`:`/boards/${e.slug}/`;function m(m,h){let g=[],_=``,v=i(`header`,`pagehead sticky top-[-8px] z-30 max-md:hidden`,e(`Boards`)),y=i(`div`,`px-4 pb-5 pt-1`);m.append(i(`div`,`min-h-0 flex-1 overflow-y-auto`,v,y));async function b(e,t){let n=await f(`/api/boards/${e.slug}`,{public:t},`PATCH`);if(n.ok)return e.token=(await n.json().catch(()=>({}))).token??``,w();_=await c(n,`Could not change ${e.slug}`),await T()}async function x(e){_=await u(`/api/boards/${e}`,`DELETE`,`Could not delete ${e}`)??``,await T()}function S(e){let n=i(`div`,`group flex flex-col gap-1 py-2.5`),r=document.createElement(`a`);r.className=`min-w-0 flex-1 truncate text-[13px] font-medium text-neutral-800 hover:text-indigo-700 max-md:basis-full`,r.href=p(e),r.target=`_blank`,r.rel=`noreferrer`,r.textContent=e.title;let o=i(`div`,`flex flex-wrap items-center gap-x-2 gap-y-1`,r),c=a(``,``,e.public,t=>{e.public=t,b(e,t)});c.className=`flex flex-none cursor-pointer items-center gap-1.5 text-[11.5px] text-neutral-500`,c.append(i(`span`,``,`Public`));let l=t(`Delete`,`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-red-600 group-hover:opacity-100 pointer-coarse:opacity-100`);l.title=`Renames the folder on disk; nothing is erased`,l.onclick=()=>void x(e.slug);let u=s(`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-indigo-600 group-hover:opacity-100 pointer-coarse:opacity-100`,()=>`${location.origin}${p(e)}`);u.title=`Copy the board's link`,o.append(i(`div`,`ml-auto flex flex-none items-center gap-2`,c,u,l)),n.append(o);let f=i(`div`,`flex flex-wrap items-center gap-x-3 gap-y-1 text-[11.5px] text-neutral-500`);f.append(i(`span`,`min-w-0 break-all font-mono text-neutral-400`,e.slug)),e.description&&f.append(i(`span`,`min-w-0 truncate`,e.description)),f.append(i(`span`,`flex-none text-neutral-400`,d(Date.parse(e.updatedAt))));for(let n of e.sessions){let e=t(n.slice(0,8),`flex-none cursor-pointer font-mono text-indigo-600 hover:underline`);e.title=`Open session ${n}`,e.onclick=()=>h(n),f.append(e)}if(e.public&&e.token){let t=i(`span`,`min-w-0 break-all font-mono text-neutral-400`,p(e));t.title=`Public URL — anyone holding this link can read the board, no password`,f.append(t)}return n.append(f),n}let C=[{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 w(){let e=i(`div`,`mx-auto flex w-full max-w-3xl flex-col gap-4`);g.length===0&&e.append(n(`No boards yet. Ask an agent to build one — it writes a folder under ~/.pier/boards.`));for(let{title:t,hint:n,wanted:r}of C){let a=g.filter(e=>e.public===r);a.length!==0&&e.append(o(t,n,i(`div`,`flex flex-col divide-y divide-neutral-200/70`,...a.map(S))))}y.replaceChildren(..._?[i(`p`,`mb-3 text-[13px] text-red-600`,_)]:[],e)}async function T(){let e=await l(`/api/boards`,`Could not load boards`);if(!e.ok)return _=e.error,w();g=e.value,w(),_=``}let E=r(m,()=>void T());return window.addEventListener(`focus`,()=>{E.visible&&T()}),E}export{m as createBoardsView};
@@ -1,4 +1,4 @@
1
- import{A as e,C as t,E as n,H as r,K as i,Q as a,V as o,W as s,_ as c,a as l,at as u,dt as d,f,h as p,i as m,it as h,l as g,m as ee,p as te,q as _,s as ne,ut as re,v as ie}from"./index-Cyp2DKKF.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(`
1
+ import{A as e,C as t,E as n,H as r,K as i,Q as a,V as o,W as s,_ as c,a as l,at as u,dt as d,f,h as p,i as m,it as h,l as g,m as ee,p as te,q as _,s as ne,ut as re,v as ie}from"./index-BrNHu2qj.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
2
 
3
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=i(`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=i(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-1 text-left hover:bg-neutral-100`,h(re,`h-3 w-3 text-indigo-600 ${e.value===t?``:`invisible`}`),i(`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(i(`span`,`min-w-0 truncate text-[11px] text-neutral-400`,e.subject)),e.author&&r.append(i(`span`,`ml-auto flex-none pl-1.5 text-[10.5px] text-neutral-400`,`${e.author} · ${e.at?_(e.at):``}`)),ue(r,()=>le(e)),r.onclick=()=>n(e.value),r}function de(e,t,n,r,a){let{base:o,head:s}=n,c=()=>s&&o===`${s}~1`?s:``,u=c()?`commit`:`compare`,d=t.commits[0],f=e=>[...e?[{value:``,label:`Working tree`,subject:`the files on disk, uncommitted changes included`}]:[],{...d,value:`HEAD`},...t.refs.map(e=>({value:e.name,subject:e.subject})),...t.commits.map(e=>({...e,value:e.hash}))],p=i(`div`,`flex w-[38rem] max-w-full flex-col`);function m(){let e=(e,t)=>{let n=i(`button`,`cursor-pointer rounded-full px-2.5 py-0.5 text-[11.5px] ${u===t?`bg-indigo-50 font-medium text-indigo-700`:`text-neutral-500 hover:bg-neutral-100`}`,e);return n.onclick=()=>{u=t,m()},n},n=(e,t,n,r)=>i(`div`,`flex min-w-0 flex-1 flex-col`,i(`div`,`flex-none px-2 pb-0.5 text-[10.5px] font-semibold uppercase tracking-wide text-neutral-400`,e),i(`div`,`min-h-0 flex-1 overflow-y-auto`,...t.map(e=>S(e,n,r)))),d=u===`compare`?i(`div`,`flex h-72 min-h-0 divide-x divide-neutral-200 pb-1`,n(`base`,f(!1),o,e=>{o=e,m(),r(o,s)}),n(`to`,f(!0),s,e=>{s=e,m(),r(o,s)})):i(`div`,`flex h-72 min-h-0 flex-col overflow-y-auto pb-1`,...t.commits.map(e=>S({value:e.hash,...e},c(),e=>{o=`${e}~1`,s=e,l(),r(o,s)}))),g=i(`div`,`flex-none border-t border-neutral-200`,S({value:a,label:`${a} ↔ Working tree`,subject:`back to the default: the main line's tip vs the files on disk`},o===a&&s===``?a:``,()=>{o=a,s=``,m(),r(o,s)}));g.querySelector(`button > span`).prepend(h(v)),p.replaceChildren(i(`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`)),d,...u===`compare`?[g]:[])}m(),ne(e,p)}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,n=`indigo`)=>t(e,`${ge} cursor-pointer truncate text-left ${n===`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(l,_,ne,re){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=i(`header`,`pagehead flex-nowrap`),M=i(`div`,`${p} flex-col items-stretch gap-1.5 px-3 py-2.5`),N=i(`div`,`min-h-0 flex-1 overflow-y-auto px-1.5 py-1.5`),ye=i(`aside`,`${ie} w-64 flex-none text-[12.5px] leading-5 max-md:h-1/2 max-md:w-full`,M,N),P=i(`div`,`${p} flex-wrap gap-2 px-4 py-2.5 font-mono text-[11.5px] text-neutral-500`),F=i(`div`,`h-full min-w-0 overflow-auto`),I=i(`div`,`pointer-events-none absolute inset-y-1 right-0.5 z-20 hidden w-2`),be=i(`div`,`relative min-h-0 flex-1`,F,I),xe=i(`section`,`${ie} min-h-0 min-w-0 flex-1`,P,be);l.append(ve,i(`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`)=>i(`p`,`px-3 py-2 text-[12.5px] ${t}`,e),B=e=>i(`div`,`flex h-full items-center justify-center p-6`,n(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 a(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,t){let n=Ce(e),r=[i(`span`,`truncate`,t)];n&&r.push(i(`span`,`ml-auto flex-none rounded-full bg-amber-100/80 px-1.5 font-mono text-[10px] font-semibold text-amber-700`,String(n)));let{el:a,summary:o}=s(``,r);o.className+=` rounded-lg px-2 py-1 transition-colors hover:bg-neutral-100`;let c=i(`div`,`pl-3`);a.append(c);let l=!1,u=()=>{l||(l=!0,c.append(z(`…`)),Te(c,e))};return a.ontoggle=()=>{O[a.open?`add`:`delete`](e),a.open&&u()},((b.branch?T:!e.includes(`/`))&&!E||O.has(e))&&(a.open=!0,u()),a}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=i(`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`,i(`span`,`truncate`,t));return n&&r.append(i(`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?[i(`span`,`flex-none font-mono text-[10.5px] font-semibold text-emerald-600`,`+${e}`)]:[],...t?[i(`span`,`flex-none font-mono text-[10.5px] font-semibold text-red-600`,`−${t}`)]:[]];function W(e,t=!1,n){if(P.classList.toggle(`hidden`,!e),P.classList.toggle(`flex`,!!e),!e)return P.replaceChildren();let r=w.get(e),a=[i(`span`,`min-w-0 truncate`,e)];r&&(a.push(i(`span`,`flex-none font-semibold ${C[r.status]??``}`,r.status)),a.push(...U(r.add,r.del)));let s=i(`div`,`ml-auto flex flex-none items-center gap-2`);if(n&&s.append(n),t){let t=document.createElement(`a`);t.className=`btn flex-none text-[12px]`,t.href=R(`file`,{path:e}),t.download=o(e),t.textContent=`Download`,s.append(t)}P.replaceChildren(...a,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=i(`div`,`min-w-0`);if(F.classList.remove(`flex`,`flex-col`),W(e,!0),F.replaceChildren(r),fe.test(e)){let e=i(`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 a=await fetch(n);if(!q(t))return;if(!a.ok){let{error:e}=await a.json().catch(()=>({}));r.replaceChildren(z(e??`HTTP ${a.status}`,`text-red-600`));return}if(!(a.headers.get(`content-type`)??``).startsWith(`text/plain`)){r.replaceChildren(z(`Binary file — use Download.`));return}let o=te(await a.text());if(!q(t))return;let s=await ee(e);q(t)&&r.replaceChildren(f(o,s))}function je(e){let t=[],n=0,r=0,i=!1,a=e.split(`
4
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=i(`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=i(`span`,`flex-none text-[10.5px] text-neutral-400`),a=i=>{e.length&&(n=(n+i+e.length)%e.length,t[e[n].start].scrollIntoView({block:`center`}),r.textContent=`${n+1}/${e.length}`)},o=()=>e.length>0&&!l.classList.contains(`hidden`),s=(e,t,n,r)=>{let s=i(`button`,`icon-btn flex-none`,h(e));return s.onclick=()=>a(t),g(s,r,n,()=>a(t),o),s};return{el:i(`div`,`flex flex-none items-center gap-1`,r,s(d,-1,`Previous change`,[`p`,`k`]),s(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 a(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 o=await ee(e);if(!q(t))return;let s=f(i,o),c=[...s.children],l=Me(i);G.set(l,c),W(e,n,G.el),F.replaceChildren(s),Pe(l,i.length,c)}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 a=i(`button`,`icon-btn ${n?`bg-indigo-50 text-indigo-700 hover:bg-indigo-100 hover:text-indigo-700`:``}`);return a.append(h(e)),a.title=t,a.setAttribute(`aria-label`,t),a.setAttribute(`aria-pressed`,String(n)),a.onclick=r,a};function X(){let e=Be(se,`Show only files that differ`,T,()=>{T=!T,X(),H()}),n=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(i(`div`,`flex items-center gap-1.5`,i(`span`,`min-w-0 flex-1 text-[11px] leading-snug text-neutral-400`,`Not a git repository — nothing to compare.`),n));return}let r=t(ze(),`${c} font-mono`);if(Y()){let e=b.commits.find(e=>e.hash===Y());ue(r,()=>e?le(e):``)}else r.title=`Choose what to diff`;r.onclick=async()=>{await We(),de(r,b,{base:x,head:S},(e,t)=>{x=e,S=t,Z()},$())};let[a,o]=[0,0];for(let e of w.values())a+=e.add,o+=e.del;M.replaceChildren(r,i(`div`,`flex items-center gap-1.5 pl-0.5`,i(`span`,`text-[11px] text-neutral-400`,`${w.size} changed file${w.size===1?``:`s`}`),...U(a,o),i(`span`,`ml-auto`),e,n))}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 a(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(i(`span`,`truncate max-md:hidden`,v||`Choose a folder…`),i(`span`,`truncate md:hidden`,v?o(v):`Choose a folder…`)),t.title=v?`${v} — switch folder`:`Choose a folder`,t.onclick=()=>m(t,b.worktrees.map(e=>({path:e.path,...e.branch?{hint:e.branch}:{}})),v||void 0,ne);let n=i(`button`,`icon-btn ml-auto`,h(u));n.type=`button`,n.title=`Close Files`,n.setAttribute(`aria-label`,`Close Files`),n.onclick=re;let r=b.branch?[i(`span`,`${ge} bg-neutral-100 text-neutral-600`,h(ce),i(`span`,`truncate`,b.branch))]:[];ve.replaceChildren(e(`Files`),t,...r,n)}async function We(){if(v)try{b=await a(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 a(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 r(l,(e,t)=>{let n=_(),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};