kennel 0.2.21 → 0.2.23
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 +39 -8
- package/dist/cli/dispatch.js +7 -5
- package/dist/cli/usage.js +5 -1
- package/dist/package/preview.js +138 -67
- package/dist/plugins/external.d.ts +1 -0
- package/dist/plugins/external.js +17 -0
- package/dist/site/hot-viewer.d.ts +3 -1
- package/dist/site/hot-viewer.js +43 -33
- package/dist/site/plugin.d.ts +2 -0
- package/dist/site/plugin.js +32 -0
- package/dist/site/terminal.d.ts +1 -0
- package/dist/site/terminal.js +77 -0
- package/dist/site/viewer.d.ts +6 -6
- package/dist/site/viewer.js +39 -32
- package/package.json +2 -2
- package/schemas/config.schema.json +9 -0
- package/web/assets/{abnfDiagram-VRR7QNED-D8JkjVKl.js → abnfDiagram-VRR7QNED-aRemVX7f.js} +1 -1
- package/web/assets/{arc-BU-iDKmm.js → arc-BpDsnNTf.js} +1 -1
- package/web/assets/{architectureDiagram-ZJ3FMSHR-CqrAC8xg.js → architectureDiagram-ZJ3FMSHR-DNXHQddP.js} +1 -1
- package/web/assets/ask-tray-PMT6VVXN-HMPARq3A.js +23 -0
- package/web/assets/{blockDiagram-677ZJIJ3-CwKkdWVm.js → blockDiagram-677ZJIJ3-CF4H4upo.js} +1 -1
- package/web/assets/{c4Diagram-LMCZKHZV-C0j-XG7a.js → c4Diagram-LMCZKHZV-BPWUnjtx.js} +1 -1
- package/web/assets/channel-CEZ4sDYU.js +1 -0
- package/web/assets/{chunk-2Q5K7J3B-D91WnA8d.js → chunk-2Q5K7J3B-DGsa7uv7.js} +1 -1
- package/web/assets/{chunk-32BRIVSS-CMUWV3Yb.js → chunk-32BRIVSS-BRfic3W2.js} +1 -1
- package/web/assets/{chunk-5VM5RSS4-CBonp6Qu.js → chunk-5VM5RSS4-L2h7hliu.js} +1 -1
- package/web/assets/{chunk-EX3LRPZG-B5p4u10U.js → chunk-EX3LRPZG-BPgD_fgx.js} +1 -1
- package/web/assets/{chunk-JWPE2WC7-BvcBor40.js → chunk-JWPE2WC7-CckCaqu_.js} +1 -1
- package/web/assets/{chunk-MOJQB5TN-eoj4Adi1.js → chunk-MOJQB5TN-zyGgx-VW.js} +1 -1
- package/web/assets/{chunk-RYQCIY6F-DlFyEcEU.js → chunk-RYQCIY6F-Drzpg5IG.js} +1 -1
- package/web/assets/{chunk-V7JOEXUC-Drt-X5bO.js → chunk-V7JOEXUC-Di4ESKz-.js} +1 -1
- package/web/assets/{chunk-VR4S4FIN-BHmZP44S.js → chunk-VR4S4FIN-C5lQtDI1.js} +1 -1
- package/web/assets/{chunk-XXDRQBXY-BpRFqfxG.js → chunk-XXDRQBXY-Cell0ft2.js} +1 -1
- package/web/assets/classDiagram-OUVF2IWQ-CjxqMQn2.js +1 -0
- package/web/assets/classDiagram-v2-EOCWNBFH-CjxqMQn2.js +1 -0
- package/web/assets/{cose-bilkent-JH36ORCC-BWofo0Io.js → cose-bilkent-JH36ORCC-CiKtOFxl.js} +1 -1
- package/web/assets/{cynefin-VYW2F7L2-vZngODMB.js → cynefin-VYW2F7L2-B75MWf5b.js} +1 -1
- package/web/assets/{cynefinDiagram-TSTJHNR4-Cexlv1-W.js → cynefinDiagram-TSTJHNR4-BmNVkSWW.js} +1 -1
- package/web/assets/{dagre-VKFMJZFB-Z9zxnPrH.js → dagre-VKFMJZFB-BcM1Wqu5.js} +1 -1
- package/web/assets/{diagram-FQU43EPY-DePIt5Jb.js → diagram-FQU43EPY-BiBWrxa8.js} +1 -1
- package/web/assets/{diagram-G47NLZAW-BhO62yXV.js → diagram-G47NLZAW-Cfxukr7T.js} +1 -1
- package/web/assets/{diagram-NH7WQ7WH-BPB7x0TD.js → diagram-NH7WQ7WH-C83VkBYW.js} +1 -1
- package/web/assets/{diagram-OA4YK3LP-BTu87fOH.js → diagram-OA4YK3LP-Oj0uj6CX.js} +1 -1
- package/web/assets/{diagram-WEI45ONY-NbsxbHXS.js → diagram-WEI45ONY-DhbL0nvt.js} +1 -1
- package/web/assets/{ebnfDiagram-CCIWWBDH-DcabTYw5.js → ebnfDiagram-CCIWWBDH-D4JYp1UC.js} +1 -1
- package/web/assets/{entity-search-page-G6QEQNXT-_igDx9q_.js → entity-search-page-G6QEQNXT-UKzUU5id.js} +1 -1
- package/web/assets/{erDiagram-Q63AITRT-qZDSW4hk.js → erDiagram-Q63AITRT-C-oyRt9s.js} +1 -1
- package/web/assets/{flowDiagram-23GEKE2U-BKWFG8oh.js → flowDiagram-23GEKE2U-0Tplj9FK.js} +1 -1
- package/web/assets/{ganttDiagram-NO4QXBWP-RVyAZqbP.js → ganttDiagram-NO4QXBWP-wgh2e0B_.js} +1 -1
- package/web/assets/{gitGraphDiagram-IHSO6WYX-DyJiGxOQ.js → gitGraphDiagram-IHSO6WYX-D6z9qDXa.js} +1 -1
- package/web/assets/index-D_Fcgs3V.js +451 -0
- package/web/assets/{infoDiagram-FWYZ7A6U-11u7EDGL.js → infoDiagram-FWYZ7A6U-D3o7el5X.js} +1 -1
- package/web/assets/{ishikawaDiagram-FXEZZL3T-BExEsB80.js → ishikawaDiagram-FXEZZL3T-CM0QyWYH.js} +1 -1
- package/web/assets/{journeyDiagram-5HDEW3XC-CBxrerT9.js → journeyDiagram-5HDEW3XC-fmIoh0h6.js} +1 -1
- package/web/assets/{kanban-definition-HUTT4EX6-zA-xYwTx.js → kanban-definition-HUTT4EX6-aEtqsiWf.js} +1 -1
- package/web/assets/{linear-DfGuGay4.js → linear-aJ-2UZO5.js} +1 -1
- package/web/assets/{linked-session-header-M7WY6IGF-BZNiFQoC.js → linked-session-header-M7WY6IGF-CltMJZiX.js} +1 -1
- package/web/assets/{markdown-D8rwfvNM.js → markdown-BP-54rPU.js} +1 -1
- package/web/assets/{mermaid.core-B11SxqQq.js → mermaid.core-CL9g6xVu.js} +5 -5
- package/web/assets/{mindmap-definition-LN4V7U3C-Cxv0my2M.js → mindmap-definition-LN4V7U3C-CtQo0XrW.js} +1 -1
- package/web/assets/{pegDiagram-2B236MQR-BneyaGqf.js → pegDiagram-2B236MQR-ecu1L_3A.js} +1 -1
- package/web/assets/{pieDiagram-ENE6RG2P-CBUh5NNN.js → pieDiagram-ENE6RG2P-CdCKg3Cn.js} +1 -1
- package/web/assets/{plugin-host-modules-_3hYj4i2.js → plugin-host-modules-Dz_BPJRS.js} +1 -1
- package/web/assets/{project-search-dialog-HJFGW4DG-t9HH44Ls.js → project-search-dialog-HJFGW4DG-CKtXc-UP.js} +1 -1
- package/web/assets/project-search-page-NKQEBGPF-CMzuYEeM.js +2 -0
- package/web/assets/{quadrantDiagram-ABIIQ3AL-_70f5MLq.js → quadrantDiagram-ABIIQ3AL-Bt8r5TlW.js} +1 -1
- package/web/assets/{railroadDiagram-RFXS5EU6-BjRVGtzp.js → railroadDiagram-RFXS5EU6-DrWDVEVi.js} +1 -1
- package/web/assets/react-DELVxLrX.js +9 -0
- package/web/assets/{requirementDiagram-TGXJPOKE-b4jG1W5K.js → requirementDiagram-TGXJPOKE-Ewjwpo6V.js} +1 -1
- package/web/assets/{sankeyDiagram-HTMAVEWB-Cid_FHOs.js → sankeyDiagram-HTMAVEWB-CJCMHuzX.js} +1 -1
- package/web/assets/{selection-popup-532EHNCZ-Czn9J64H.js → selection-popup-532EHNCZ-py5aCy1E.js} +1 -1
- package/web/assets/{sequenceDiagram-DBY2YBRQ-XmWVFjsx.js → sequenceDiagram-DBY2YBRQ-Chj-4L6Q.js} +1 -1
- package/web/assets/{sizeCapture-X5ZJPWSS-DcCu2WOn.js → sizeCapture-X5ZJPWSS-BguWhDIF.js} +1 -1
- package/web/assets/{stateDiagram-2N3HPSRC-DJJtthm-.js → stateDiagram-2N3HPSRC-BWisWMpK.js} +1 -1
- package/web/assets/stateDiagram-v2-6OUMAXLB-AiBXToz-.js +1 -0
- package/web/assets/{swimlanes-5IMT3BWC-DafiRSZ_.js → swimlanes-5IMT3BWC-CxVQ97_C.js} +2 -2
- package/web/assets/swimlanesDiagram-G3AALYLV-DliPmGmf.js +8 -0
- package/web/assets/{tanstack-DzYfxaKh.js → tanstack-CRaSxWtK.js} +1 -1
- package/web/assets/{timeline-definition-FHXFAJF6-BSuB4MkD.js → timeline-definition-FHXFAJF6-CCbI0InF.js} +1 -1
- package/web/assets/{vennDiagram-L72KCM5P-DzQg-Ww0.js → vennDiagram-L72KCM5P-mA5yKpJj.js} +1 -1
- package/web/assets/{wardleyDiagram-EHGQE667-DQuK20Nj.js → wardleyDiagram-EHGQE667-1Epw2EsU.js} +1 -1
- package/web/assets/{xychartDiagram-FW5EYKEG-DVpYoZuG.js → xychartDiagram-FW5EYKEG-DM1CMD-p.js} +1 -1
- package/web/index.html +4 -4
- package/dist/site/mail.d.ts +0 -1
- package/dist/site/mail.js +0 -10
- package/web/assets/ask-tray-PMT6VVXN-wk47U8hG.js +0 -23
- package/web/assets/channel-DPvp96Av.js +0 -1
- package/web/assets/classDiagram-OUVF2IWQ-UEDL7zk8.js +0 -1
- package/web/assets/classDiagram-v2-EOCWNBFH-UEDL7zk8.js +0 -1
- package/web/assets/index-DDNcaGdr.js +0 -451
- package/web/assets/project-search-page-NKQEBGPF-C-SLaeRU.js +0 -2
- package/web/assets/react-Df0zP-bB.js +0 -9
- package/web/assets/stateDiagram-v2-6OUMAXLB-N45X3hYp.js +0 -1
- package/web/assets/swimlanesDiagram-G3AALYLV-CAU4pVsD.js +0 -8
package/dist/site/hot-viewer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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.
|
|
121
|
-
?
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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(() =>
|
|
243
|
+
}).finally(async () => {
|
|
244
|
+
terminal.close();
|
|
245
|
+
await releaseLock(lock, token);
|
|
246
|
+
});
|
|
237
247
|
}
|
|
@@ -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
|
+
};
|
package/dist/site/viewer.d.ts
CHANGED
|
@@ -4,11 +4,11 @@ export type ViewerOptions = {
|
|
|
4
4
|
hot: boolean;
|
|
5
5
|
lan: boolean;
|
|
6
6
|
open: boolean;
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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>;
|
package/dist/site/viewer.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
264
|
-
await
|
|
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
|
-
|
|
309
|
-
console.log(`Kennel is already running at ${viewerUrl}`);
|
|
315
|
+
const terminal = previewTerminal(viewerUrl, options);
|
|
310
316
|
if (options.open)
|
|
311
|
-
|
|
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
|
-
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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.
|
|
3
|
+
"version": "0.2.23",
|
|
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",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"esbuild": "^0.27.0",
|
|
68
68
|
"kennel-knowledge": "https://registry.npmjs.org/%40raggle-ai%2fkennel-knowledge/-/kennel-knowledge-0.1.0.tgz",
|
|
69
69
|
"kennel-mcp": "https://registry.npmjs.org/%40raggle-ai%2fkennel-mcp/-/kennel-mcp-0.1.8.tgz",
|
|
70
|
-
"kennel-plugins": "0.1.
|
|
70
|
+
"kennel-plugins": "0.1.10",
|
|
71
71
|
"koffi": "^3.3.1",
|
|
72
72
|
"lucide-react": "^0.562.0",
|
|
73
73
|
"pi-acp": "0.0.33",
|
|
@@ -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-
|
|
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-
|
|
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};
|