kennel 0.2.21 → 0.2.22

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 (94) hide show
  1. package/README.md +39 -8
  2. package/dist/cli/dispatch.js +7 -5
  3. package/dist/cli/usage.js +5 -1
  4. package/dist/package/preview.js +138 -67
  5. package/dist/plugins/external.d.ts +1 -0
  6. package/dist/plugins/external.js +17 -0
  7. package/dist/site/hot-viewer.d.ts +3 -1
  8. package/dist/site/hot-viewer.js +43 -33
  9. package/dist/site/plugin.d.ts +2 -0
  10. package/dist/site/plugin.js +32 -0
  11. package/dist/site/terminal.d.ts +1 -0
  12. package/dist/site/terminal.js +77 -0
  13. package/dist/site/viewer.d.ts +6 -6
  14. package/dist/site/viewer.js +39 -32
  15. package/package.json +1 -1
  16. package/schemas/config.schema.json +9 -0
  17. package/web/assets/{abnfDiagram-VRR7QNED-D8JkjVKl.js → abnfDiagram-VRR7QNED-aRemVX7f.js} +1 -1
  18. package/web/assets/{arc-BU-iDKmm.js → arc-BpDsnNTf.js} +1 -1
  19. package/web/assets/{architectureDiagram-ZJ3FMSHR-CqrAC8xg.js → architectureDiagram-ZJ3FMSHR-DNXHQddP.js} +1 -1
  20. package/web/assets/ask-tray-PMT6VVXN-HMPARq3A.js +23 -0
  21. package/web/assets/{blockDiagram-677ZJIJ3-CwKkdWVm.js → blockDiagram-677ZJIJ3-CF4H4upo.js} +1 -1
  22. package/web/assets/{c4Diagram-LMCZKHZV-C0j-XG7a.js → c4Diagram-LMCZKHZV-BPWUnjtx.js} +1 -1
  23. package/web/assets/channel-CEZ4sDYU.js +1 -0
  24. package/web/assets/{chunk-2Q5K7J3B-D91WnA8d.js → chunk-2Q5K7J3B-DGsa7uv7.js} +1 -1
  25. package/web/assets/{chunk-32BRIVSS-CMUWV3Yb.js → chunk-32BRIVSS-BRfic3W2.js} +1 -1
  26. package/web/assets/{chunk-5VM5RSS4-CBonp6Qu.js → chunk-5VM5RSS4-L2h7hliu.js} +1 -1
  27. package/web/assets/{chunk-EX3LRPZG-B5p4u10U.js → chunk-EX3LRPZG-BPgD_fgx.js} +1 -1
  28. package/web/assets/{chunk-JWPE2WC7-BvcBor40.js → chunk-JWPE2WC7-CckCaqu_.js} +1 -1
  29. package/web/assets/{chunk-MOJQB5TN-eoj4Adi1.js → chunk-MOJQB5TN-zyGgx-VW.js} +1 -1
  30. package/web/assets/{chunk-RYQCIY6F-DlFyEcEU.js → chunk-RYQCIY6F-Drzpg5IG.js} +1 -1
  31. package/web/assets/{chunk-V7JOEXUC-Drt-X5bO.js → chunk-V7JOEXUC-Di4ESKz-.js} +1 -1
  32. package/web/assets/{chunk-VR4S4FIN-BHmZP44S.js → chunk-VR4S4FIN-C5lQtDI1.js} +1 -1
  33. package/web/assets/{chunk-XXDRQBXY-BpRFqfxG.js → chunk-XXDRQBXY-Cell0ft2.js} +1 -1
  34. package/web/assets/classDiagram-OUVF2IWQ-CjxqMQn2.js +1 -0
  35. package/web/assets/classDiagram-v2-EOCWNBFH-CjxqMQn2.js +1 -0
  36. package/web/assets/{cose-bilkent-JH36ORCC-BWofo0Io.js → cose-bilkent-JH36ORCC-CiKtOFxl.js} +1 -1
  37. package/web/assets/{cynefin-VYW2F7L2-vZngODMB.js → cynefin-VYW2F7L2-B75MWf5b.js} +1 -1
  38. package/web/assets/{cynefinDiagram-TSTJHNR4-Cexlv1-W.js → cynefinDiagram-TSTJHNR4-BmNVkSWW.js} +1 -1
  39. package/web/assets/{dagre-VKFMJZFB-Z9zxnPrH.js → dagre-VKFMJZFB-BcM1Wqu5.js} +1 -1
  40. package/web/assets/{diagram-FQU43EPY-DePIt5Jb.js → diagram-FQU43EPY-BiBWrxa8.js} +1 -1
  41. package/web/assets/{diagram-G47NLZAW-BhO62yXV.js → diagram-G47NLZAW-Cfxukr7T.js} +1 -1
  42. package/web/assets/{diagram-NH7WQ7WH-BPB7x0TD.js → diagram-NH7WQ7WH-C83VkBYW.js} +1 -1
  43. package/web/assets/{diagram-OA4YK3LP-BTu87fOH.js → diagram-OA4YK3LP-Oj0uj6CX.js} +1 -1
  44. package/web/assets/{diagram-WEI45ONY-NbsxbHXS.js → diagram-WEI45ONY-DhbL0nvt.js} +1 -1
  45. package/web/assets/{ebnfDiagram-CCIWWBDH-DcabTYw5.js → ebnfDiagram-CCIWWBDH-D4JYp1UC.js} +1 -1
  46. package/web/assets/{entity-search-page-G6QEQNXT-_igDx9q_.js → entity-search-page-G6QEQNXT-UKzUU5id.js} +1 -1
  47. package/web/assets/{erDiagram-Q63AITRT-qZDSW4hk.js → erDiagram-Q63AITRT-C-oyRt9s.js} +1 -1
  48. package/web/assets/{flowDiagram-23GEKE2U-BKWFG8oh.js → flowDiagram-23GEKE2U-0Tplj9FK.js} +1 -1
  49. package/web/assets/{ganttDiagram-NO4QXBWP-RVyAZqbP.js → ganttDiagram-NO4QXBWP-wgh2e0B_.js} +1 -1
  50. package/web/assets/{gitGraphDiagram-IHSO6WYX-DyJiGxOQ.js → gitGraphDiagram-IHSO6WYX-D6z9qDXa.js} +1 -1
  51. package/web/assets/index-D_Fcgs3V.js +451 -0
  52. package/web/assets/{infoDiagram-FWYZ7A6U-11u7EDGL.js → infoDiagram-FWYZ7A6U-D3o7el5X.js} +1 -1
  53. package/web/assets/{ishikawaDiagram-FXEZZL3T-BExEsB80.js → ishikawaDiagram-FXEZZL3T-CM0QyWYH.js} +1 -1
  54. package/web/assets/{journeyDiagram-5HDEW3XC-CBxrerT9.js → journeyDiagram-5HDEW3XC-fmIoh0h6.js} +1 -1
  55. package/web/assets/{kanban-definition-HUTT4EX6-zA-xYwTx.js → kanban-definition-HUTT4EX6-aEtqsiWf.js} +1 -1
  56. package/web/assets/{linear-DfGuGay4.js → linear-aJ-2UZO5.js} +1 -1
  57. package/web/assets/{linked-session-header-M7WY6IGF-BZNiFQoC.js → linked-session-header-M7WY6IGF-CltMJZiX.js} +1 -1
  58. package/web/assets/{markdown-D8rwfvNM.js → markdown-BP-54rPU.js} +1 -1
  59. package/web/assets/{mermaid.core-B11SxqQq.js → mermaid.core-CL9g6xVu.js} +5 -5
  60. package/web/assets/{mindmap-definition-LN4V7U3C-Cxv0my2M.js → mindmap-definition-LN4V7U3C-CtQo0XrW.js} +1 -1
  61. package/web/assets/{pegDiagram-2B236MQR-BneyaGqf.js → pegDiagram-2B236MQR-ecu1L_3A.js} +1 -1
  62. package/web/assets/{pieDiagram-ENE6RG2P-CBUh5NNN.js → pieDiagram-ENE6RG2P-CdCKg3Cn.js} +1 -1
  63. package/web/assets/{plugin-host-modules-_3hYj4i2.js → plugin-host-modules-Dz_BPJRS.js} +1 -1
  64. package/web/assets/{project-search-dialog-HJFGW4DG-t9HH44Ls.js → project-search-dialog-HJFGW4DG-CKtXc-UP.js} +1 -1
  65. package/web/assets/project-search-page-NKQEBGPF-CMzuYEeM.js +2 -0
  66. package/web/assets/{quadrantDiagram-ABIIQ3AL-_70f5MLq.js → quadrantDiagram-ABIIQ3AL-Bt8r5TlW.js} +1 -1
  67. package/web/assets/{railroadDiagram-RFXS5EU6-BjRVGtzp.js → railroadDiagram-RFXS5EU6-DrWDVEVi.js} +1 -1
  68. package/web/assets/react-DELVxLrX.js +9 -0
  69. package/web/assets/{requirementDiagram-TGXJPOKE-b4jG1W5K.js → requirementDiagram-TGXJPOKE-Ewjwpo6V.js} +1 -1
  70. package/web/assets/{sankeyDiagram-HTMAVEWB-Cid_FHOs.js → sankeyDiagram-HTMAVEWB-CJCMHuzX.js} +1 -1
  71. package/web/assets/{selection-popup-532EHNCZ-Czn9J64H.js → selection-popup-532EHNCZ-py5aCy1E.js} +1 -1
  72. package/web/assets/{sequenceDiagram-DBY2YBRQ-XmWVFjsx.js → sequenceDiagram-DBY2YBRQ-Chj-4L6Q.js} +1 -1
  73. package/web/assets/{sizeCapture-X5ZJPWSS-DcCu2WOn.js → sizeCapture-X5ZJPWSS-BguWhDIF.js} +1 -1
  74. package/web/assets/{stateDiagram-2N3HPSRC-DJJtthm-.js → stateDiagram-2N3HPSRC-BWisWMpK.js} +1 -1
  75. package/web/assets/stateDiagram-v2-6OUMAXLB-AiBXToz-.js +1 -0
  76. package/web/assets/{swimlanes-5IMT3BWC-DafiRSZ_.js → swimlanes-5IMT3BWC-CxVQ97_C.js} +2 -2
  77. package/web/assets/swimlanesDiagram-G3AALYLV-DliPmGmf.js +8 -0
  78. package/web/assets/{tanstack-DzYfxaKh.js → tanstack-CRaSxWtK.js} +1 -1
  79. package/web/assets/{timeline-definition-FHXFAJF6-BSuB4MkD.js → timeline-definition-FHXFAJF6-CCbI0InF.js} +1 -1
  80. package/web/assets/{vennDiagram-L72KCM5P-DzQg-Ww0.js → vennDiagram-L72KCM5P-mA5yKpJj.js} +1 -1
  81. package/web/assets/{wardleyDiagram-EHGQE667-DQuK20Nj.js → wardleyDiagram-EHGQE667-1Epw2EsU.js} +1 -1
  82. package/web/assets/{xychartDiagram-FW5EYKEG-DVpYoZuG.js → xychartDiagram-FW5EYKEG-DM1CMD-p.js} +1 -1
  83. package/web/index.html +4 -4
  84. package/dist/site/mail.d.ts +0 -1
  85. package/dist/site/mail.js +0 -10
  86. package/web/assets/ask-tray-PMT6VVXN-wk47U8hG.js +0 -23
  87. package/web/assets/channel-DPvp96Av.js +0 -1
  88. package/web/assets/classDiagram-OUVF2IWQ-UEDL7zk8.js +0 -1
  89. package/web/assets/classDiagram-v2-EOCWNBFH-UEDL7zk8.js +0 -1
  90. package/web/assets/index-DDNcaGdr.js +0 -451
  91. package/web/assets/project-search-page-NKQEBGPF-C-SLaeRU.js +0 -2
  92. package/web/assets/react-Df0zP-bB.js +0 -9
  93. package/web/assets/stateDiagram-v2-6OUMAXLB-N45X3hYp.js +0 -1
  94. package/web/assets/swimlanesDiagram-G3AALYLV-CAU4pVsD.js +0 -8
