kennel 0.2.31 → 0.2.33
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/dist/cli/dispatch.js +10 -0
- package/dist/cli/usage.js +9 -0
- package/dist/core/runtime.js +531 -197
- package/dist/llm/clipboard.d.ts +1 -0
- package/dist/llm/clipboard.js +59 -0
- package/dist/llm/command.d.ts +1 -0
- package/dist/llm/command.js +226 -0
- package/dist/llm/worker.d.ts +19 -0
- package/dist/llm/worker.js +1131 -0
- package/dist/notes/command.d.ts +1 -0
- package/dist/notes/command.js +556 -0
- package/dist/package/preview.js +554 -219
- package/dist/plugins/external.d.ts +6 -0
- package/dist/plugins/external.js +16 -0
- package/dist/site/server-plugins.d.ts +12 -0
- package/dist/site/server-plugins.js +132 -11
- package/package.json +4 -2
- package/schemas/config.schema.json +11 -0
- package/templates/plugin/package.json +1 -0
- package/templates/plugin/server.ts +43 -0
- package/templates/plugin/tsconfig.json +1 -1
- package/web/assets/{abnfDiagram-VRR7QNED-DPZyna8O.js → abnfDiagram-VRR7QNED-CpP1VHKI.js} +1 -1
- package/web/assets/{arc-BAcU9y_V.js → arc-kL4AxfvR.js} +1 -1
- package/web/assets/{architectureDiagram-ZJ3FMSHR-D5yd7Cag.js → architectureDiagram-ZJ3FMSHR-BWaRktRJ.js} +1 -1
- package/web/assets/{ask-tray-PMT6VVXN-C4dJH21Q.js → ask-tray-PMT6VVXN-FqPDpwtw.js} +3 -3
- package/web/assets/{blockDiagram-677ZJIJ3-C7LSGyxV.js → blockDiagram-677ZJIJ3-B07uVoPU.js} +1 -1
- package/web/assets/{c4Diagram-LMCZKHZV-BW2KfoQr.js → c4Diagram-LMCZKHZV-uvoaQC09.js} +1 -1
- package/web/assets/channel-Djmf1Ik1.js +1 -0
- package/web/assets/{chunk-2Q5K7J3B-4Og6VAFG.js → chunk-2Q5K7J3B-BH3SaHqk.js} +1 -1
- package/web/assets/{chunk-32BRIVSS-BYOowQl5.js → chunk-32BRIVSS-SPD8Eraw.js} +1 -1
- package/web/assets/{chunk-5VM5RSS4-C1m-lq64.js → chunk-5VM5RSS4-DNeOgOuZ.js} +1 -1
- package/web/assets/{chunk-EX3LRPZG-B2hriRpJ.js → chunk-EX3LRPZG-SJQ-vKGz.js} +1 -1
- package/web/assets/{chunk-JWPE2WC7-C6RsnzVA.js → chunk-JWPE2WC7-Dy_9wZ2Y.js} +1 -1
- package/web/assets/{chunk-MOJQB5TN-yKbMmUWf.js → chunk-MOJQB5TN-CuIx80Vn.js} +1 -1
- package/web/assets/{chunk-RYQCIY6F-BOokm3Qc.js → chunk-RYQCIY6F-DDG61RKR.js} +1 -1
- package/web/assets/{chunk-V7JOEXUC-CKW-H5An.js → chunk-V7JOEXUC-7OMVhaoH.js} +1 -1
- package/web/assets/{chunk-VR4S4FIN-BU5pFrup.js → chunk-VR4S4FIN-Y-HNaSmV.js} +1 -1
- package/web/assets/{chunk-XXDRQBXY-DYtdSJHU.js → chunk-XXDRQBXY-CtG0MPgC.js} +1 -1
- package/web/assets/classDiagram-OUVF2IWQ-hlDS9RWf.js +1 -0
- package/web/assets/classDiagram-v2-EOCWNBFH-hlDS9RWf.js +1 -0
- package/web/assets/{cose-bilkent-JH36ORCC-BZeXtC6E.js → cose-bilkent-JH36ORCC-BZaPMYcl.js} +1 -1
- package/web/assets/{cynefin-VYW2F7L2-BSDVyzMI.js → cynefin-VYW2F7L2-DxAlRHA1.js} +1 -1
- package/web/assets/{cynefinDiagram-TSTJHNR4-CReoDBYo.js → cynefinDiagram-TSTJHNR4-gjgzmagS.js} +1 -1
- package/web/assets/{dagre-VKFMJZFB-CBLLzMxd.js → dagre-VKFMJZFB-DvLPeYuw.js} +1 -1
- package/web/assets/{diagram-FQU43EPY-BcGWiZXZ.js → diagram-FQU43EPY-443LFnzG.js} +1 -1
- package/web/assets/{diagram-G47NLZAW-BXUYZA9N.js → diagram-G47NLZAW-CqfUewot.js} +1 -1
- package/web/assets/{diagram-NH7WQ7WH-BrvrZs7N.js → diagram-NH7WQ7WH-D-FElUGW.js} +1 -1
- package/web/assets/{diagram-OA4YK3LP-7lnVcjXL.js → diagram-OA4YK3LP-DbidUAp5.js} +1 -1
- package/web/assets/{diagram-WEI45ONY-s58_bMFf.js → diagram-WEI45ONY-CPLeckCZ.js} +1 -1
- package/web/assets/{ebnfDiagram-CCIWWBDH-BemWecoD.js → ebnfDiagram-CCIWWBDH-DR0rqZzN.js} +1 -1
- package/web/assets/{entity-search-page-G6QEQNXT--sKP5dP3.js → entity-search-page-G6QEQNXT-CnG8d-at.js} +1 -1
- package/web/assets/{erDiagram-Q63AITRT-ClVJddch.js → erDiagram-Q63AITRT-DMXv_8Tj.js} +1 -1
- package/web/assets/{flowDiagram-23GEKE2U-xzUJu-i_.js → flowDiagram-23GEKE2U-DnbQjZs0.js} +1 -1
- package/web/assets/{ganttDiagram-NO4QXBWP-CXQmtF5g.js → ganttDiagram-NO4QXBWP-DBKIrjj8.js} +1 -1
- package/web/assets/{gitGraphDiagram-IHSO6WYX-DCdljcLl.js → gitGraphDiagram-IHSO6WYX-BqqxKWT_.js} +1 -1
- package/web/assets/{index-CtTx1o2d.js → index-Bd3ghcW-.js} +5 -5
- package/web/assets/{index-vjrhIAJn.css → index-Dt8OzQCe.css} +1 -1
- package/web/assets/{infoDiagram-FWYZ7A6U-B11SJYSK.js → infoDiagram-FWYZ7A6U-Ba6Fs17y.js} +1 -1
- package/web/assets/{ishikawaDiagram-FXEZZL3T-B2CCGlAm.js → ishikawaDiagram-FXEZZL3T-D3BOtFjy.js} +1 -1
- package/web/assets/{journeyDiagram-5HDEW3XC-CBPETzOk.js → journeyDiagram-5HDEW3XC-MbzFPQm8.js} +1 -1
- package/web/assets/{kanban-definition-HUTT4EX6-unhQ3RPE.js → kanban-definition-HUTT4EX6-CurBmwu1.js} +1 -1
- package/web/assets/{linear-DuP_pG51.js → linear-CQIr4JjI.js} +1 -1
- package/web/assets/{linked-session-header-M7WY6IGF-DwlNCJlj.js → linked-session-header-M7WY6IGF-BBgN8zTq.js} +1 -1
- package/web/assets/{mermaid.core-BlYNzkpn.js → mermaid.core-bE6Vl3bZ.js} +4 -4
- package/web/assets/{mindmap-definition-LN4V7U3C-CtKHN6O1.js → mindmap-definition-LN4V7U3C-C1a46OGU.js} +1 -1
- package/web/assets/{pegDiagram-2B236MQR-CRyz6XWY.js → pegDiagram-2B236MQR-DZxMOxek.js} +1 -1
- package/web/assets/{pieDiagram-ENE6RG2P-BXfkPm3Q.js → pieDiagram-ENE6RG2P-BN0XOKLi.js} +1 -1
- package/web/assets/{plugin-host-modules-5NQgs5Cq.js → plugin-host-modules-CaRD5_5q.js} +1 -1
- package/web/assets/{project-search-dialog-HJFGW4DG-DPly4OiB.js → project-search-dialog-HJFGW4DG-h_dHeh7g.js} +1 -1
- package/web/assets/{project-search-page-NKQEBGPF-Cc6HV7BN.js → project-search-page-NKQEBGPF-2AzjjLEC.js} +1 -1
- package/web/assets/{quadrantDiagram-ABIIQ3AL-Drb_qWex.js → quadrantDiagram-ABIIQ3AL-C6IegTP-.js} +1 -1
- package/web/assets/{railroadDiagram-RFXS5EU6-DCKk2Pix.js → railroadDiagram-RFXS5EU6-bsjyAl-q.js} +1 -1
- package/web/assets/{requirementDiagram-TGXJPOKE-6WwZwcpD.js → requirementDiagram-TGXJPOKE-Cl8EZWy7.js} +1 -1
- package/web/assets/{sankeyDiagram-HTMAVEWB-DGGk3WCE.js → sankeyDiagram-HTMAVEWB-DhK4rRux.js} +1 -1
- package/web/assets/{selection-popup-532EHNCZ-yPhTrk7L.js → selection-popup-532EHNCZ-E2iQ2gQQ.js} +1 -1
- package/web/assets/{sequenceDiagram-DBY2YBRQ-a4tGi3DH.js → sequenceDiagram-DBY2YBRQ-DQTaPSD6.js} +1 -1
- package/web/assets/{sizeCapture-X5ZJPWSS-CHehBmy9.js → sizeCapture-X5ZJPWSS-k77bZ1zg.js} +1 -1
- package/web/assets/{stateDiagram-2N3HPSRC-Bn9GSWgV.js → stateDiagram-2N3HPSRC-Cv_5WuZJ.js} +1 -1
- package/web/assets/{stateDiagram-v2-6OUMAXLB-DI7fdBd7.js → stateDiagram-v2-6OUMAXLB-DJ1Lrskh.js} +1 -1
- package/web/assets/{swimlanes-5IMT3BWC-BX0WRzfR.js → swimlanes-5IMT3BWC-gBtAjG3z.js} +2 -2
- package/web/assets/swimlanesDiagram-G3AALYLV-DF1K9voG.js +8 -0
- package/web/assets/{timeline-definition-FHXFAJF6-9Ni2kIzq.js → timeline-definition-FHXFAJF6-CFjnM1Qq.js} +1 -1
- package/web/assets/{vennDiagram-L72KCM5P-rfBSNh3U.js → vennDiagram-L72KCM5P-CeMWrThY.js} +1 -1
- package/web/assets/{wardleyDiagram-EHGQE667-CzziImCC.js → wardleyDiagram-EHGQE667-R-ZaAOAI.js} +1 -1
- package/web/assets/{xychartDiagram-FW5EYKEG-DTLI4h5R.js → xychartDiagram-FW5EYKEG-BFXoMXiN.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/channel-um92dqpm.js +0 -1
- package/web/assets/classDiagram-OUVF2IWQ-h7sFEJBg.js +0 -1
- package/web/assets/classDiagram-v2-EOCWNBFH-h7sFEJBg.js +0 -1
- package/web/assets/swimlanesDiagram-G3AALYLV-OGHC9nqi.js +0 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function copyToClipboard(text: string): Promise<void>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { accessSync, constants } from 'node:fs';
|
|
3
|
+
import { delimiter, join } from 'node:path';
|
|
4
|
+
function onPath(command) {
|
|
5
|
+
return (process.env.PATH ?? '').split(delimiter).some((directory) => {
|
|
6
|
+
if (!directory)
|
|
7
|
+
return false;
|
|
8
|
+
try {
|
|
9
|
+
accessSync(join(directory, command), constants.X_OK);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** The platform clipboard tool. No shell runs, so the text is never interpreted. */
|
|
18
|
+
function clipboardTool() {
|
|
19
|
+
if (process.platform === 'darwin')
|
|
20
|
+
return { command: 'pbcopy', args: [], encoding: 'utf8' };
|
|
21
|
+
// clip.exe reads UTF-16LE text that starts with a byte order mark.
|
|
22
|
+
if (process.platform === 'win32')
|
|
23
|
+
return { command: 'clip', args: [], encoding: 'utf16le' };
|
|
24
|
+
if (process.env.WAYLAND_DISPLAY && onPath('wl-copy'))
|
|
25
|
+
return { command: 'wl-copy', args: [], encoding: 'utf8' };
|
|
26
|
+
if (process.env.DISPLAY && onPath('xclip'))
|
|
27
|
+
return { command: 'xclip', args: ['-selection', 'clipboard'], encoding: 'utf8' };
|
|
28
|
+
if (process.env.DISPLAY && onPath('xsel'))
|
|
29
|
+
return { command: 'xsel', args: ['--clipboard', '--input'], encoding: 'utf8' };
|
|
30
|
+
throw new Error('No clipboard tool is available. Install wl-copy (Wayland) or xclip or xsel (X11) in a desktop session, or run knl llm without --copy.');
|
|
31
|
+
}
|
|
32
|
+
export function copyToClipboard(text) {
|
|
33
|
+
const tool = clipboardTool();
|
|
34
|
+
const input = tool.encoding === 'utf16le' ? Buffer.from(`\ufeff${text}`, 'utf16le') : Buffer.from(text, 'utf8');
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
// stderr is inherited: wl-copy keeps serving the selection in a forked
|
|
37
|
+
// process, and a pipe it holds open would keep this command alive.
|
|
38
|
+
const child = spawn(tool.command, tool.args, { stdio: ['pipe', 'ignore', 'inherit'] });
|
|
39
|
+
const timer = setTimeout(() => {
|
|
40
|
+
child.kill('SIGKILL');
|
|
41
|
+
reject(new Error(`${tool.command} did not finish within 10 seconds`));
|
|
42
|
+
}, 10_000);
|
|
43
|
+
child.once('error', (error) => {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
reject(new Error(`Cannot run ${tool.command}: ${error.message}`));
|
|
46
|
+
});
|
|
47
|
+
child.once('exit', (code, signal) => {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
if (code === 0)
|
|
50
|
+
resolve();
|
|
51
|
+
else
|
|
52
|
+
reject(new Error(`${tool.command} failed ${signal ? `with signal ${signal}` : `with exit code ${code}`}`));
|
|
53
|
+
});
|
|
54
|
+
child.stdin.on('error', () => {
|
|
55
|
+
// The exit status reports the failure.
|
|
56
|
+
});
|
|
57
|
+
child.stdin.end(input);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function llm(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { fork } from 'node:child_process';
|
|
2
|
+
import { realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
const TIMEOUT_MS = 60_000;
|
|
6
|
+
const LLM_USAGE = `Usage:
|
|
7
|
+
knl llm [folder] [--profile <name>|--no-instructions] [--no-plugins] [--copy]
|
|
8
|
+
knl llm --folder <path> [options]
|
|
9
|
+
knl llm full [--folder <path>] [options]
|
|
10
|
+
knl llm [--folder <path>] --depth <count> [options]
|
|
11
|
+
knl llm --help
|
|
12
|
+
|
|
13
|
+
Prints only applicable AGENTS.md, personal instructions, and the research
|
|
14
|
+
write destination. The folder defaults to the current directory. Startup
|
|
15
|
+
reads registered workspace configs without scanning notes or the project catalog.
|
|
16
|
+
Folders inside the configured Raggle base repository (rgl base) omit the
|
|
17
|
+
generated write destination and linked repository context. Elsewhere, the
|
|
18
|
+
destination is an explicit Raggle notes link when one exists; otherwise,
|
|
19
|
+
with repositoryNotes enabled in the Kennel user config, it is the repository
|
|
20
|
+
notes folder (kennel notes path), looked up read-only: startup registers nothing.
|
|
21
|
+
|
|
22
|
+
full Add folder details and plugin sections; outside the base,
|
|
23
|
+
also add all links and local subpaths.
|
|
24
|
+
--depth <count> Add links and plugin sections, limited to a positive graph depth.
|
|
25
|
+
--profile <name> Use this personal instruction profile.
|
|
26
|
+
--no-instructions Omit personal profiles, all inlined instruction files, and the
|
|
27
|
+
generated write destination. Expanded modes can still include
|
|
28
|
+
graph paths and plugin context.
|
|
29
|
+
--no-plugins Omit plugins in expanded modes. Startup never loads plugins.
|
|
30
|
+
--copy Copy the message to the clipboard instead of printing it.
|
|
31
|
+
|
|
32
|
+
Use --folder ./full for a folder named full. Use knl llm full for plugin
|
|
33
|
+
context from the pinned build; refresh it with \`kennel plugin add <source>\`.
|
|
34
|
+
`;
|
|
35
|
+
function usageError(message) {
|
|
36
|
+
throw new Error(`${message}\nRun knl llm --help for usage.`);
|
|
37
|
+
}
|
|
38
|
+
/** Strict argument parser: unknown, repeated, or conflicting input fails. */
|
|
39
|
+
function parseLlmArgs(args) {
|
|
40
|
+
const options = { help: false, includeInstructions: true, includePlugins: true, copy: false };
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
const once = (name) => {
|
|
43
|
+
if (seen.has(name))
|
|
44
|
+
usageError(`Use ${name} once`);
|
|
45
|
+
seen.add(name);
|
|
46
|
+
};
|
|
47
|
+
const positionals = [];
|
|
48
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
49
|
+
const arg = args[index];
|
|
50
|
+
const equals = arg.startsWith('--') ? arg.indexOf('=') : -1;
|
|
51
|
+
const name = equals === -1 ? arg : arg.slice(0, equals);
|
|
52
|
+
const inline = equals === -1 ? undefined : arg.slice(equals + 1);
|
|
53
|
+
const value = (label) => {
|
|
54
|
+
const next = inline ?? args[++index];
|
|
55
|
+
if (next === undefined || next === '' || (inline === undefined && next.startsWith('-'))) {
|
|
56
|
+
usageError(`${name} requires ${label}`);
|
|
57
|
+
}
|
|
58
|
+
return next;
|
|
59
|
+
};
|
|
60
|
+
const flag = () => {
|
|
61
|
+
if (inline !== undefined)
|
|
62
|
+
usageError(`${name} does not take a value`);
|
|
63
|
+
once(name);
|
|
64
|
+
};
|
|
65
|
+
if (name === '-h' || name === '--help') {
|
|
66
|
+
options.help = true;
|
|
67
|
+
}
|
|
68
|
+
else if (name === '--folder') {
|
|
69
|
+
once(name);
|
|
70
|
+
options.folder = value('a folder path');
|
|
71
|
+
}
|
|
72
|
+
else if (name === '--depth') {
|
|
73
|
+
once(name);
|
|
74
|
+
const count = value('a positive count');
|
|
75
|
+
if (!/^[1-9][0-9]{0,8}$/u.test(count))
|
|
76
|
+
usageError(`--depth must be a positive integer: ${count}`);
|
|
77
|
+
options.depth = Number(count);
|
|
78
|
+
}
|
|
79
|
+
else if (name === '--profile') {
|
|
80
|
+
once(name);
|
|
81
|
+
options.profile = value('a profile name');
|
|
82
|
+
}
|
|
83
|
+
else if (name === '--no-instructions') {
|
|
84
|
+
flag();
|
|
85
|
+
options.includeInstructions = false;
|
|
86
|
+
}
|
|
87
|
+
else if (name === '--no-plugins') {
|
|
88
|
+
flag();
|
|
89
|
+
options.includePlugins = false;
|
|
90
|
+
}
|
|
91
|
+
else if (name === '--copy') {
|
|
92
|
+
flag();
|
|
93
|
+
options.copy = true;
|
|
94
|
+
}
|
|
95
|
+
else if (arg.startsWith('-')) {
|
|
96
|
+
usageError(`Unknown option: ${arg}`);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
positionals.push(arg);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (options.help)
|
|
103
|
+
return options;
|
|
104
|
+
if (positionals[0] === 'full') {
|
|
105
|
+
positionals.shift();
|
|
106
|
+
if (options.depth !== undefined)
|
|
107
|
+
usageError('Use either full or --depth, not both');
|
|
108
|
+
options.depth = 'full';
|
|
109
|
+
}
|
|
110
|
+
if (positionals.length > 1)
|
|
111
|
+
usageError(`Unexpected argument: ${positionals[1]}`);
|
|
112
|
+
if (positionals.length === 1) {
|
|
113
|
+
if (options.folder !== undefined)
|
|
114
|
+
usageError('Give the folder once: as an argument or with --folder');
|
|
115
|
+
options.folder = positionals[0];
|
|
116
|
+
}
|
|
117
|
+
if (options.profile !== undefined && !options.includeInstructions) {
|
|
118
|
+
usageError('--profile cannot be combined with --no-instructions');
|
|
119
|
+
}
|
|
120
|
+
return options;
|
|
121
|
+
}
|
|
122
|
+
function resolveFolder(input) {
|
|
123
|
+
let folder;
|
|
124
|
+
try {
|
|
125
|
+
folder = realpathSync(resolve(input));
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
throw new Error(`Folder does not exist: ${input}`);
|
|
129
|
+
}
|
|
130
|
+
if (!statSync(folder).isDirectory())
|
|
131
|
+
throw new Error(`Not a folder: ${input}. knl llm needs a folder.`);
|
|
132
|
+
return folder;
|
|
133
|
+
}
|
|
134
|
+
class Interrupted extends Error {
|
|
135
|
+
}
|
|
136
|
+
/** Runs one bounded generation in a child process and returns its message. */
|
|
137
|
+
function generate(request) {
|
|
138
|
+
return new Promise((resolvePromise, reject) => {
|
|
139
|
+
const child = fork(fileURLToPath(new URL('./worker.js', import.meta.url)), [JSON.stringify(request)], {
|
|
140
|
+
env: { ...process.env, NODE_OPTIONS: '' },
|
|
141
|
+
execArgv: [],
|
|
142
|
+
// Optional catalog/plugin logs are not startup output. Failures arrive over IPC.
|
|
143
|
+
stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
|
|
144
|
+
});
|
|
145
|
+
let stage = 'context generation';
|
|
146
|
+
let settled = false;
|
|
147
|
+
const finish = (error, text) => {
|
|
148
|
+
if (settled)
|
|
149
|
+
return;
|
|
150
|
+
settled = true;
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
process.off('SIGINT', interrupt);
|
|
153
|
+
process.off('SIGTERM', interrupt);
|
|
154
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
155
|
+
child.kill('SIGKILL');
|
|
156
|
+
if (error)
|
|
157
|
+
reject(error);
|
|
158
|
+
else
|
|
159
|
+
resolvePromise(text);
|
|
160
|
+
};
|
|
161
|
+
const interrupt = () => finish(new Interrupted('Cancelled. No context was printed or copied.'));
|
|
162
|
+
const timer = setTimeout(() => {
|
|
163
|
+
finish(new Error(`Context generation timed out after ${TIMEOUT_MS / 1000} seconds during ${stage}. No context was printed or copied.`));
|
|
164
|
+
}, TIMEOUT_MS);
|
|
165
|
+
process.once('SIGINT', interrupt);
|
|
166
|
+
process.once('SIGTERM', interrupt);
|
|
167
|
+
child.on('message', (message) => {
|
|
168
|
+
if (message?.type === 'stage' && typeof message.stage === 'string') {
|
|
169
|
+
stage = message.stage;
|
|
170
|
+
}
|
|
171
|
+
else if (message?.type === 'result' && message.ok && typeof message.text === 'string') {
|
|
172
|
+
finish(undefined, message.text);
|
|
173
|
+
}
|
|
174
|
+
else if (message?.type === 'result' && !message.ok) {
|
|
175
|
+
finish(new Error(String(message.error)));
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
finish(new Error('The context worker sent an invalid message'));
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
child.once('error', (error) => finish(new Error(`Cannot start the context worker: ${error.message}`)));
|
|
182
|
+
child.once('exit', (code, signal) => {
|
|
183
|
+
// A terminal Ctrl+C reaches the worker too, sometimes before this process.
|
|
184
|
+
if (signal === 'SIGINT' || signal === 'SIGTERM')
|
|
185
|
+
return interrupt();
|
|
186
|
+
finish(new Error(`The context worker stopped during ${stage} (${signal ? `signal ${signal}` : `exit code ${code}`}) without a result`));
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
export async function llm(args) {
|
|
191
|
+
const options = parseLlmArgs(args);
|
|
192
|
+
if (options.help) {
|
|
193
|
+
process.stdout.write(LLM_USAGE);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const folder = resolveFolder(options.folder ?? '.');
|
|
197
|
+
let text;
|
|
198
|
+
try {
|
|
199
|
+
text = await generate({
|
|
200
|
+
folder,
|
|
201
|
+
...(options.depth !== undefined ? { depth: options.depth } : {}),
|
|
202
|
+
...(options.profile !== undefined ? { profile: options.profile } : {}),
|
|
203
|
+
includeInstructions: options.includeInstructions,
|
|
204
|
+
includePlugins: options.includePlugins,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
209
|
+
process.exitCode = error instanceof Interrupted ? 130 : 1;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (options.copy) {
|
|
213
|
+
const { copyToClipboard } = await import('./clipboard.js');
|
|
214
|
+
try {
|
|
215
|
+
await copyToClipboard(text);
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
219
|
+
process.exitCode = 1;
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
console.error(`Copied the LLM context for ${folder} to the clipboard (${Buffer.byteLength(text, 'utf8')} bytes).`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
process.stdout.write(text);
|
|
226
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type WorkerRequest = {
|
|
2
|
+
folder: string;
|
|
3
|
+
depth?: number | 'full';
|
|
4
|
+
profile?: string;
|
|
5
|
+
includeInstructions: boolean;
|
|
6
|
+
includePlugins: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type WorkerMessage = {
|
|
9
|
+
type: 'stage';
|
|
10
|
+
stage: string;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'result';
|
|
13
|
+
ok: true;
|
|
14
|
+
text: string;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'result';
|
|
17
|
+
ok: false;
|
|
18
|
+
error: string;
|
|
19
|
+
};
|