@qodeca/xezar 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -15
- package/dist/agent-config/account-identity.d.ts +2 -8
- package/dist/agent-config/account-identity.js +54 -10
- package/dist/agent-config/account-identity.js.map +1 -1
- package/dist/agent-config/catalog.d.ts +49 -7
- package/dist/agent-config/catalog.js +107 -0
- package/dist/agent-config/catalog.js.map +1 -1
- package/dist/agent-config/model-settings/pi.d.ts +12 -6
- package/dist/agent-config/model-settings/pi.js +12 -6
- package/dist/agent-config/model-settings/pi.js.map +1 -1
- package/dist/agent-config/seed.js +10 -0
- package/dist/agent-config/seed.js.map +1 -1
- package/dist/contract/github.d.ts +15 -0
- package/dist/contract/index.d.ts +9 -0
- package/dist/contract/index.js +1527 -895
- package/dist/contract/mcp-api-reference.d.ts +174 -0
- package/dist/contract/mcp-audit.d.ts +103 -0
- package/dist/contract/mcp-discovery.d.ts +257 -0
- package/dist/contract/mcp-event-catalog.d.ts +121 -0
- package/dist/contract/mcp-event-catalog.test.d.ts +1 -0
- package/dist/contract/mcp-idempotency.d.ts +200 -0
- package/dist/contract/mcp-journal.d.ts +268 -0
- package/dist/contract/mcp-leader.d.ts +88 -0
- package/dist/contract/mcp-ownership.d.ts +78 -0
- package/dist/contract/mcp-versioning.d.ts +139 -0
- package/dist/contract/runs.d.ts +81 -0
- package/dist/core/agent-profiles.d.ts +15 -4
- package/dist/core/agent-profiles.js +22 -8
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/opencode-server-runner.js +71 -11
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/secret-redaction.d.ts +7 -1
- package/dist/core/secret-redaction.js +44 -10
- package/dist/core/secret-redaction.js.map +1 -1
- package/dist/index.js +138 -27
- package/dist/index.js.map +1 -1
- package/dist/machine-identity.d.ts +66 -0
- package/dist/machine-identity.js +96 -0
- package/dist/machine-identity.js.map +1 -0
- package/dist/mcp/adapters/claude-code.d.ts +41 -0
- package/dist/mcp/adapters/claude-code.js +40 -0
- package/dist/mcp/adapters/claude-code.js.map +1 -0
- package/dist/mcp/adapters/codex.d.ts +140 -0
- package/dist/mcp/adapters/codex.js +323 -0
- package/dist/mcp/adapters/codex.js.map +1 -0
- package/dist/mcp/adapters/opencode.d.ts +148 -0
- package/dist/mcp/adapters/opencode.js +528 -0
- package/dist/mcp/adapters/opencode.js.map +1 -0
- package/dist/mcp/adapters/pi-link.d.ts +102 -0
- package/dist/mcp/adapters/pi-link.js +235 -0
- package/dist/mcp/adapters/pi-link.js.map +1 -0
- package/dist/mcp/adapters/pi.d.ts +188 -0
- package/dist/mcp/adapters/pi.js +418 -0
- package/dist/mcp/adapters/pi.js.map +1 -0
- package/dist/mcp/api-reference.d.ts +26 -0
- package/dist/mcp/api-reference.js +133 -0
- package/dist/mcp/api-reference.js.map +1 -0
- package/dist/mcp/audit-trail.d.ts +162 -0
- package/dist/mcp/audit-trail.js +306 -0
- package/dist/mcp/audit-trail.js.map +1 -0
- package/dist/mcp/bridge.d.ts +81 -0
- package/dist/mcp/bridge.js +407 -0
- package/dist/mcp/bridge.js.map +1 -0
- package/dist/mcp/connection-file.d.ts +49 -0
- package/dist/mcp/connection-file.js +63 -0
- package/dist/mcp/connection-file.js.map +1 -0
- package/dist/mcp/echo-guard.d.ts +94 -0
- package/dist/mcp/echo-guard.js +160 -0
- package/dist/mcp/echo-guard.js.map +1 -0
- package/dist/mcp/event-catalog.d.ts +120 -0
- package/dist/mcp/event-catalog.js +306 -0
- package/dist/mcp/event-catalog.js.map +1 -0
- package/dist/mcp/event-controller.d.ts +230 -0
- package/dist/mcp/event-controller.js +596 -0
- package/dist/mcp/event-controller.js.map +1 -0
- package/dist/mcp/event-journal.d.ts +57 -0
- package/dist/mcp/event-journal.js +423 -0
- package/dist/mcp/event-journal.js.map +1 -0
- package/dist/mcp/index.d.ts +63 -0
- package/dist/mcp/index.js +386 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/ipc.d.ts +202 -0
- package/dist/mcp/ipc.js +206 -0
- package/dist/mcp/ipc.js.map +1 -0
- package/dist/mcp/leader-delivery.d.ts +119 -0
- package/dist/mcp/leader-delivery.js +450 -0
- package/dist/mcp/leader-delivery.js.map +1 -0
- package/dist/mcp/operation-receipts.d.ts +210 -0
- package/dist/mcp/operation-receipts.js +723 -0
- package/dist/mcp/operation-receipts.js.map +1 -0
- package/dist/mcp/project-catalogs.d.ts +23 -0
- package/dist/mcp/project-catalogs.js +25 -0
- package/dist/mcp/project-catalogs.js.map +1 -0
- package/dist/mcp/project-leaders.d.ts +26 -0
- package/dist/mcp/project-leaders.js +17 -0
- package/dist/mcp/project-leaders.js.map +1 -0
- package/dist/mcp/protocol.d.ts +50 -0
- package/dist/mcp/protocol.js +47 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/reconnect.d.ts +297 -0
- package/dist/mcp/reconnect.js +465 -0
- package/dist/mcp/reconnect.js.map +1 -0
- package/dist/mcp/resource-ownership.d.ts +228 -0
- package/dist/mcp/resource-ownership.js +390 -0
- package/dist/mcp/resource-ownership.js.map +1 -0
- package/dist/mcp/service-adapter.d.ts +16488 -0
- package/dist/mcp/service-adapter.js +148 -0
- package/dist/mcp/service-adapter.js.map +1 -0
- package/dist/mcp/service.d.ts +83 -0
- package/dist/mcp/service.js +233 -0
- package/dist/mcp/service.js.map +1 -0
- package/dist/mcp/session-binding.d.ts +105 -0
- package/dist/mcp/session-binding.js +163 -0
- package/dist/mcp/session-binding.js.map +1 -0
- package/dist/mcp/stale-write.d.ts +145 -0
- package/dist/mcp/stale-write.js +219 -0
- package/dist/mcp/stale-write.js.map +1 -0
- package/dist/mcp/tool.d.ts +48 -0
- package/dist/mcp/tool.js +24 -0
- package/dist/mcp/tool.js.map +1 -0
- package/dist/mcp/tools/discovery.d.ts +40 -0
- package/dist/mcp/tools/discovery.js +232 -0
- package/dist/mcp/tools/discovery.js.map +1 -0
- package/dist/mcp/tools/execution-control.d.ts +171 -0
- package/dist/mcp/tools/execution-control.js +513 -0
- package/dist/mcp/tools/execution-control.js.map +1 -0
- package/dist/mcp/tools/handoff-git.d.ts +26 -0
- package/dist/mcp/tools/handoff-git.js +544 -0
- package/dist/mcp/tools/handoff-git.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +2 -0
- package/dist/mcp/tools/index.js +23 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/leader-events.d.ts +46 -0
- package/dist/mcp/tools/leader-events.js +126 -0
- package/dist/mcp/tools/leader-events.js.map +1 -0
- package/dist/mcp/tools/local-handoff.d.ts +102 -0
- package/dist/mcp/tools/local-handoff.js +306 -0
- package/dist/mcp/tools/local-handoff.js.map +1 -0
- package/dist/mcp/tools/project-config.d.ts +340 -0
- package/dist/mcp/tools/project-config.js +1075 -0
- package/dist/mcp/tools/project-config.js.map +1 -0
- package/dist/mcp/tools/results-evidence.d.ts +112 -0
- package/dist/mcp/tools/results-evidence.js +1045 -0
- package/dist/mcp/tools/results-evidence.js.map +1 -0
- package/dist/mcp/tools/task-create.d.ts +70 -0
- package/dist/mcp/tools/task-create.js +473 -0
- package/dist/mcp/tools/task-create.js.map +1 -0
- package/dist/mcp/tools/task-reads.d.ts +102 -0
- package/dist/mcp/tools/task-reads.js +689 -0
- package/dist/mcp/tools/task-reads.js.map +1 -0
- package/dist/mcp/tools/work-organisation.d.ts +2 -0
- package/dist/mcp/tools/work-organisation.js +502 -0
- package/dist/mcp/tools/work-organisation.js.map +1 -0
- package/dist/paths.d.ts +17 -7
- package/dist/paths.js +18 -8
- package/dist/paths.js.map +1 -1
- package/dist/runs/project-writer.js +76 -11
- package/dist/runs/project-writer.js.map +1 -1
- package/dist/runs/retention.d.ts +24 -1
- package/dist/runs/retention.js +62 -1
- package/dist/runs/retention.js.map +1 -1
- package/dist/server/forge/github.js +44 -0
- package/dist/server/forge/github.js.map +1 -1
- package/dist/server/forge/types.d.ts +16 -0
- package/dist/server/project-context.d.ts +81 -8
- package/dist/server/project-context.js +143 -35
- package/dist/server/project-context.js.map +1 -1
- package/dist/server/server.d.ts +1050 -53
- package/dist/server/server.js +326 -120
- package/dist/server/server.js.map +1 -1
- package/dist/server/validators.d.ts +26 -6
- package/dist/server/validators.js +17 -0
- package/dist/server/validators.js.map +1 -1
- package/dist/skills-remote.js +64 -6
- package/dist/skills-remote.js.map +1 -1
- package/dist/workflows/run.d.ts +277 -12
- package/dist/workflows/run.js +554 -59
- package/dist/workflows/run.js.map +1 -1
- package/dist/workspace/agent-profiles.d.ts +30 -3
- package/dist/workspace/agent-profiles.js +39 -4
- package/dist/workspace/agent-profiles.js.map +1 -1
- package/dist/workspace/config.js +25 -4
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/project-owner.d.ts +210 -0
- package/dist/workspace/project-owner.js +518 -0
- package/dist/workspace/project-owner.js.map +1 -0
- package/package.json +5 -3
- package/scripts/pi-leader-extension.ts +420 -0
- package/scripts/sync-readme.mjs +83 -2
- package/web/dist/assets/{alert-dialog-BVsVcbFy.js → alert-dialog-D6rP6d39.js} +1 -1
- package/web/dist/assets/arrow-down-DMOfFIzW.js +1 -0
- package/web/dist/assets/arrow-left-C4Ec2BUg.js +1 -0
- package/web/dist/assets/{centered-state-Djgly6Ni.js → centered-state-B0ZqrPaa.js} +12 -12
- package/web/dist/assets/chevron-right-mHJ-crvn.js +1 -0
- package/web/dist/assets/{collapsible-DZ4D-DHG.js → collapsible-DchEHgUM.js} +1 -1
- package/web/dist/assets/{commit-list-B-z1JL9G.js → commit-list-BpMim1Rv.js} +1 -1
- package/web/dist/assets/{compare-variants-D62m4j3B.js → compare-variants-D5ItQbAq.js} +1 -1
- package/web/dist/assets/{diff-BKew9rQX.js → diff-HqqZ_WDR.js} +2 -2
- package/web/dist/assets/{diff-stat-D0mc5V6Z.js → diff-stat-NsU-3ecr.js} +1 -1
- package/web/dist/assets/{diff-view-xpIcuLIC.js → diff-view-B7c11Xux.js} +1 -1
- package/web/dist/assets/{dropdown-menu-CEQziH7X.js → dropdown-menu-CtTpnyod.js} +1 -1
- package/web/dist/assets/{editable-title-BCX3T2BY.js → editable-title-BXtuGQry.js} +1 -1
- package/web/dist/assets/{ellipsis-vertical-ByVdDc4z.js → ellipsis-vertical-MHtGZSH6.js} +1 -1
- package/web/dist/assets/{file-xBSB5hXO.js → file-DKLw5SOb.js} +1 -1
- package/web/dist/assets/{folder-CldgbZ5A.js → folder-Drw6-ufH.js} +1 -1
- package/web/dist/assets/{git-toolbar-DJANqg7Y.js → git-toolbar-BQH8bORV.js} +1 -1
- package/web/dist/assets/github-ynnpKFCJ.js +1 -0
- package/web/dist/assets/{image-preview-Cv5mZr0n.js → image-preview-DLhMN_R9.js} +1 -1
- package/web/dist/assets/index-BEsrNdXt.js +29 -0
- package/web/dist/assets/index-Da8DOBau.css +2 -0
- package/web/dist/assets/{markdown-CgEEE2ND.js → markdown-q365eN6Y.js} +1 -1
- package/web/dist/assets/{new-task-form-1r2bgsdD.js → new-task-form-CW-hNIWQ.js} +1 -1
- package/web/dist/assets/{pill-q-x22_ej.js → pill-B_TFulXa.js} +1 -1
- package/web/dist/assets/{project-router-VDN4jQM8.js → project-router-CLPc-ImZ.js} +1 -1
- package/web/dist/assets/{prompt-templates-BmZtCe-L.js → prompt-templates-CLc4RxyO.js} +1 -1
- package/web/dist/assets/{repo-git-btG0sdbO.js → repo-git-Is7R6-Tj.js} +1 -1
- package/web/dist/assets/{run-diff-vguEMJzh.js → run-diff-DHMvDlVo.js} +2 -2
- package/web/dist/assets/run-header-DvZJbQ8I.js +1 -0
- package/web/dist/assets/{search-x-QmhaAbg5.js → search-x-Ce2MMetu.js} +1 -1
- package/web/dist/assets/{skill-empty-hint-BrzUwByZ.js → skill-empty-hint-C_xCSvzB.js} +1 -1
- package/web/dist/assets/skills-CivqD4si.js +1 -0
- package/web/dist/assets/{sparkles-CP8QDOgX.js → sparkles-BCqPoj5g.js} +1 -1
- package/web/dist/assets/{square-terminal-CGypshSD.js → square-terminal-ikj5g-oK.js} +1 -1
- package/web/dist/assets/{tab-link-D4lFzzI1.js → tab-link-VsEjFV1Y.js} +1 -1
- package/web/dist/assets/{task-changes-BtZId14z.js → task-changes-CH-E81-_.js} +1 -1
- package/web/dist/assets/{task-commits-BoeyJXrv.js → task-commits-DqfkMFxp.js} +1 -1
- package/web/dist/assets/{task-files-DIy2oUnj.js → task-files-D_nkHZ_a.js} +2 -2
- package/web/dist/assets/task-thread-bxQompxJ.js +9 -0
- package/web/dist/assets/{textarea-CyBCyKIk.js → textarea-xajuW1L_.js} +1 -1
- package/web/dist/assets/{thread-loading-Cuq2jchC.js → thread-loading-C3JQZbIE.js} +1 -1
- package/web/dist/assets/{trash-2-B6Dp3ZNs.js → trash-2-hJrBhpUD.js} +1 -1
- package/web/dist/assets/{triangle-alert-xBya8FUp.js → triangle-alert-D1u82y1a.js} +1 -1
- package/web/dist/assets/{upload-D6MXEB9p.js → upload-Bn6FBZ59.js} +1 -1
- package/web/dist/assets/{use-desktop-CPBwzQ7M.js → use-desktop-DOIMfHfY.js} +1 -1
- package/web/dist/assets/{use-submit-shortcut-JTpNzU-u.js → use-submit-shortcut-Dj4DHTdE.js} +1 -1
- package/web/dist/assets/utils-YwBpOoRN.js +64 -0
- package/web/dist/assets/{workflows-DG115scU.js → workflows-CTaAKz2m.js} +3 -3
- package/web/dist/assets/{zoomable-image-GTYwLtkS.js → zoomable-image-C1-6P3tg.js} +1 -1
- package/web/dist/index.html +23 -23
- package/web/dist/assets/arrow-down-CYgAlZtW.js +0 -1
- package/web/dist/assets/arrow-left-BOqE0a3k.js +0 -1
- package/web/dist/assets/chevron-right-Cc9BWnbN.js +0 -1
- package/web/dist/assets/github-D6vK0jFn.js +0 -1
- package/web/dist/assets/index-CkmR6h7s.css +0 -2
- package/web/dist/assets/index-DVDqF4bM.js +0 -9
- package/web/dist/assets/refresh-cw-DcNCj2nu.js +0 -1
- package/web/dist/assets/run-header-DM7MiKj9.js +0 -1
- package/web/dist/assets/skills-DMfxQtrD.js +0 -1
- package/web/dist/assets/task-thread-CbMPmrWq.js +0 -9
- package/web/dist/assets/utils--z9hRtl3.js +0 -64
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xezar's pi leader extension (#330 WP2).
|
|
3
|
+
*
|
|
4
|
+
* WHAT IT IS FOR. xezar's event controller can push a significant event to a leader running in
|
|
5
|
+
* OpenCode, because `opencode serve` has an address. pi has none: its RPC is stdio-only and
|
|
6
|
+
* spawn-only — no port, no socket, no attach mode (`pi --help` and `docs/rpc.md` on 0.85.1) — and
|
|
7
|
+
* xezar never starts an agent process for a leader. So a pi that YOU started in YOUR terminal could
|
|
8
|
+
* not be reached at all, and its events waited until you typed something. That is what this closes.
|
|
9
|
+
*
|
|
10
|
+
* It runs inside your pi, opens one Unix socket, and writes a small descriptor into the project's
|
|
11
|
+
* own xezar directory saying where that socket is. xezar reads the descriptor when you attach, dials
|
|
12
|
+
* the socket, and from then on speaks pi's OWN RPC vocabulary down it — `prompt`, `steer`,
|
|
13
|
+
* `get_state`, `get_messages` — which this file translates into the pi extension API. xezar's side
|
|
14
|
+
* therefore does not know or care that a socket is involved (`src/mcp/adapters/pi-link.ts`).
|
|
15
|
+
*
|
|
16
|
+
* WHAT IT DOES NOT DO, deliberately:
|
|
17
|
+
* - It sends nothing to xezar on its own. It answers commands and forwards pi's events. It never
|
|
18
|
+
* reads your files, your settings or your credentials, and it opens no network socket. The
|
|
19
|
+
* Unix socket lives inside a directory this extension creates with mode 0700, which is what
|
|
20
|
+
* keeps other accounts on the machine out — see `makePrivateSocketDir` for why the DIRECTORY
|
|
21
|
+
* and not the socket file is the guard.
|
|
22
|
+
* - It starts no turn of its own accord. A turn happens only when xezar hands over an event, which
|
|
23
|
+
* only happens for a project you attached, in a session you started.
|
|
24
|
+
* - It never edits, retries or invents. A command it does not understand is refused by name.
|
|
25
|
+
*
|
|
26
|
+
* INSTALL. Either point pi at it once:
|
|
27
|
+
* pi --extension /path/to/xezar/scripts/pi-leader-extension.ts
|
|
28
|
+
* or copy it into `~/.pi/agent/extensions/` (global) or `<project>/.pi/extensions/` (project-local),
|
|
29
|
+
* which also makes `/reload` work on it. Then, in the cockpit, attach the pi leader.
|
|
30
|
+
*
|
|
31
|
+
* WHY THE SOCKET IS OPENED IN `session_start` AND NOT IN THE FACTORY. pi documents it plainly
|
|
32
|
+
* (`docs/extensions.md` § Long-lived resources and shutdown): a factory may run in an invocation
|
|
33
|
+
* that never starts a session, so background resources belong to `session_start`, with an IDEMPOTENT
|
|
34
|
+
* `session_shutdown` to close them. pi tears the runtime down and rebuilds it on `/new`, `/resume`,
|
|
35
|
+
* `/fork`, `/clone` and `/reload` — not only on quit — so the socket path carries the session id and
|
|
36
|
+
* the teardown is safe to run twice.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { createServer, type Server, type Socket } from 'node:net';
|
|
40
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
41
|
+
import { dirname, join, resolve } from 'node:path';
|
|
42
|
+
import { tmpdir } from 'node:os';
|
|
43
|
+
|
|
44
|
+
// The extension API types are pi's, resolved from the pi installation that loads this file. They are
|
|
45
|
+
// imported as types only, so this file needs no dependency of its own and no `package.json`.
|
|
46
|
+
type TextContent = { type: 'text'; text: string };
|
|
47
|
+
type AgentMessage = { role?: unknown; content?: unknown };
|
|
48
|
+
type SessionEntry = { type?: unknown; message?: AgentMessage };
|
|
49
|
+
|
|
50
|
+
interface ReadonlySessionManagerLike {
|
|
51
|
+
getSessionId(): string;
|
|
52
|
+
getCwd(): string;
|
|
53
|
+
getBranch(fromId?: string): SessionEntry[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface ExtensionContextLike {
|
|
57
|
+
cwd: string;
|
|
58
|
+
sessionManager: ReadonlySessionManagerLike;
|
|
59
|
+
isIdle(): boolean;
|
|
60
|
+
hasPendingMessages(): boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface ExtensionApiLike {
|
|
64
|
+
on(event: string, handler: (event: unknown, ctx: ExtensionContextLike) => unknown): void;
|
|
65
|
+
sendUserMessage(content: string | TextContent[], options?: { deliverAs?: 'steer' | 'followUp' }): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Additive changes keep version 1; an incompatible one bumps it. Must match `pi-link.ts`. */
|
|
69
|
+
const SCHEMA_VERSION = 1;
|
|
70
|
+
const DESCRIPTOR_FILE = 'pi-leader.json';
|
|
71
|
+
const XEZAR_DATA_DIR = join('.local', 'xezar');
|
|
72
|
+
/** A Unix socket path is limited to ~104 bytes on macOS, so it never lives under the repository. */
|
|
73
|
+
const SOCKET_PREFIX = 'xez-pi-';
|
|
74
|
+
const SOCKET_FILE = 'leader.sock';
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A private `0700` directory to put the socket in, or `undefined` if we cannot have one.
|
|
78
|
+
*
|
|
79
|
+
* THE DIRECTORY IS THE GUARD, AND IT HAS TO BE. An earlier version of this file put the socket
|
|
80
|
+
* straight into `os.tmpdir()` and claimed in a comment that only this user could reach it. That was
|
|
81
|
+
* false, and a QA on #358 measured it: with no `TMPDIR` set, Linux's `os.tmpdir()` is `/tmp`, mode
|
|
82
|
+
* `1777`, and the socket landed there as `0755`. Any other local account could then read the whole
|
|
83
|
+
* leader conversation with `get_messages` and write `prompt`/`steer` into a pi that has bash and
|
|
84
|
+
* edit tools. macOS hid it, because it gives each user a `0700` `/var/folders/…/T` — so the bug was
|
|
85
|
+
* invisible on the machine this was written on and live on the ordinary Linux box.
|
|
86
|
+
*
|
|
87
|
+
* A mode on the socket FILE is not the fix: Linux enforces permissions on a Unix socket, macOS and
|
|
88
|
+
* the BSDs do not, so only the containing directory is portable. `/tmp` being sticky (`1777`) is
|
|
89
|
+
* what makes creating our own directory there safe — another user cannot remove or rename it.
|
|
90
|
+
*
|
|
91
|
+
* Three things are therefore deliberate:
|
|
92
|
+
* - the directory is created NON-recursively, so an existing path is an error we handle rather
|
|
93
|
+
* than something we silently adopt;
|
|
94
|
+
* - anything already at the path is removed first, so a leftover from a crashed pi (or a planted
|
|
95
|
+
* symlink) cannot be inherited;
|
|
96
|
+
* - the result is VERIFIED with `lstat` before it is used — the mode is checked, and a symlink is
|
|
97
|
+
* refused, because `mkdir -p` over a symlink-to-directory succeeds and `chmod` would follow it.
|
|
98
|
+
*/
|
|
99
|
+
function makePrivateSocketDir(sessionId: string): { dir: string; socket: string } | undefined {
|
|
100
|
+
const dir = join(tmpdir(), `${SOCKET_PREFIX}${sessionId}`);
|
|
101
|
+
try {
|
|
102
|
+
rmSync(dir, { recursive: true, force: true });
|
|
103
|
+
mkdirSync(dir, { mode: 0o700 });
|
|
104
|
+
// mkdir's mode is masked by the process umask, so ask for it again explicitly.
|
|
105
|
+
chmodSync(dir, 0o700);
|
|
106
|
+
const stat = lstatSync(dir);
|
|
107
|
+
if (!stat.isDirectory()) return undefined;
|
|
108
|
+
if ((stat.mode & 0o077) !== 0) return undefined;
|
|
109
|
+
return { dir, socket: join(dir, SOCKET_FILE) };
|
|
110
|
+
} catch {
|
|
111
|
+
// No private directory, no socket. Failing closed here costs push delivery and nothing else:
|
|
112
|
+
// the events stay in xezar's journal and the leader still reads them with `leader_events`.
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export default function xezarLeaderExtension(pi: ExtensionApiLike): void {
|
|
118
|
+
let server: Server | undefined;
|
|
119
|
+
let descriptorPath: string | undefined;
|
|
120
|
+
let socketPath: string | undefined;
|
|
121
|
+
let socketDir: string | undefined;
|
|
122
|
+
const clients = new Set<Socket>();
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* pi's `on` has no unsubscribe, so these are registered ONCE for the whole life of the extension
|
|
126
|
+
* and broadcast to whatever sockets are open at the time. Registering them per connection would
|
|
127
|
+
* add four handlers to the person's pi on every attach and never take them away — a slow leak
|
|
128
|
+
* inside somebody else's editor, which is not a thing to ship.
|
|
129
|
+
*
|
|
130
|
+
* Only these four. The adapter needs the busy boundary and the user message that marks a real
|
|
131
|
+
* reaction; forwarding more would put session content on a socket for no reason.
|
|
132
|
+
*/
|
|
133
|
+
for (const type of ['agent_start', 'agent_settled', 'message_start', 'message_end'] as const) {
|
|
134
|
+
pi.on(type, (event: unknown) => {
|
|
135
|
+
if (clients.size === 0) return;
|
|
136
|
+
const message = (event as { message?: AgentMessage } | undefined)?.message;
|
|
137
|
+
const frame = `${JSON.stringify(message === undefined ? { type } : { type, message })}\n`;
|
|
138
|
+
for (const client of clients) {
|
|
139
|
+
if (client.destroyed) continue;
|
|
140
|
+
try {
|
|
141
|
+
client.write(frame);
|
|
142
|
+
} catch {
|
|
143
|
+
/* A peer that went away is not worth surfacing into someone's coding session. */
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Idempotent, and it must stay that way: pi calls it on quit AND on every session replacement. */
|
|
150
|
+
const teardown = (): void => {
|
|
151
|
+
for (const client of clients) client.destroy();
|
|
152
|
+
clients.clear();
|
|
153
|
+
server?.close();
|
|
154
|
+
server = undefined;
|
|
155
|
+
if (descriptorPath !== undefined) {
|
|
156
|
+
try {
|
|
157
|
+
rmSync(descriptorPath, { force: true });
|
|
158
|
+
} catch {
|
|
159
|
+
/* Leaving a stale file behind is not worth failing a shutdown over; xezar detects it. */
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// The whole private directory, not just the socket inside it.
|
|
163
|
+
if (socketDir !== undefined) {
|
|
164
|
+
try {
|
|
165
|
+
rmSync(socketDir, { recursive: true, force: true });
|
|
166
|
+
} catch {
|
|
167
|
+
/* Same: a leftover empty 0700 directory harms nothing. */
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
descriptorPath = undefined;
|
|
171
|
+
socketPath = undefined;
|
|
172
|
+
socketDir = undefined;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
pi.on('session_start', async (_event: unknown, ctx: ExtensionContextLike) => {
|
|
176
|
+
// A replacement session fires `session_shutdown` first, but run teardown anyway: it is cheap,
|
|
177
|
+
// it is idempotent, and a half-open server here would fail the bind below.
|
|
178
|
+
teardown();
|
|
179
|
+
|
|
180
|
+
const dataDir = findProjectDataDir(ctx.cwd);
|
|
181
|
+
if (dataDir === undefined) return; // Not a xezar project. Do nothing at all, quietly.
|
|
182
|
+
|
|
183
|
+
const sessionId = safeSessionId(ctx);
|
|
184
|
+
const place = makePrivateSocketDir(sessionId);
|
|
185
|
+
if (place === undefined) return; // Could not get a private directory: open nothing at all.
|
|
186
|
+
socketDir = place.dir;
|
|
187
|
+
socketPath = place.socket;
|
|
188
|
+
|
|
189
|
+
const next = createServer((socket) => serve(socket, pi, ctx, clients));
|
|
190
|
+
next.on('error', () => {
|
|
191
|
+
// A leader that cannot listen must not take pi down with it. xezar keeps the rows and the
|
|
192
|
+
// person reads them with `leader_events`, exactly as before this extension existed.
|
|
193
|
+
teardown();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await new Promise<void>((done) => {
|
|
197
|
+
next.once('listening', () => done());
|
|
198
|
+
next.once('error', () => done());
|
|
199
|
+
next.listen(socketPath);
|
|
200
|
+
});
|
|
201
|
+
if (!next.listening) {
|
|
202
|
+
teardown();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
server = next;
|
|
206
|
+
|
|
207
|
+
// Written only once the socket really listens, so the file never names nothing — the same
|
|
208
|
+
// ordering rule xezar's own connection file follows.
|
|
209
|
+
descriptorPath = join(dataDir, DESCRIPTOR_FILE);
|
|
210
|
+
writeDescriptor(descriptorPath, { socket: socketPath, sessionId });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
pi.on('session_shutdown', () => teardown());
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** One connection, JSONL both ways: xezar's commands in, pi's answers and events out. */
|
|
217
|
+
function serve(socket: Socket, pi: ExtensionApiLike, ctx: ExtensionContextLike, clients: Set<Socket>): void {
|
|
218
|
+
clients.add(socket);
|
|
219
|
+
socket.setNoDelay(true);
|
|
220
|
+
socket.setEncoding('utf8');
|
|
221
|
+
|
|
222
|
+
const write = (frame: Record<string, unknown>): void => {
|
|
223
|
+
if (socket.destroyed) return;
|
|
224
|
+
try {
|
|
225
|
+
socket.write(`${JSON.stringify(frame)}\n`);
|
|
226
|
+
} catch {
|
|
227
|
+
/* A peer that went away is not an error worth surfacing into someone's coding session. */
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// pi's events reach this socket through the one set of handlers registered in the factory, which
|
|
232
|
+
// broadcasts to `clients`. Nothing is subscribed per connection, on purpose — see there.
|
|
233
|
+
let buffer = '';
|
|
234
|
+
socket.on('data', (chunk: string) => {
|
|
235
|
+
buffer += chunk;
|
|
236
|
+
// A peer that never sends a newline must not grow this without bound.
|
|
237
|
+
if (buffer.length > 1_000_000) {
|
|
238
|
+
socket.destroy();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
let nl: number;
|
|
242
|
+
// Split on `\n` only and strip a trailing `\r` — pi's own framing rule (`docs/rpc.md`).
|
|
243
|
+
while ((nl = buffer.indexOf('\n')) >= 0) {
|
|
244
|
+
const line = buffer.slice(0, nl).replace(/\r$/, '');
|
|
245
|
+
buffer = buffer.slice(nl + 1);
|
|
246
|
+
if (!line.trim()) continue;
|
|
247
|
+
let command: Record<string, unknown>;
|
|
248
|
+
try {
|
|
249
|
+
command = JSON.parse(line) as Record<string, unknown>;
|
|
250
|
+
} catch {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
handle(command, pi, ctx, write);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const done = (): void => {
|
|
258
|
+
clients.delete(socket);
|
|
259
|
+
};
|
|
260
|
+
socket.on('close', done);
|
|
261
|
+
socket.on('error', done);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* One command. The vocabulary is pi's RPC, so xezar's adapter is unchanged by this file existing.
|
|
266
|
+
*
|
|
267
|
+
* `success` means ACCEPTED, never "the turn went well" — pi's own rule, and the one xezar's adapter
|
|
268
|
+
* is written against.
|
|
269
|
+
*/
|
|
270
|
+
function handle(
|
|
271
|
+
command: Record<string, unknown>,
|
|
272
|
+
pi: ExtensionApiLike,
|
|
273
|
+
ctx: ExtensionContextLike,
|
|
274
|
+
write: (frame: Record<string, unknown>) => void,
|
|
275
|
+
): void {
|
|
276
|
+
const id = typeof command.id === 'string' ? command.id : undefined;
|
|
277
|
+
const type = typeof command.type === 'string' ? command.type : '';
|
|
278
|
+
const reply = (frame: Record<string, unknown>): void => write({ type: 'response', ...(id === undefined ? {} : { id }), command: type, ...frame });
|
|
279
|
+
const text = typeof command.message === 'string' ? command.message : '';
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
if (type === 'get_state') {
|
|
283
|
+
reply({
|
|
284
|
+
success: true,
|
|
285
|
+
data: {
|
|
286
|
+
isStreaming: !ctx.isIdle(),
|
|
287
|
+
// The extension API answers "are any queued" as a boolean, not a count (pi has no
|
|
288
|
+
// `pendingMessageCount` for extensions). A truthful lower bound: xezar only ever asks
|
|
289
|
+
// whether it is above zero, and this never claims a number it does not know.
|
|
290
|
+
pendingMessageCount: ctx.hasPendingMessages() ? 1 : 0,
|
|
291
|
+
sessionId: safeSessionId(ctx),
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (type === 'get_messages') {
|
|
297
|
+
reply({ success: true, data: { messages: conversation(ctx) } });
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (type === 'prompt') {
|
|
301
|
+
// Real pi refuses a plain `prompt` during a turn and says so in this exact wording; xezar's
|
|
302
|
+
// adapter matches on it and falls back to `steer`. Answering the same way keeps the two
|
|
303
|
+
// transports interchangeable. `sendUserMessage` cannot report this itself — while streaming
|
|
304
|
+
// it fails into pi's extension-error channel rather than throwing here — so it is checked.
|
|
305
|
+
if (!ctx.isIdle()) {
|
|
306
|
+
reply({
|
|
307
|
+
success: false,
|
|
308
|
+
error: { message: "Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message." },
|
|
309
|
+
});
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
pi.sendUserMessage(text);
|
|
313
|
+
reply({ success: true });
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (type === 'steer') {
|
|
317
|
+
pi.sendUserMessage(text, { deliverAs: 'steer' });
|
|
318
|
+
reply({ success: true });
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
reply({ success: false, error: { message: `the xezar leader extension does not implement ${type || '(no type)'}` } });
|
|
322
|
+
} catch (err) {
|
|
323
|
+
reply({ success: false, error: { message: err instanceof Error ? err.message : String(err) } });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* The conversation as it stands, oldest first, in `get_messages` shape.
|
|
329
|
+
*
|
|
330
|
+
* `getBranch()` and not `getEntries()`: entries are the raw append-only file, so after a `/fork` it
|
|
331
|
+
* still carries messages from branches the person walked away from. Not `buildContextEntries()`
|
|
332
|
+
* either: that drops pre-compaction messages, and xezar reads this to find out what it has ALREADY
|
|
333
|
+
* told pi — a dropped marker would make it hand the same event over twice.
|
|
334
|
+
*/
|
|
335
|
+
function conversation(ctx: ExtensionContextLike): { role: string; content: TextContent[] }[] {
|
|
336
|
+
let entries: SessionEntry[];
|
|
337
|
+
try {
|
|
338
|
+
entries = ctx.sessionManager.getBranch();
|
|
339
|
+
} catch {
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
const out: { role: string; content: TextContent[] }[] = [];
|
|
343
|
+
for (const entry of entries) {
|
|
344
|
+
if (entry?.type !== 'message') continue;
|
|
345
|
+
const message = entry.message;
|
|
346
|
+
const role = typeof message?.role === 'string' ? message.role : undefined;
|
|
347
|
+
if (role === undefined) continue;
|
|
348
|
+
out.push({ role, content: textOf(message?.content) });
|
|
349
|
+
}
|
|
350
|
+
return out;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** `UserMessage.content` is `string | (TextContent | ImageContent)[]`. Both are real; handle both. */
|
|
354
|
+
function textOf(content: unknown): TextContent[] {
|
|
355
|
+
if (typeof content === 'string') return [{ type: 'text', text: content }];
|
|
356
|
+
if (!Array.isArray(content)) return [];
|
|
357
|
+
const parts: TextContent[] = [];
|
|
358
|
+
for (const part of content) {
|
|
359
|
+
const text = (part as { type?: unknown; text?: unknown } | null)?.text;
|
|
360
|
+
if ((part as { type?: unknown } | null)?.type === 'text' && typeof text === 'string') parts.push({ type: 'text', text });
|
|
361
|
+
}
|
|
362
|
+
return parts;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function safeSessionId(ctx: ExtensionContextLike): string {
|
|
366
|
+
let id: string;
|
|
367
|
+
try {
|
|
368
|
+
id = ctx.sessionManager.getSessionId();
|
|
369
|
+
} catch {
|
|
370
|
+
return `pid-${process.pid}`;
|
|
371
|
+
}
|
|
372
|
+
// Never let a session id become a path of its own.
|
|
373
|
+
const clean = id.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 40);
|
|
374
|
+
return clean.length > 0 ? clean : `pid-${process.pid}`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Walk up from pi's working directory for a xezar project's `.local/xezar`. Discovery, not
|
|
379
|
+
* configuration: the person tells this extension nothing, and a directory that is not a xezar
|
|
380
|
+
* project simply gets no leader socket.
|
|
381
|
+
*/
|
|
382
|
+
function findProjectDataDir(cwd: string): string | undefined {
|
|
383
|
+
let dir = resolve(cwd);
|
|
384
|
+
for (let depth = 0; depth < 40; depth += 1) {
|
|
385
|
+
const candidate = join(dir, XEZAR_DATA_DIR);
|
|
386
|
+
if (existsSync(candidate)) return candidate;
|
|
387
|
+
const parent = dirname(dir);
|
|
388
|
+
if (parent === dir) return undefined;
|
|
389
|
+
dir = parent;
|
|
390
|
+
}
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Atomic (tmp + rename) at 0600, like every other descriptor xezar writes. */
|
|
395
|
+
function writeDescriptor(path: string, input: { socket: string; sessionId: string }): void {
|
|
396
|
+
const descriptor = {
|
|
397
|
+
schemaVersion: SCHEMA_VERSION,
|
|
398
|
+
session: { pid: process.pid, startedAt: new Date().toISOString(), sessionId: input.sessionId },
|
|
399
|
+
endpoint: { socket: input.socket },
|
|
400
|
+
};
|
|
401
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
402
|
+
try {
|
|
403
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
404
|
+
writeFileSync(tmp, `${JSON.stringify(descriptor, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
405
|
+
renameSync(tmp, path);
|
|
406
|
+
} catch {
|
|
407
|
+
try {
|
|
408
|
+
rmSync(tmp, { force: true });
|
|
409
|
+
} catch {
|
|
410
|
+
/* Nothing further to do: without a descriptor xezar simply reports no pi leader. */
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Exported for xezar's own tests; pi only ever uses the default export. */
|
|
416
|
+
export const __internals = { conversation, textOf, handle, findProjectDataDir, writeDescriptor, readDescriptor, makePrivateSocketDir, safeSessionId, serve };
|
|
417
|
+
|
|
418
|
+
function readDescriptor(path: string): unknown {
|
|
419
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
420
|
+
}
|
package/scripts/sync-readme.mjs
CHANGED
|
@@ -7,14 +7,95 @@
|
|
|
7
7
|
// package, so the one source is copied in rather than referenced out — the copy is gitignored,
|
|
8
8
|
// regenerated by every build, and never edited by hand.
|
|
9
9
|
//
|
|
10
|
+
// The copy is not byte-for-byte: every relative link and image source is rewritten to an
|
|
11
|
+
// absolute GitHub URL (#287). The root README's `docs/...` and `LICENSE` links are right on
|
|
12
|
+
// GitHub, but the published package contains neither, so on npmjs.com every screenshot and
|
|
13
|
+
// every guide link was broken. Links inside code (fenced blocks, inline code spans) are left
|
|
14
|
+
// alone, because there they are examples, not links.
|
|
15
|
+
//
|
|
10
16
|
// Runs as `prebuild`, so `npm run build` (and the release pipeline, which builds first) always
|
|
11
17
|
// packs a current copy. `check:pack` is the gate that notices if this ever stops happening.
|
|
12
18
|
|
|
13
|
-
import {
|
|
19
|
+
import { readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
14
20
|
import path from 'node:path';
|
|
15
21
|
import { fileURLToPath } from 'node:url';
|
|
16
22
|
|
|
17
23
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
24
|
const repoRoot = path.resolve(packageRoot, '../..');
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
/** The branch the absolute links point at: the docs a reader follows are the current ones. */
|
|
27
|
+
const REF = 'main';
|
|
28
|
+
const IMAGE = /\.(png|jpe?g|gif|svg|webp|avif)$/i;
|
|
29
|
+
|
|
30
|
+
/** `git+https://github.com/owner/name.git` (the manifest's `repository.url`) → `https://github.com/owner/name`. */
|
|
31
|
+
export function githubRepoUrl(repositoryUrl) {
|
|
32
|
+
const match = /github\.com[/:]([^/]+)\/([^/#?]+?)(?:\.git)?\/?$/.exec(String(repositoryUrl ?? ''));
|
|
33
|
+
if (!match) throw new Error(`sync-readme: repository.url is not a GitHub repository: ${repositoryUrl}`);
|
|
34
|
+
return `https://github.com/${match[1]}/${match[2]}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isRelative(target) {
|
|
38
|
+
return !(target.startsWith('#') || target.startsWith('//') || /^[a-z][a-z0-9+.-]*:/i.test(target));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function absolute(target, repoUrl) {
|
|
42
|
+
if (!isRelative(target)) return target;
|
|
43
|
+
const hash = target.indexOf('#');
|
|
44
|
+
const file = path.posix.normalize(hash === -1 ? target : target.slice(0, hash)).replace(/^(\.\/|\/)+/, '');
|
|
45
|
+
const fragment = hash === -1 ? '' : target.slice(hash);
|
|
46
|
+
if (file === '.' || file.startsWith('../')) return target; // outside the repository: leave it for the test to flag
|
|
47
|
+
if (IMAGE.test(file)) return `${repoUrl.replace('https://github.com/', 'https://raw.githubusercontent.com/')}/${REF}/${file}${fragment}`;
|
|
48
|
+
return `${repoUrl}/blob/${REF}/${file}${fragment}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function rewriteProse(text, repoUrl) {
|
|
52
|
+
return text
|
|
53
|
+
.replace(/(\]\()(\s*)([^)\s]+)/g, (_, open, space, target) => `${open}${space}${absolute(target, repoUrl)}`)
|
|
54
|
+
.replace(/(\s(?:src|href)=")([^"]+)(")/gi, (_, open, target, close) => `${open}${absolute(target, repoUrl)}${close}`)
|
|
55
|
+
.replace(/^( {0,3}\[(?!\^)[^\]]+\]:[ \t]*)(\S+)/gm, (_, open, target) => `${open}${absolute(target, repoUrl)}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The README as npm should show it: every relative Markdown link, image, reference definition and
|
|
60
|
+
* HTML `src`/`href` made absolute against `repoUrl`, and everything inside code left untouched.
|
|
61
|
+
*/
|
|
62
|
+
export function absolutizeReadmeLinks(markdown, repoUrl) {
|
|
63
|
+
let fence = null;
|
|
64
|
+
return markdown
|
|
65
|
+
.split('\n')
|
|
66
|
+
.map((line) => {
|
|
67
|
+
const marker = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
|
|
68
|
+
if (fence) {
|
|
69
|
+
if (marker && marker[0] === fence[0] && marker.length >= fence.length) fence = null;
|
|
70
|
+
return line;
|
|
71
|
+
}
|
|
72
|
+
if (marker) {
|
|
73
|
+
fence = marker;
|
|
74
|
+
return line;
|
|
75
|
+
}
|
|
76
|
+
// Odd pieces of the split are inline code spans.
|
|
77
|
+
return line
|
|
78
|
+
.split(/(`+[^`]*`+)/)
|
|
79
|
+
.map((piece, i) => (i % 2 === 1 ? piece : rewriteProse(piece, repoUrl)))
|
|
80
|
+
.join('');
|
|
81
|
+
})
|
|
82
|
+
.join('\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function main() {
|
|
86
|
+
const manifest = JSON.parse(readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
87
|
+
const readme = readFileSync(path.join(repoRoot, 'README.md'), 'utf8');
|
|
88
|
+
writeFileSync(path.join(packageRoot, 'README.md'), absolutizeReadmeLinks(readme, githubRepoUrl(manifest.repository?.url)));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Imported by its test for the pure functions above; only a direct run writes the copy. Real paths
|
|
92
|
+
// on both sides, because the script may be reached through a symlinked directory.
|
|
93
|
+
function isDirectRun() {
|
|
94
|
+
try {
|
|
95
|
+
return Boolean(process.argv[1]) && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (isDirectRun()) main();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-QTnfLwEv.js";import{i as t,t as n}from"./react-runtime-CCIEwYL0.js";import{D as r,_ as i,b as a,c as o,d as s,f as c,g as l,h as u,l as d,o as f,p as ee,s as te,u as ne,w as re,x as p,y as m}from"./centered-state-
|
|
1
|
+
import{r as e}from"./rolldown-runtime-QTnfLwEv.js";import{i as t,t as n}from"./react-runtime-CCIEwYL0.js";import{D as r,_ as i,b as a,c as o,d as s,f as c,g as l,h as u,l as d,o as f,p as ee,s as te,u as ne,w as re,x as p,y as m}from"./centered-state-B0ZqrPaa.js";import{t as h}from"./utils-YwBpOoRN.js";var g=e(t(),1),_=n(),v=`Dialog`,[y,b]=a(v),[ie,x]=y(v),S=e=>{let{__scopeDialog:t,children:n,open:r,defaultOpen:a,onOpenChange:o,modal:s=!0}=e,c=g.useRef(null),l=g.useRef(null),[d,f]=i({prop:r,defaultProp:a??!1,onChange:o,caller:v});return(0,_.jsx)(ie,{scope:t,triggerRef:c,contentRef:l,contentId:u(),titleId:u(),descriptionId:u(),open:d,onOpenChange:f,onOpenToggle:g.useCallback(()=>f(e=>!e),[f]),modal:s,children:n})};S.displayName=v;var C=`DialogTrigger`,w=g.forwardRef((e,t)=>{let{__scopeDialog:n,...i}=e,a=x(C,n),o=r(t,a.triggerRef);return(0,_.jsx)(p.button,{type:`button`,"aria-haspopup":`dialog`,"aria-expanded":a.open,"aria-controls":a.open?a.contentId:void 0,"data-state":B(a.open),...i,ref:o,onClick:m(e.onClick,a.onOpenToggle)})});w.displayName=C;var T=`DialogPortal`,[E,D]=y(T,{forceMount:void 0}),O=e=>{let{__scopeDialog:t,forceMount:n,children:r,container:i}=e,a=x(T,t);return(0,_.jsx)(E,{scope:t,forceMount:n,children:g.Children.map(r,e=>(0,_.jsx)(l,{present:n||a.open,children:(0,_.jsx)(ne,{asChild:!0,container:i,children:e})}))})};O.displayName=T;var k=`DialogOverlay`,A=g.forwardRef((e,t)=>{let n=D(k,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=x(k,e.__scopeDialog);return a.modal?(0,_.jsx)(l,{present:r||a.open,children:(0,_.jsx)(oe,{...i,ref:t})}):null});A.displayName=k;var ae=re(`DialogOverlay.RemoveScroll`),oe=g.forwardRef((e,t)=>{let{__scopeDialog:n,...i}=e,a=x(k,n),s=r(t,ee());return(0,_.jsx)(o,{as:ae,allowPinchZoom:!0,shards:[a.contentRef],children:(0,_.jsx)(p.div,{"data-state":B(a.open),...i,ref:s,style:{pointerEvents:`auto`,...i.style}})})}),j=`DialogContent`,M=g.forwardRef((e,t)=>{let n=D(j,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=x(j,e.__scopeDialog);return(0,_.jsx)(l,{present:r||a.open,children:a.modal?(0,_.jsx)(se,{...i,ref:t}):(0,_.jsx)(ce,{...i,ref:t})})});M.displayName=j;var se=g.forwardRef((e,t)=>{let n=x(j,e.__scopeDialog),i=g.useRef(null),a=r(t,n.contentRef,i);return g.useEffect(()=>{let e=i.current;if(e)return te(e)},[]),(0,_.jsx)(N,{...e,ref:a,trapFocus:n.open,disableOutsidePointerEvents:n.open,onCloseAutoFocus:m(e.onCloseAutoFocus,e=>{e.preventDefault(),n.triggerRef.current?.focus()}),onPointerDownOutside:m(e.onPointerDownOutside,e=>{let t=e.detail.originalEvent,n=t.button===0&&t.ctrlKey===!0;(t.button===2||n)&&e.preventDefault()}),onFocusOutside:m(e.onFocusOutside,e=>e.preventDefault())})}),ce=g.forwardRef((e,t)=>{let n=x(j,e.__scopeDialog),r=g.useRef(!1),i=g.useRef(!1);return(0,_.jsx)(N,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:t=>{e.onCloseAutoFocus?.(t),t.defaultPrevented||(r.current||n.triggerRef.current?.focus(),t.preventDefault()),r.current=!1,i.current=!1},onInteractOutside:t=>{e.onInteractOutside?.(t),t.defaultPrevented||(r.current=!0,t.detail.originalEvent.type===`pointerdown`&&(i.current=!0));let a=t.target;n.triggerRef.current?.contains(a)&&t.preventDefault(),t.detail.originalEvent.type===`focusin`&&i.current&&t.preventDefault()}})}),N=g.forwardRef((e,t)=>{let{__scopeDialog:n,trapFocus:r,onOpenAutoFocus:i,onCloseAutoFocus:a,...o}=e,l=x(j,n);return d(),(0,_.jsx)(_.Fragment,{children:(0,_.jsx)(s,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:a,children:(0,_.jsx)(c,{role:`dialog`,id:l.contentId,"aria-describedby":l.descriptionId,"aria-labelledby":l.titleId,"data-state":B(l.open),...o,ref:t,deferPointerDownOutside:!0,onDismiss:()=>l.onOpenChange(!1)})})})}),P=`DialogTitle`,F=g.forwardRef((e,t)=>{let{__scopeDialog:n,...r}=e,i=x(P,n);return(0,_.jsx)(p.h2,{id:i.titleId,...r,ref:t})});F.displayName=P;var I=`DialogDescription`,L=g.forwardRef((e,t)=>{let{__scopeDialog:n,...r}=e,i=x(I,n);return(0,_.jsx)(p.p,{id:i.descriptionId,...r,ref:t})});L.displayName=I;var R=`DialogClose`,z=g.forwardRef((e,t)=>{let{__scopeDialog:n,...r}=e,i=x(R,n);return(0,_.jsx)(p.button,{type:`button`,...r,ref:t,onClick:m(e.onClick,()=>i.onOpenChange(!1))})});z.displayName=R;function B(e){return e?`open`:`closed`}var V=`AlertDialog`,[le,ue]=a(V,[b]),H=b(),U=e=>{let{__scopeAlertDialog:t,...n}=e,r=H(t);return(0,_.jsx)(S,{...r,...n,modal:!0})};U.displayName=V;var de=`AlertDialogTrigger`,fe=g.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=H(n);return(0,_.jsx)(w,{...i,...r,ref:t})});fe.displayName=de;var pe=`AlertDialogPortal`,W=e=>{let{__scopeAlertDialog:t,...n}=e,r=H(t);return(0,_.jsx)(O,{...r,...n})};W.displayName=pe;var me=`AlertDialogOverlay`,G=g.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=H(n);return(0,_.jsx)(A,{...i,...r,ref:t})});G.displayName=me;var K=`AlertDialogContent`,[he,ge]=le(K),q=g.forwardRef((e,t)=>{let{__scopeAlertDialog:n,children:i,...a}=e,o=H(n),s=r(t,g.useRef(null)),c=g.useRef(null);return(0,_.jsx)(he,{scope:n,cancelRef:c,children:(0,_.jsx)(M,{role:`alertdialog`,...o,...a,ref:s,onOpenAutoFocus:m(a.onOpenAutoFocus,e=>{e.preventDefault(),c.current?.focus({preventScroll:!0})}),onPointerDownOutside:e=>e.preventDefault(),onInteractOutside:e=>e.preventDefault(),children:i})})});q.displayName=K;var _e=`AlertDialogTitle`,J=g.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=H(n);return(0,_.jsx)(F,{...i,...r,ref:t})});J.displayName=_e;var ve=`AlertDialogDescription`,Y=g.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=H(n);return(0,_.jsx)(L,{...i,...r,ref:t})});Y.displayName=ve;var ye=`AlertDialogAction`,X=g.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=H(n);return(0,_.jsx)(z,{...i,...r,ref:t})});X.displayName=ye;var Z=`AlertDialogCancel`,Q=g.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...i}=e,{cancelRef:a}=ge(Z,n),o=H(n),s=r(t,a);return(0,_.jsx)(z,{...o,...i,ref:s})});Q.displayName=Z;var be=U,xe=W,Se=G,Ce=q,we=X,Te=Q,Ee=J,De=Y;function Oe({...e}){return(0,_.jsx)(be,{"data-slot":`alert-dialog`,...e})}function ke({...e}){return(0,_.jsx)(xe,{"data-slot":`alert-dialog-portal`,...e})}function $({className:e,...t}){return(0,_.jsx)(Se,{"data-slot":`alert-dialog-overlay`,className:h(`fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0`,e),...t})}function Ae({className:e,...t}){return(0,_.jsxs)(ke,{children:[(0,_.jsx)($,{}),(0,_.jsx)(Ce,{"data-slot":`alert-dialog-content`,className:h(`fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-card p-6 shadow-modal duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-md`,e),...t})]})}function je({className:e,...t}){return(0,_.jsx)(`div`,{"data-slot":`alert-dialog-header`,className:h(`flex flex-col gap-2 text-center sm:text-left`,e),...t})}function Me({className:e,...t}){return(0,_.jsx)(`div`,{"data-slot":`alert-dialog-footer`,className:h(`flex flex-col-reverse gap-2 sm:flex-row sm:justify-end`,e),...t})}function Ne({className:e,...t}){return(0,_.jsx)(Ee,{"data-slot":`alert-dialog-title`,className:h(`text-lg leading-none font-semibold`,e),...t})}function Pe({className:e,...t}){return(0,_.jsx)(De,{"data-slot":`alert-dialog-description`,className:h(`text-sm text-muted-foreground`,e),...t})}function Fe({className:e,...t}){return(0,_.jsx)(we,{"data-slot":`alert-dialog-action`,className:h(f({variant:`contrast`}),e),...t})}function Ie({className:e,...t}){return(0,_.jsx)(Te,{"data-slot":`alert-dialog-cancel`,className:h(f({variant:`outline`}),e),...t})}export{Pe as a,Ne as c,M as d,L as f,w as g,F as h,Ae as i,S as l,O as m,Fe as n,Me as o,A as p,Ie as r,je as s,Oe as t,z as u};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{r as e}from"./utils-YwBpOoRN.js";var t=e(`arrow-down`,[[`path`,{d:`M12 5v14`,key:`s699le`}],[`path`,{d:`m19 12-7 7-7-7`,key:`1idqje`}]]);export{t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{r as e}from"./utils-YwBpOoRN.js";var t=e(`arrow-left`,[[`path`,{d:`m12 19-7-7 7-7`,key:`1l729n`}],[`path`,{d:`M19 12H5`,key:`x3x0zl`}]]);export{t};
|