@@ -1,4 +1,4 @@
1
- import { execFile, execFileSync, spawn } from 'node:child_process';
1
+ import { execFileSync, spawn } from 'node:child_process';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { existsSync } from 'node:fs';
4
4
  import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
@@ -6,11 +6,17 @@ import { createConnection } from 'node:net';
6
6
  import { tmpdir } from 'node:os';
7
7
  import { dirname, relative, resolve } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
+ import { popUrl } from './viewer.js';
9
10
  import { createKennelClient } from '../core/api-client.js';
10
11
  import { pluginSetKey } from '../plugins/external.js';
12
+ import { openPreview, previewTerminal } from './terminal.js';
11
13
  function sourceRoot() {
12
14
  return resolve(dirname(fileURLToPath(import.meta.url)), '../../../..');
13
15
  }
16
+ /** The hot viewer runs Vite from a Kennel source checkout; npm installs have none. */
17
+ export function hotViewerAvailable() {
18
+ return existsSync(resolve(sourceRoot(), 'apps/web/vite.config.ts'));
19
+ }
14
20
  async function viewerStatus(url) {
15
21
  return createKennelClient({ baseUrl: url }).health({ signal: AbortSignal.timeout(1_000) })
16
22
  .then((body) => body.viewer === 'kennel-vite' || body.viewer === 'kennel-core'
@@ -55,13 +61,6 @@ function startupLock(host, port) {
55
61
  const name = `${host}-${port}`.replaceAll(/[^a-zA-Z0-9.-]/g, '-');
56
62
  return resolve(tmpdir(), `kennel-hot-viewer-${name}.lock`);
57
63
  }
58
- function openUrl(url) {
59
- const command = process.platform === 'darwin' ? '/usr/bin/open' : process.platform === 'win32' ? 'explorer.exe' : 'xdg-open';
60
- execFile(command, [url], (error) => {
61
- if (error)
62
- console.error(`Could not open browser: ${error.message}`);
63
- });
64
- }
65
64
  function lanHost() {
66
65
  const route = execFileSync('/sbin/route', ['-n', 'get', 'default'], {
67
66
  encoding: 'utf8',
@@ -100,8 +99,7 @@ export async function hotViewer(options) {
100
99
  throw new Error('The hot viewer is not available through Tailscale. Use kennel serve for remote access.');
101
100
  }
102
101
  const root = sourceRoot();
103
- const config = resolve(root, 'apps/web/vite.config.ts');
104
- if (!existsSync(config)) {
102
+ if (!hotViewerAvailable()) {
105
103
  throw new Error('The hot viewer requires a Kennel source checkout.');
106
104
  }
107
105
  const args = [
@@ -117,21 +115,23 @@ export async function hotViewer(options) {
117
115
  '--strictPort',
118
116
  ];
119
117
  const baseQuery = new URLSearchParams({ folder: options.root });
120
- const openPath = options.target
121
- ? `/?${new URLSearchParams({
122
- folder: options.root,
123
- document: relative(options.root, options.target),
124
- ...(options.watch ? { watch: '1' } : {}),
125
- })}`
126
- : options.openPath ?? `/?${baseQuery}`;
118
+ const openPath = options.plugin
119
+ ? new URL(popUrl(options.host, options.port, options)).pathname
120
+ : options.target
121
+ ? `/?${new URLSearchParams({
122
+ folder: options.root,
123
+ document: relative(options.root, options.target),
124
+ ...(options.watch ? { watch: '1' } : {}),
125
+ })}`
126
+ : `/?${baseQuery}`;
127
127
  const url = `http://${options.host}:${options.port}`;
128
128
  const displayHost = options.lan ? lanHost() : options.host;
129
129
  // The LAN address is computed before the server starts so the Vite config
130
130
  // (allowedHosts + the API host guard) accepts the phone's Host header.
131
131
  const viewerHosts = [displayHost, 'localhost', '127.0.0.1'].join(',');
132
132
  const viewerUrl = `http://${displayHost}:${options.port}${openPath}`;
133
- console.log(`Kennel hot viewer: ${viewerUrl}`);
134
133
  if (options.dryRun) {
134
+ console.log(`Kennel hot viewer: ${viewerUrl}`);
135
135
  console.log(`bun ${[...args, ...(options.open ? ['--open', openPath] : [])].join(' ')}`);
136
136
  return;
137
137
  }
@@ -139,7 +139,7 @@ export async function hotViewer(options) {
139
139
  const expectedKey = pluginSetKey();
140
140
  // Reuse only a Vite viewer that serves the same plugin set. Anything else on
141
141
  // the port is stale for this plugin set and must be restarted, never reused.
142
- const reuse = (running) => {
142
+ const reuse = async (running) => {
143
143
  const restart = 'Stop that viewer (Ctrl+C in its terminal) and run this command again, or select another port.';
144
144
  if (running.viewer !== 'kennel-vite') {
145
145
  throw new Error(`Port ${options.port} runs the static Kennel viewer, not the hot viewer. ${restart}`);
@@ -148,18 +148,20 @@ export async function hotViewer(options) {
148
148
  throw new Error(`The Kennel hot viewer on port ${options.port} serves plugin set ${running.kernelKey}, `
149
149
  + `but the installed plugins are ${expectedKey}. ${restart}`);
150
150
  }
151
- if (options.open) {
152
- // Do not stack another tab: the viewer is already open. Print the URL
153
- // so the caller can still get to it deliberately.
154
- console.log(`Kennel hot viewer is already running at ${viewerUrl}`);
151
+ const terminal = previewTerminal(viewerUrl, options);
152
+ if (options.open)
153
+ openPreview(viewerUrl);
154
+ try {
155
+ if (terminal.interactive)
156
+ await terminal.closed;
155
157
  }
156
- else {
157
- console.log('Kennel hot viewer is already running. Reusing it.');
158
+ finally {
159
+ terminal.close();
158
160
  }
159
161
  };
160
162
  const status = await viewerStatus(url);
161
163
  if (typeof status === 'object') {
162
- reuse(status);
164
+ await reuse(status);
163
165
  return;
164
166
  }
165
167
  if (status === 'other') {
@@ -168,7 +170,7 @@ export async function hotViewer(options) {
168
170
  if (await portOpen(options.host, options.port)) {
169
171
  const running = await runningViewer(url, 20, true);
170
172
  if (running) {
171
- reuse(running);
173
+ await reuse(running);
172
174
  return;
173
175
  }
174
176
  throw new Error(`Port ${options.port} is used by another service. Stop it or select another port.`);
@@ -178,7 +180,7 @@ export async function hotViewer(options) {
178
180
  if (!token) {
179
181
  const running = await runningViewer(url, 40, true);
180
182
  if (running) {
181
- reuse(running);
183
+ await reuse(running);
182
184
  return;
183
185
  }
184
186
  await rm(lock, { force: true, recursive: true });
@@ -189,10 +191,9 @@ export async function hotViewer(options) {
189
191
  const running = await runningViewer(url);
190
192
  if (running) {
191
193
  await releaseLock(lock, token);
192
- reuse(running);
194
+ await reuse(running);
193
195
  return;
194
196
  }
195
- console.log(`bun ${args.join(' ')}`);
196
197
  if (options.open)
197
198
  args.push('--open', openPath);
198
199
  const child = spawn('bun', args, {
@@ -211,7 +212,12 @@ export async function hotViewer(options) {
211
212
  ...(options.target ? { KENNEL_VIEWER_TARGET: options.target } : {}),
212
213
  LC_ALL: 'en_GB.UTF-8',
213
214
  },
214
- stdio: 'inherit',
215
+ stdio: ['ignore', 'inherit', 'inherit'],
216
+ });
217
+ const terminal = previewTerminal(viewerUrl, options);
218
+ void terminal.closed.then(() => {
219
+ if (child.exitCode === null && child.signalCode === null)
220
+ child.kill('SIGTERM');
215
221
  });
216
222
  await new Promise((done, fail) => {
217
223
  child.once('error', fail);
@@ -220,10 +226,11 @@ export async function hotViewer(options) {
220
226
  done();
221
227
  return;
222
228
  }
229
+ terminal.close();
223
230
  const existing = await runningViewer(url, 20, true);
224
231
  if (existing) {
225
232
  try {
226
- reuse(existing);
233
+ await reuse(existing);
227
234
  done();
228
235
  }
229
236
  catch (error) {
@@ -233,5 +240,8 @@ export async function hotViewer(options) {
233
240
  }
234
241
  fail(new Error(`Kennel hot viewer stopped with exit code ${code ?? 'unknown'}`));
235
242
  });
236
- }).finally(() => releaseLock(lock, token));
243
+ }).finally(async () => {
244
+ terminal.close();
245
+ await releaseLock(lock, token);
246
+ });
237
247
  }
@@ -0,0 +1,2 @@
1
+ /** Installed ids are commands; plugins can declare additional command aliases. */
2
+ export declare function pluginCommand(command: string, args: string[]): Promise<boolean>;
@@ -0,0 +1,32 @@
1
+ import { readExternalPlugins } from "../plugins/external.js";
2
+ import { viewer } from "./viewer.js";
3
+ /** Installed ids are commands; plugins can declare additional command aliases. */
4
+ export async function pluginCommand(command, args) {
5
+ const plugins = readExternalPlugins();
6
+ const byId = plugins.find((plugin) => plugin.id === command);
7
+ const matches = byId
8
+ ? [byId]
9
+ : plugins.filter((plugin) => plugin.aliases?.includes(command));
10
+ if (matches.length === 0)
11
+ return false;
12
+ if (matches.length > 1) {
13
+ throw new Error(`Ambiguous plugin command ${command}. Use a plugin id: ${matches.map((plugin) => plugin.id).sort().join(", ")}`);
14
+ }
15
+ const input = [];
16
+ let subpath;
17
+ for (let index = 0; index < args.length; index += 1) {
18
+ const arg = args[index];
19
+ if (arg === "--item" || arg.startsWith("--item=")) {
20
+ if (subpath !== undefined)
21
+ throw new Error("Use --item once");
22
+ subpath = arg === "--item" ? args[++index] : arg.slice("--item=".length);
23
+ if (!subpath || subpath.startsWith("--"))
24
+ throw new Error("--item requires a plugin item id");
25
+ }
26
+ else {
27
+ input.push(arg);
28
+ }
29
+ }
30
+ await viewer(input, { id: matches[0].id, ...(subpath ? { subpath } : {}) });
31
+ return true;
32
+ }
@@ -0,0 +1 @@
1
+ export { openPreview, previewTerminal } from "kennel-preview/terminal";
@@ -0,0 +1,77 @@
1
+ // packages/preview/src/terminal.ts
2
+ import { execFile } from "node:child_process";
3
+ import { basename } from "node:path";
4
+ import { emitKeypressEvents } from "node:readline";
5
+ function openPreview(url) {
6
+ const command = process.platform === "darwin" ? "/usr/bin/open" : process.platform === "win32" ? "explorer.exe" : "xdg-open";
7
+ execFile(command, [url], (error) => {
8
+ if (error)
9
+ console.error(`Could not open browser: ${error.message}`);
10
+ });
11
+ }
12
+ function previewTerminal(url, target) {
13
+ url = new URL(url).href;
14
+ const input = process.stdin;
15
+ const output = process.stdout;
16
+ const terminal = Boolean(output.isTTY && process.env.TERM !== "dumb");
17
+ const interactive = terminal && Boolean(input.isTTY);
18
+ const name = (basename(target.target ?? target.root) || target.root).replace(/[\u0000-\u001f\u007f-\u009f]/g, "");
19
+ const label = `(open ${name})`;
20
+ const color = terminal && process.env.NO_COLOR === undefined && output.hasColors();
21
+ const accent = (text) => color ? `\x1B[36m${text}\x1B[39m` : text;
22
+ const link = terminal ? `\x1B]8;;${url}\x1B\\${accent(label)}\x1B]8;;\x1B\\` : `${label}
23
+ ${url}`;
24
+ const wasRaw = Boolean(input.isRaw);
25
+ const wasFlowing = input.readableFlowing === true;
26
+ let resolveClosed;
27
+ const closed = new Promise((resolve) => {
28
+ resolveClosed = resolve;
29
+ });
30
+ let finished = false;
31
+ const close = () => {
32
+ if (finished)
33
+ return;
34
+ finished = true;
35
+ process.off("SIGINT", close);
36
+ process.off("SIGTERM", close);
37
+ process.off("exit", close);
38
+ if (interactive) {
39
+ input.off("keypress", keypress);
40
+ input.off("end", close);
41
+ input.setRawMode(wasRaw);
42
+ if (!wasFlowing)
43
+ input.pause();
44
+ }
45
+ resolveClosed();
46
+ };
47
+ const keypress = (_text, key) => {
48
+ if (key.ctrl && (key.name === "c" || key.name === "d"))
49
+ close();
50
+ else if (key.name === "o" && !key.ctrl && !key.meta)
51
+ openPreview(url);
52
+ };
53
+ process.once("SIGINT", close);
54
+ process.once("SIGTERM", close);
55
+ process.once("exit", close);
56
+ if (interactive) {
57
+ emitKeypressEvents(input);
58
+ input.setRawMode(true);
59
+ input.on("keypress", keypress);
60
+ input.once("end", close);
61
+ input.resume();
62
+ }
63
+ console.log(`
64
+ ${link}
65
+ `);
66
+ if (interactive) {
67
+ console.log(` shortcuts:
68
+ ${accent("o")} - open in browser
69
+ ctrl+c - exit
70
+ `);
71
+ }
72
+ return { close, closed, interactive };
73
+ }
74
+ export {
75
+ openPreview,
76
+ previewTerminal
77
+ };
@@ -4,11 +4,11 @@ export type ViewerOptions = {
4
4
  hot: boolean;
5
5
  lan: boolean;
6
6
  open: boolean;
7
- /**
8
- * Optional path (including query) to open in the browser, for hot-viewer flows.
9
- * Example: `/folder/<encoded-folder>/views/mail`.
10
- */
11
- openPath?: string;
7
+ /** Installed plugin and its optional plugin-owned item subpath. */
8
+ plugin?: {
9
+ id: string;
10
+ subpath?: string;
11
+ };
12
12
  root: string;
13
13
  port: string;
14
14
  /**
@@ -33,4 +33,4 @@ export declare function viewerFolder(root: string, value?: string | null): strin
33
33
  export declare function scopedFolder(root: string, value?: string | null): string;
34
34
  export declare function splitViewerArgs(input: string[]): ViewerOptions;
35
35
  export declare function popUrl(host: string, port: string, options: ViewerOptions): string;
36
- export declare function viewer(input: string[]): Promise<void>;
36
+ export declare function viewer(input: string[], plugin?: ViewerOptions["plugin"]): Promise<void>;
@@ -1,8 +1,9 @@
1
- import { execFile, execFileSync } from "node:child_process";
1
+ import { execFileSync } from "node:child_process";
2
2
  import { existsSync, realpathSync, statSync } from "node:fs";
3
3
  import { createServer } from "node:net";
4
4
  import { dirname, isAbsolute, relative, resolve } from "node:path";
5
5
  import { previewKind } from "./kinds.js";
6
+ import { openPreview, previewTerminal } from "./terminal.js";
6
7
  export function viewerFolder(root, value) {
7
8
  return resolve(value ?? root);
8
9
  }
@@ -165,6 +166,11 @@ export function popUrl(host, port, options) {
165
166
  const scheme = options.tailscale ? "https" : "http";
166
167
  const authority = options.tailscale ? host : `${host}:${port}`;
167
168
  const url = new URL(`${scheme}://${authority}/`);
169
+ if (options.plugin) {
170
+ const { id, subpath } = options.plugin;
171
+ url.pathname = `/folder/${encodeBase64Url(options.root)}/views/${encodeURIComponent(id)}${subpath ? `/${encodeURIComponent(subpath)}` : ""}`;
172
+ return url.href;
173
+ }
168
174
  if (!options.target) {
169
175
  url.pathname = `/folder/${encodeBase64Url(options.root)}/notes`;
170
176
  return url.href;
@@ -222,17 +228,6 @@ async function availableViewerPort(start, host) {
222
228
  }
223
229
  throw new Error(`Kennel could not find a free port at or above ${start}`);
224
230
  }
225
- function openUrl(url) {
226
- const command = process.platform === "darwin"
227
- ? "/usr/bin/open"
228
- : process.platform === "win32"
229
- ? "explorer.exe"
230
- : "xdg-open";
231
- execFile(command, [url], (error) => {
232
- if (error)
233
- console.error(`Could not open browser: ${error.message}`);
234
- });
235
- }
236
231
  function lanHost() {
237
232
  const route = execFileSync("/sbin/route", ["-n", "get", "default"], {
238
233
  encoding: "utf8",
@@ -256,12 +251,24 @@ function tailscaleHost() {
256
251
  return host;
257
252
  throw new Error("kennel could not find the Tailscale DNS name");
258
253
  }
259
- export async function viewer(input) {
254
+ export async function viewer(input, plugin) {
260
255
  const portExplicit = viewerPortWasExplicit(input);
261
256
  const options = splitViewerArgs(input);
257
+ if (plugin) {
258
+ if (options.target)
259
+ throw new Error("A plugin command requires a folder, not a file. Use --item to select a plugin item.");
260
+ options.plugin = plugin;
261
+ }
262
262
  if (options.hot) {
263
- const { hotViewer } = await import("./hot-viewer.js");
264
- await hotViewer(options);
263
+ // Installed packages serve the packaged static viewer instead.
264
+ const { hotViewer, hotViewerAvailable } = await import("./hot-viewer.js");
265
+ if (hotViewerAvailable()) {
266
+ await hotViewer(options);
267
+ return;
268
+ }
269
+ }
270
+ if (options.dryRun) {
271
+ console.log(popUrl(options.host, options.port, options));
265
272
  return;
266
273
  }
267
274
  const { ensurePluginBuild, usePluginBuild, webRoot } = await import("../plugins/external.js");
@@ -305,10 +312,16 @@ export async function viewer(input) {
305
312
  existing.kernelKey === plugins.key &&
306
313
  isSameOrDescendant(existing.root, options.root)))) {
307
314
  const viewerUrl = popUrl(displayHost, options.port, options);
308
- console.log(viewerUrl);
309
- console.log(`Kennel is already running at ${viewerUrl}`);
315
+ const terminal = previewTerminal(viewerUrl, options);
310
316
  if (options.open)
311
- openUrl(viewerUrl);
317
+ openPreview(viewerUrl);
318
+ try {
319
+ if (terminal.interactive)
320
+ await terminal.closed;
321
+ }
322
+ finally {
323
+ terminal.close();
324
+ }
312
325
  return;
313
326
  }
314
327
  if (!portExplicit) {
@@ -325,25 +338,19 @@ export async function viewer(input) {
325
338
  });
326
339
  try {
327
340
  await api.listen(Number(options.port));
328
- console.log(`Reading documents from ${options.root}`);
329
341
  if (options.tailscale) {
330
342
  execFileSync("tailscale", ["serve", "--bg", "--yes", options.port]);
331
343
  }
332
344
  const viewerUrl = popUrl(displayHost, options.port, options);
333
- console.log(viewerUrl);
334
- console.log(`Kennel is running at ${viewerUrl}`);
335
- console.log("Press Ctrl+C to stop.");
345
+ const terminal = previewTerminal(viewerUrl, options);
336
346
  if (options.open)
337
- openUrl(viewerUrl);
338
- await new Promise((done) => {
339
- const close = () => {
340
- process.off("SIGINT", close);
341
- process.off("SIGTERM", close);
342
- done();
343
- };
344
- process.once("SIGINT", close);
345
- process.once("SIGTERM", close);
346
- });
347
+ openPreview(viewerUrl);
348
+ try {
349
+ await terminal.closed;
350
+ }
351
+ finally {
352
+ terminal.close();
353
+ }
347
354
  }
348
355
  finally {
349
356
  await api.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kennel",
3
- "version": "0.2.21",
3
+ "version": "0.2.22",
4
4
  "description": "Open-source pop-up previews for AI output: markdown, HTML artifacts, email, images, code, tables, notes, and calendars, from the terminal or your agent session.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -56,6 +56,15 @@
56
56
  "type": "string",
57
57
  "pattern": "^[a-z][a-z0-9-]*$"
58
58
  },
59
+ "aliases": {
60
+ "description": "Optional CLI command aliases declared by the plugin. The plugin id is always a command.",
61
+ "type": "array",
62
+ "uniqueItems": true,
63
+ "items": {
64
+ "type": "string",
65
+ "pattern": "^[a-z][a-z0-9-]*$"
66
+ }
67
+ },
59
68
  "directory": {
60
69
  "description": "Plugin package root relative to the repository root.",
61
70
  "type": "string",
@@ -1 +1 @@
1
- import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-eoj4Adi1.js";import{p as f}from"./chunk-JWPE2WC7-BvcBor40.js";import{_ as n,l as o}from"./mermaid.core-B11SxqQq.js";import{b as c,M as d}from"./cynefin-VYW2F7L2-vZngODMB.js";import"./index-DDNcaGdr.js";import"./react-Df0zP-bB.js";import"./markdown-D8rwfvNM.js";import"./tanstack-DzYfxaKh.js";var v=c().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),m=t?parseInt(t,10):0,l=s?parseInt(s,10):1/0;return{min:m,max:l}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new d(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},C={parser:R,db:a,renderer:u,styles:p};export{C as diagram};
1
+ import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-zyGgx-VW.js";import{p as f}from"./chunk-JWPE2WC7-CckCaqu_.js";import{_ as n,l as o}from"./mermaid.core-CL9g6xVu.js";import{b as c,M as d}from"./cynefin-VYW2F7L2-B75MWf5b.js";import"./index-D_Fcgs3V.js";import"./react-DELVxLrX.js";import"./markdown-BP-54rPU.js";import"./tanstack-CRaSxWtK.js";var v=c().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),m=t?parseInt(t,10):0,l=s?parseInt(s,10):1/0;return{min:m,max:l}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new d(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},C={parser:R,db:a,renderer:u,styles:p};export{C as diagram};
@@ -1 +1 @@
1
- import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as sn,ab as on,ac as fn}from"./mermaid.core-B11SxqQq.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
1
+ import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as sn,ab as on,ac as fn}from"./mermaid.core-CL9g6xVu.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};