@oh-my-pi/pi-utils 18.4.9 → 18.4.11
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/CHANGELOG.md +27 -0
- package/dist/types/dirs.d.ts +5 -1
- package/dist/types/marked/core.d.ts +24 -0
- package/dist/types/marked/inline-closers.d.ts +119 -0
- package/dist/types/marked/inline-text.d.ts +70 -0
- package/dist/types/math-delimiters.d.ts +22 -0
- package/package.json +2 -2
- package/src/dirs.ts +68 -9
- package/src/marked/core.ts +234 -166
- package/src/marked/inline-closers.ts +434 -0
- package/src/marked/inline-text.ts +321 -0
- package/src/math-delimiters.ts +121 -19
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.4.11] - 2026-10-02
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added XDG-aware utility paths for skill descriptions and prediction state, with automatic adoption of legacy data when XDG locations are first resolved.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed machine-global daemon runtime paths so brokers such as text prediction use the shared XDG state location across profiles and custom agent directories.
|
|
14
|
+
|
|
15
|
+
## [18.4.10] - 2026-10-02
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Added `startFrom(src, from)` to inline Markdown tokenizer extensions: a start hint that returns the first match at or after `from` (or `undefined`), so long paragraphs stay linear ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
20
|
+
- Added `this.source` and `this.end` for inline Markdown tokenizer extensions: the whole inline source and where the text being lexed ends in it, with one `this` per source that the link labels and emphasis inside it share, so a tokenizer can remember what it already scanned ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
21
|
+
- Added `mathSpanInContext` and `MathSpans` to `math-delimiters`, which find math spans without rescanning a run of unclosed openers ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Fixed long Markdown paragraphs lexing slowly: a 44 KB paragraph with no blank line now parses in about 3 ms instead of 100 ms ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
26
|
+
- Fixed Markdown paragraphs with many unclosed `[`, `*` or `_`, or with a long address-like word and no dotted domain, lexing slowly: a 40 KB paragraph of each now lexes in 4-24 ms instead of 2-12 s ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
27
|
+
- Fixed Markdown paragraphs of deeply nested emphasis, links or images lexing slowly: 32 KB now lexes in about 50 ms instead of 7 s, and a long word inside every level no longer costs its length once per level, except in nested image labels that hold a backslash escape ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
28
|
+
- Fixed Markdown paragraphs with long or many unclosed runs of backticks, or `<http://` autolinks with no space or `>` after them, lexing slowly: 80 KB of each now lexes in about 50-60 ms instead of seconds (40 KB of one unclosed run took 10 s) ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
29
|
+
- Fixed Markdown paragraphs of nested brackets, URLs with long trailing punctuation, or unclosed HTML tags or comments lexing slowly: 80 KB of each now lexes in under 40 ms instead of 4-30 s ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
30
|
+
- Fixed deeply nested Markdown links and emphasis overflowing the stack early: in a fresh process links now nest about three times as deep before a stack overflow, and emphasis twice as deep ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
31
|
+
|
|
5
32
|
## [18.4.9] - 2026-10-01
|
|
6
33
|
|
|
7
34
|
### Added
|
package/dist/types/dirs.d.ts
CHANGED
|
@@ -277,6 +277,10 @@ export declare function getTinyModelsCacheDir(agentDir?: string): string;
|
|
|
277
277
|
export declare function getDocumentConversionCacheDir(agentDir?: string): string;
|
|
278
278
|
/** Get the composer speculative cache database (~/.omp/agent/cache/composer.db; XDG default: $XDG_CACHE_HOME/omp/cache/composer.db). */
|
|
279
279
|
export declare function getComposerCacheDbPath(agentDir?: string): string;
|
|
280
|
+
/** Get the skill descriptions database (~/.omp/agent/skill-descriptions.db; XDG default: $XDG_DATA_HOME/omp/skill-descriptions.db). */
|
|
281
|
+
export declare function getSkillDescriptionsDbPath(agentDir?: string): string;
|
|
282
|
+
/** Get the text-predict engine state directory (~/.omp/agent/predict/<method>; XDG default: $XDG_DATA_HOME/omp/predict/<method>). Adopts legacy engine state on first XDG resolution. */
|
|
283
|
+
export declare function getPredictStateDir(agentDir: string | undefined, method: string): string;
|
|
280
284
|
/** Get the sessions directory (~/.omp/agent/sessions). */
|
|
281
285
|
export declare function getSessionsDir(agentDir?: string): string;
|
|
282
286
|
/** Get the content-addressed blob store directory (~/.omp/agent/blobs). */
|
|
@@ -314,7 +318,7 @@ export declare function getTinyWorkerRuntimeDir(): string;
|
|
|
314
318
|
export declare function getDaemonRuntimeRoot(): string;
|
|
315
319
|
/** Get the daemon runtime directory for a project (~/.omp/run/daemons/<hash>; XDG default: $XDG_STATE_HOME/omp/run/daemons/<hash>). */
|
|
316
320
|
export declare function getDaemonRuntimeDir(projectDir: string): string;
|
|
317
|
-
/** Root directory containing every machine-global daemon service scope. */
|
|
321
|
+
/** Root directory containing every machine-global daemon service scope (~/.omp/run/daemons/global; XDG default: $XDG_STATE_HOME/omp/run/daemons/global). Shared across profiles. */
|
|
318
322
|
export declare function getGlobalDaemonRuntimeRoot(): string;
|
|
319
323
|
/** Get a profile-independent runtime directory for a machine-global daemon service. */
|
|
320
324
|
export declare function getGlobalDaemonRuntimeDir(service: string): string;
|
|
@@ -184,16 +184,40 @@ export type TokensList = Token[] & {
|
|
|
184
184
|
/** Context supplied to extension tokenizers. */
|
|
185
185
|
export interface TokenizerThis {
|
|
186
186
|
lexer: Lexer;
|
|
187
|
+
/**
|
|
188
|
+
* For an inline tokenizer, the whole inline source, such as a paragraph's text. The `src` it receives is the part
|
|
189
|
+
* of it before `end` that starts at `end - src.length`: inside a link label or the text of emphasis, `end` is where
|
|
190
|
+
* that text ends, right before its closer, and a token there must not reach past it. Inline tokenizers get one
|
|
191
|
+
* context object per inline source, the same for every call while the lexer works through it and the labels and
|
|
192
|
+
* emphasis inside it, so a tokenizer can keep state for it in a `WeakMap`.
|
|
193
|
+
*/
|
|
194
|
+
source?: string;
|
|
195
|
+
/** For an inline tokenizer, where the text the lexer works through ends in `source` (see `source`). */
|
|
196
|
+
end?: number;
|
|
187
197
|
}
|
|
188
198
|
/** A tokenizer extension callback. */
|
|
189
199
|
export type TokenizerExtensionFunction = (this: TokenizerThis, src: string, tokens: Token[] | TokensList) => Tokens.Generic | undefined;
|
|
190
200
|
/** A tokenizer extension start hint. */
|
|
191
201
|
export type TokenizerStartFunction = (this: TokenizerThis, src: string) => number | void;
|
|
202
|
+
/**
|
|
203
|
+
* An offset form of a start hint: the first index at or after `from` in `src` where the extension's tokenizer could
|
|
204
|
+
* match, or `undefined` (not -1) when there is none; anything else below `from` throws. The test at each index must
|
|
205
|
+
* read `src` only from that index on, so the answer for `from` is also the answer for every later offset up to it,
|
|
206
|
+
* and must still pass where `src` goes on past its end: for a link label or the text of emphasis, the lexer asks on
|
|
207
|
+
* the inline source it lies in, then asks again on the label or text at the index it got, if that lies inside.
|
|
208
|
+
*/
|
|
209
|
+
export type TokenizerStartFromFunction = (this: TokenizerThis, src: string, from: number) => number | undefined;
|
|
192
210
|
/** An inline or block tokenizer extension. */
|
|
193
211
|
export interface TokenizerExtension {
|
|
194
212
|
name: string;
|
|
195
213
|
level: "block" | "inline";
|
|
196
214
|
start?: TokenizerStartFunction;
|
|
215
|
+
/**
|
|
216
|
+
* Replaces `start` for inline text when present. Inline lexing calls it on the whole inline source and reuses
|
|
217
|
+
* the answer until lexing passes it, inside link labels and emphasis too, instead of calling `start` on the rest
|
|
218
|
+
* of the source at every text step, so a hint that stops at its answer keeps a long paragraph linear.
|
|
219
|
+
*/
|
|
220
|
+
startFrom?: TokenizerStartFromFunction;
|
|
197
221
|
tokenizer: TokenizerExtensionFunction;
|
|
198
222
|
childTokens?: string[];
|
|
199
223
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where brackets, emphasis, code spans, autolinks and inline HTML close in inline Markdown. The indexes are built
|
|
3
|
+
* over one root inline source, each on first use, and shared by every source lexed inside it (a link label, the text
|
|
4
|
+
* of emphasis), so lexing nested sources builds no index of its own and keeps none alive per nesting level.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PUNCTUATION: RegExp;
|
|
7
|
+
/** Whether an odd run of backslashes right before `index` escapes the character there. */
|
|
8
|
+
export declare function escapedAt(src: string, index: number): boolean;
|
|
9
|
+
export declare function canOpenDelimiter(src: string, index: number, width: number, marker: string, previous?: string): boolean;
|
|
10
|
+
export declare function canCloseDelimiter(src: string, index: number, marker: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Where a bracket opened in the root source closes. A search starts right after the opening bracket, and a bracket
|
|
13
|
+
* never begins an escape, so the escape pairing from there is the pairing from the start of the root source. The
|
|
14
|
+
* closer is then the first unescaped closing bracket at or after the start whose depth (openers minus closers
|
|
15
|
+
* before it, from the start of the root source) equals the depth at the start.
|
|
16
|
+
*/
|
|
17
|
+
declare class BracketDepths {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(src: string, open: number, close: number);
|
|
20
|
+
/** The offset of the bracket closing the one just before `start`, or -1. */
|
|
21
|
+
closeAfter(start: number): number;
|
|
22
|
+
/** Whether an unescaped closing bracket lies at or after `from`, right after a bracket, and before `to`. */
|
|
23
|
+
closesWithin(from: number, to: number): boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The delimiters the emphasis rule's closer walk visits for one marker and width in the root source, typed the way
|
|
27
|
+
* the walk types them: a delimiter that can close is a closer, else one that can open is an opener. For width 1 they
|
|
28
|
+
* are every unescaped marker. For width 2 they are marker pairs taken two at a time from the start of each run of
|
|
29
|
+
* markers (after it when an odd run of backslashes escapes the start), which is where a walk that reaches the run
|
|
30
|
+
* from before it enters. The closer for an opener is then the first closer after it whose depth (openers minus
|
|
31
|
+
* closers before it) equals the depth where its walk begins, the first point where the walk's nesting count would
|
|
32
|
+
* drop below zero.
|
|
33
|
+
*/
|
|
34
|
+
declare class EmphasisDelimiters {
|
|
35
|
+
#private;
|
|
36
|
+
constructor(src: string, marker: string, width: number);
|
|
37
|
+
/**
|
|
38
|
+
* Where the emphasis opened by the delimiter at `opener` closes in a source that ends at `end`, or -1 when the
|
|
39
|
+
* closer the walk meets first does not lie wholly before `end`. The walk reads nothing before its start. Past
|
|
40
|
+
* `end` it reads only for the source's last delimiter, which cannot open there and closes as it would at the end
|
|
41
|
+
* of the source: the sources inline lexing nests end right before a marker or "]", and the closing test reads
|
|
42
|
+
* either punctuation there like the end.
|
|
43
|
+
*/
|
|
44
|
+
closeFor(opener: number, end: number): number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The runs of backticks in the root source, for the code span rule. A span opened by `width` backticks closes at the
|
|
48
|
+
* first `width` backticks after its opening run that no backslash escapes: the start of a later run of at least
|
|
49
|
+
* `width`, or, in a run whose first backtick a backslash escapes, the backticks from its `width`th on, since the
|
|
50
|
+
* search for a closer goes on `width` past an escaped one.
|
|
51
|
+
*/
|
|
52
|
+
declare class CodeRuns {
|
|
53
|
+
#private;
|
|
54
|
+
constructor(src: string);
|
|
55
|
+
/** Where the run of backticks holding the backtick at `at` ends. */
|
|
56
|
+
endOf(at: number): number;
|
|
57
|
+
/** Where the closer of a code span opened by `width` backticks that end at `from` starts, or -1. */
|
|
58
|
+
closerAfter(from: number, width: number): number;
|
|
59
|
+
}
|
|
60
|
+
/** The closer indexes of one root inline source, each built on first use. */
|
|
61
|
+
export declare class CloserIndexes {
|
|
62
|
+
#private;
|
|
63
|
+
constructor(src: string);
|
|
64
|
+
get square(): BracketDepths;
|
|
65
|
+
get round(): BracketDepths;
|
|
66
|
+
emphasis(marker: string, width: number): EmphasisDelimiters;
|
|
67
|
+
/**
|
|
68
|
+
* For each offset, where the HTML rule's scan for a tag's end from there stops: the first ">" outside quotes,
|
|
69
|
+
* where a quote runs to the next quote of its kind, or -1 at the end of the source.
|
|
70
|
+
*/
|
|
71
|
+
get tagEnds(): Int32Array;
|
|
72
|
+
/** Every offset where "-->" starts, ascending. */
|
|
73
|
+
get commentEnds(): number[];
|
|
74
|
+
get codeRuns(): CodeRuns;
|
|
75
|
+
/**
|
|
76
|
+
* The first " " or ">" at or after `from`, or `Infinity`. Asked at offsets that never decrease, as the lexer's
|
|
77
|
+
* position does, so each search starts past the last one's answer.
|
|
78
|
+
*/
|
|
79
|
+
spaceOrAngleFrom(from: number): number;
|
|
80
|
+
/** Every offset of a backslash before ASCII punctuation, ascending: where unescaping drops a backslash. */
|
|
81
|
+
get escapes(): number[];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The link, emphasis, code span, autolink and inline HTML rules' closer lookups for one inline source, which is the
|
|
85
|
+
* root source of `indexes` or a part of it that ends at `end`. Offsets are relative to `rest`, a suffix of the
|
|
86
|
+
* source. A closer the root source places at or past `end` does not exist in the source.
|
|
87
|
+
*/
|
|
88
|
+
export declare class InlineClosers {
|
|
89
|
+
#private;
|
|
90
|
+
/** Where the source ends in the root source. */
|
|
91
|
+
readonly end: number;
|
|
92
|
+
constructor(indexes: CloserIndexes, end: number);
|
|
93
|
+
/** The closers of a part of `rest` that ends at `end`, lexed as its own source. */
|
|
94
|
+
nested(rest: string, end: number): InlineClosers;
|
|
95
|
+
/** Where the "[" just before `start` closes, or -1. */
|
|
96
|
+
closeSquare(rest: string, start: number): number;
|
|
97
|
+
/** Where the "(" just before `start` closes, or -1. */
|
|
98
|
+
closeRound(rest: string, start: number): number;
|
|
99
|
+
/** Whether `rest.slice(from, to)`, which starts right after a "[", holds an unescaped "]". */
|
|
100
|
+
squareCloserWithin(rest: string, from: number, to: number): boolean;
|
|
101
|
+
/** Whether `rest.slice(from, to)` holds a backslash before ASCII punctuation, which unescaping drops. */
|
|
102
|
+
escapeWithin(rest: string, from: number, to: number): boolean;
|
|
103
|
+
/** Where the emphasis opened by the `width` markers at the start of `rest` closes, or -1. */
|
|
104
|
+
closeEmphasis(rest: string, marker: string, width: number): number;
|
|
105
|
+
/** How many backticks start `rest`, which starts with one. */
|
|
106
|
+
codeOpenerWidth(rest: string): number;
|
|
107
|
+
/** Where the code span opened by the `width` backticks at the start of `rest` closes, or -1. */
|
|
108
|
+
closeCode(rest: string, width: number): number;
|
|
109
|
+
/**
|
|
110
|
+
* Where the URL autolink opened at the start of `rest` ends, its scheme ending at `from`: the first " " or ">"
|
|
111
|
+
* after the scheme if it is a ">" with text before it, or -1. One past the end of `rest` reads as no ">".
|
|
112
|
+
*/
|
|
113
|
+
closeUrlAutolink(rest: string, from: number): number;
|
|
114
|
+
/** Where the HTML tag opened by the "<" at the start of `rest` ends: the first ">" after it outside quotes, or -1. */
|
|
115
|
+
closeTag(rest: string): number;
|
|
116
|
+
/** Where the first "-->" after the "<!--" at the start of `rest` starts, or -1. */
|
|
117
|
+
closeComment(rest: string): number;
|
|
118
|
+
}
|
|
119
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plain-text step of inline lexing over one inline source: where text can end ({@link TextStops}, which also
|
|
3
|
+
* tells the bare-URL rule where it can match) and how it is appended ({@link TextRun}).
|
|
4
|
+
*/
|
|
5
|
+
import type { Lexer, Token, TokenizerExtension, TokenizerStartFromFunction } from "./core.js";
|
|
6
|
+
/**
|
|
7
|
+
* The places where plain text can end in one root inline source, as offsets into it, shared by every source lexed
|
|
8
|
+
* inside it (a link label, the text of emphasis). Each stop is the first offset at or after a search offset where a
|
|
9
|
+
* test passes, and each test reads the source only from its own offset on, so a found stop stays the answer for every
|
|
10
|
+
* later search offset up to it. A stop is searched for again only after lexing passes it, starting past it, so all
|
|
11
|
+
* the text steps of a paragraph, at every nesting level, read it a bounded number of times instead of once per step.
|
|
12
|
+
* Search offsets never decrease: the lexer asks at its position for the bare-URL rule and one past it for the text
|
|
13
|
+
* step, its position only grows, and a nested source is lexed from its start to its end before the lexer goes on
|
|
14
|
+
* past it.
|
|
15
|
+
*
|
|
16
|
+
* A nested source is the part of the root source before `end`, and a stop that holds there is a stop of the root
|
|
17
|
+
* source whose match ends by `end`. The first root stop after an offset tells whether there is one: when its match
|
|
18
|
+
* runs past `end`, no stop that ends by `end` follows it, for the reason each search gives.
|
|
19
|
+
*/
|
|
20
|
+
declare class RootTextStops {
|
|
21
|
+
#private;
|
|
22
|
+
constructor(src: string);
|
|
23
|
+
/** The first character that can start another token at or after `from`. */
|
|
24
|
+
tokenCharFrom(from: number): number;
|
|
25
|
+
/** The first bare-URL scheme at or after `from` that ends by `end`. */
|
|
26
|
+
schemeFrom(from: number, end: number): number;
|
|
27
|
+
/** The "@" of the run {@link mailFrom} last found. */
|
|
28
|
+
get mailAt(): number;
|
|
29
|
+
/** The first offset at or after `from` that starts a match of `/[A-Za-z0-9._+-]+@/` whose "@" is before `end`. */
|
|
30
|
+
mailFrom(from: number, end: number): number;
|
|
31
|
+
/** The first offset at or after `from` that starts a match of `/(?: {2,}|\\)\n/` whose "\n" is before `end`. */
|
|
32
|
+
hardBreakFrom(from: number, end: number): number;
|
|
33
|
+
/** Where `extension`'s `startFrom` hint points at or after `from`, asked again only once lexing passes it. */
|
|
34
|
+
startFrom(extension: TokenizerExtension, startFrom: TokenizerStartFromFunction, lexer: Lexer, from: number): number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The places where one inline source's plain text can end, as offsets into that source: its root source's stops
|
|
38
|
+
* that hold before the source's end. The source is a root source, or a part of one lexed as its own source (a link
|
|
39
|
+
* label, the text of emphasis), which ends right before the delimiter that closes it.
|
|
40
|
+
*/
|
|
41
|
+
export declare class TextStops {
|
|
42
|
+
#private;
|
|
43
|
+
/** The stops of `src`, a root source, or else the part of `root`'s source that ends at `end`. */
|
|
44
|
+
constructor(src: string, lexer: Lexer, root?: RootTextStops, end?: number);
|
|
45
|
+
/** The stops of `src`, the part of this source's root source that ends at `end`, lexed as its own source. */
|
|
46
|
+
nested(src: string, end: number): TextStops;
|
|
47
|
+
/**
|
|
48
|
+
* Whether the bare-URL rule can match at `pos`: its first alternative needs a scheme there, and its second a
|
|
49
|
+
* run of e-mail local characters from `pos` to an "@" (the only "@" such a run can reach) with a dotted domain
|
|
50
|
+
* right after that "@", which is the same test for every offset of the run.
|
|
51
|
+
*/
|
|
52
|
+
bareUrlCanStart(pos: number): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Length of the plain text at the start of `rest`, a suffix of the source: the distance to the nearest later
|
|
55
|
+
* offset where another token could start or an inline extension's start hint points.
|
|
56
|
+
*/
|
|
57
|
+
textLength(rest: string): number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Appends plain text to one inline source's token list, merging into a text token before it as marked does. A text
|
|
61
|
+
* token this run created grows by taking a longer slice of the source instead of by concatenation, so its raw stays
|
|
62
|
+
* a flat string: reading its last character (the emphasis rule's look-behind) does not copy the whole token.
|
|
63
|
+
*/
|
|
64
|
+
export declare class TextRun {
|
|
65
|
+
#private;
|
|
66
|
+
constructor(src: string);
|
|
67
|
+
/** Appends the source text from `start` to `end`. */
|
|
68
|
+
append(tokens: Token[], start: number, end: number): void;
|
|
69
|
+
}
|
|
70
|
+
export {};
|
|
@@ -41,5 +41,27 @@ export declare function mathOpenerAt(source: string, at: number): MathOpener | u
|
|
|
41
41
|
* though a backslash precedes it.
|
|
42
42
|
*/
|
|
43
43
|
export declare function mathSpanAt(source: string, at: number, from?: number): MathSpan | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The math spans of one source, found as {@link mathSpanAt} finds them with the escape scan stopping at the opener
|
|
46
|
+
* (the caller has consumed the escapes before it). A closer scan also answers for the openers of its kind that it
|
|
47
|
+
* passed, which meet the same closer or none, so asking at every opener of a long run reads the run once.
|
|
48
|
+
*/
|
|
49
|
+
export declare class MathSpans {
|
|
50
|
+
#private;
|
|
51
|
+
constructor(source: string);
|
|
52
|
+
/** The span opened at `at` that ends by `end`, or `undefined`. */
|
|
53
|
+
spanAt(at: number, end?: number): MathSpan | undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The span opened at the start of `src`, with `end` counted from there, for a marked inline tokenizer: `src` is the
|
|
57
|
+
* part of `context.source` before `context.end` that the tokenizer received, and the scans are remembered per
|
|
58
|
+
* context, which marked keeps for one inline source and the link labels and emphasis inside it. Only a span that ends
|
|
59
|
+
* by `context.end` counts. Inside a label or emphasis, `context.end` holds its closer (`]`, `*` or `_`), which is not
|
|
60
|
+
* a digit, so a `$` right before it closes there as it would at the end of the text.
|
|
61
|
+
*/
|
|
62
|
+
export declare function mathSpanInContext(context: {
|
|
63
|
+
source?: string;
|
|
64
|
+
end?: number;
|
|
65
|
+
}, src: string): MathSpan | undefined;
|
|
44
66
|
/** The own-line display block starting at offset 0, or `undefined`. */
|
|
45
67
|
export declare function mathBlockAt(source: string): MathBlock | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-utils",
|
|
3
|
-
"version": "18.4.
|
|
3
|
+
"version": "18.4.11",
|
|
4
4
|
"description": "Shared utilities for pi packages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@oh-my-pi/pi-natives": "18.4.
|
|
60
|
+
"@oh-my-pi/pi-natives": "18.4.11"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/bun": "^1.3.14"
|
package/src/dirs.ts
CHANGED
|
@@ -334,6 +334,7 @@ class DirResolver {
|
|
|
334
334
|
// With XDG on Linux, they point to $XDG_*_HOME/omp/.
|
|
335
335
|
readonly #rootDirs: Record<XdgCategory, string>;
|
|
336
336
|
readonly #agentDirs: Record<XdgCategory, string>;
|
|
337
|
+
readonly #baseRootDirs: Record<XdgCategory, string>;
|
|
337
338
|
|
|
338
339
|
readonly #rootCache = new Map<string, string>();
|
|
339
340
|
readonly #agentCache = new Map<string, string>();
|
|
@@ -362,7 +363,8 @@ class DirResolver {
|
|
|
362
363
|
let xdgData: string | undefined;
|
|
363
364
|
let xdgState: string | undefined;
|
|
364
365
|
let xdgCache: string | undefined;
|
|
365
|
-
|
|
366
|
+
const xdgPlatform = process.platform === "linux" || process.platform === "darwin";
|
|
367
|
+
if (xdgPlatform && isDefault) {
|
|
366
368
|
const resolveIf = (envVar: string) => {
|
|
367
369
|
const value = process.env[envVar];
|
|
368
370
|
if (!value) return undefined;
|
|
@@ -370,14 +372,10 @@ class DirResolver {
|
|
|
370
372
|
const appRoot = path.join(value, APP_NAME);
|
|
371
373
|
if (profile) {
|
|
372
374
|
const profilePath = path.join(appRoot, "profiles", profile);
|
|
373
|
-
if (fs.existsSync(profilePath))
|
|
374
|
-
return profilePath;
|
|
375
|
-
}
|
|
375
|
+
if (fs.existsSync(profilePath)) return profilePath;
|
|
376
376
|
return undefined;
|
|
377
377
|
}
|
|
378
|
-
|
|
379
|
-
return appRoot;
|
|
380
|
-
}
|
|
378
|
+
return fs.existsSync(appRoot) ? appRoot : undefined;
|
|
381
379
|
} catch {}
|
|
382
380
|
return undefined;
|
|
383
381
|
};
|
|
@@ -386,6 +384,22 @@ class DirResolver {
|
|
|
386
384
|
xdgCache = resolveIf("XDG_CACHE_HOME");
|
|
387
385
|
}
|
|
388
386
|
|
|
387
|
+
// XDG choice for machine-global paths (daemon scopes shared by every
|
|
388
|
+
// process): keyed only on the base app root, independent of both the
|
|
389
|
+
// profile and any agent-dir override, so every omp process on the machine
|
|
390
|
+
// agrees on one location. These hold process-scoped runtime state
|
|
391
|
+
// (sockets, tokens), so there is no migration to protect.
|
|
392
|
+
const resolveBase = (envVar: string) => {
|
|
393
|
+
if (!xdgPlatform) return undefined;
|
|
394
|
+
const value = process.env[envVar];
|
|
395
|
+
if (!value) return undefined;
|
|
396
|
+
try {
|
|
397
|
+
const appRoot = path.join(value, APP_NAME);
|
|
398
|
+
return fs.existsSync(appRoot) ? appRoot : undefined;
|
|
399
|
+
} catch {}
|
|
400
|
+
return undefined;
|
|
401
|
+
};
|
|
402
|
+
|
|
389
403
|
this.#rootDirs = {
|
|
390
404
|
data: xdgData ?? this.configRoot,
|
|
391
405
|
state: xdgState ?? this.configRoot,
|
|
@@ -397,6 +411,17 @@ class DirResolver {
|
|
|
397
411
|
state: xdgState ?? this.agentDir,
|
|
398
412
|
cache: xdgCache ?? this.agentDir,
|
|
399
413
|
};
|
|
414
|
+
const baseRoot = getBaseConfigRoot();
|
|
415
|
+
this.#baseRootDirs = {
|
|
416
|
+
data: resolveBase("XDG_DATA_HOME") ?? baseRoot,
|
|
417
|
+
state: resolveBase("XDG_STATE_HOME") ?? baseRoot,
|
|
418
|
+
cache: resolveBase("XDG_CACHE_HOME") ?? baseRoot,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Profile-independent config-root subdirectory, with optional XDG override. Shared across profiles. */
|
|
423
|
+
baseRootSubdir(subdir: string, xdg?: XdgCategory): string {
|
|
424
|
+
return path.join(xdg ? this.#baseRootDirs[xdg] : getBaseConfigRoot(), subdir);
|
|
400
425
|
}
|
|
401
426
|
|
|
402
427
|
/** Config-root subdirectory, with optional XDG override. */
|
|
@@ -915,6 +940,17 @@ export function getDocumentConversionCacheDir(agentDir?: string): string {
|
|
|
915
940
|
export function getComposerCacheDbPath(agentDir?: string): string {
|
|
916
941
|
return dirs.agentSubdir(agentDir, path.join("cache", "composer.db"), "cache");
|
|
917
942
|
}
|
|
943
|
+
/** Get the skill descriptions database (~/.omp/agent/skill-descriptions.db; XDG default: $XDG_DATA_HOME/omp/skill-descriptions.db). */
|
|
944
|
+
export function getSkillDescriptionsDbPath(agentDir?: string): string {
|
|
945
|
+
return dirs.agentSubdir(agentDir, "skill-descriptions.db", "data");
|
|
946
|
+
}
|
|
947
|
+
/** Get the text-predict engine state directory (~/.omp/agent/predict/<method>; XDG default: $XDG_DATA_HOME/omp/predict/<method>). Adopts legacy engine state on first XDG resolution. */
|
|
948
|
+
export function getPredictStateDir(agentDir: string | undefined, method: string): string {
|
|
949
|
+
const subdir = path.join("predict", method);
|
|
950
|
+
const stateDir = dirs.agentSubdir(agentDir, subdir, "data");
|
|
951
|
+
adoptLegacyDir(path.join(agentDir ?? dirs.agentDir, subdir), stateDir);
|
|
952
|
+
return stateDir;
|
|
953
|
+
}
|
|
918
954
|
|
|
919
955
|
/** Get the sessions directory (~/.omp/agent/sessions). */
|
|
920
956
|
export function getSessionsDir(agentDir?: string): string {
|
|
@@ -1000,6 +1036,29 @@ function adoptLegacyFile(legacyPath: string, targetPath: string): void {
|
|
|
1000
1036
|
}
|
|
1001
1037
|
}
|
|
1002
1038
|
|
|
1039
|
+
/**
|
|
1040
|
+
* Best-effort one-time copy of a legacy directory to its redirected XDG
|
|
1041
|
+
* location, so learned state survives enabling XDG. The copy is staged next to
|
|
1042
|
+
* the target and renamed into place, so a reader never sees a partial tree and
|
|
1043
|
+
* a concurrent adopter cannot clobber a finished one. The legacy directory is
|
|
1044
|
+
* left in place for older omp versions sharing the profile.
|
|
1045
|
+
*/
|
|
1046
|
+
function adoptLegacyDir(legacyPath: string, targetPath: string): void {
|
|
1047
|
+
if (targetPath === legacyPath) return;
|
|
1048
|
+
const staging = `${targetPath}.adopt-${process.pid}`;
|
|
1049
|
+
try {
|
|
1050
|
+
if (fs.existsSync(targetPath) || !fs.statSync(legacyPath, { throwIfNoEntry: false })?.isDirectory()) return;
|
|
1051
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
1052
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
1053
|
+
fs.cpSync(legacyPath, staging, { recursive: true });
|
|
1054
|
+
fs.renameSync(staging, targetPath);
|
|
1055
|
+
} catch {
|
|
1056
|
+
// Opportunistic: a lost race or unwritable XDG dir falls back to fresh
|
|
1057
|
+
// state at the new path — the pre-adoption behavior.
|
|
1058
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1003
1062
|
/** Get the secret placeholder key path (~/.omp/agent/secret-placeholder.key; XDG default: $XDG_STATE_HOME/omp/secret-placeholder.key). Adopts a legacy key on first XDG resolution. */
|
|
1004
1063
|
export function getSecretPlaceholderKeyPath(): string {
|
|
1005
1064
|
const keyPath = dirs.agentSubdir(undefined, "secret-placeholder.key", "state");
|
|
@@ -1023,9 +1082,9 @@ export function getDaemonRuntimeDir(projectDir: string): string {
|
|
|
1023
1082
|
return path.join(getDaemonRuntimeRoot(), key);
|
|
1024
1083
|
}
|
|
1025
1084
|
|
|
1026
|
-
/** Root directory containing every machine-global daemon service scope. */
|
|
1085
|
+
/** Root directory containing every machine-global daemon service scope (~/.omp/run/daemons/global; XDG default: $XDG_STATE_HOME/omp/run/daemons/global). Shared across profiles. */
|
|
1027
1086
|
export function getGlobalDaemonRuntimeRoot(): string {
|
|
1028
|
-
return path.join(
|
|
1087
|
+
return dirs.baseRootSubdir(path.join("run", "daemons", "global"), "state");
|
|
1029
1088
|
}
|
|
1030
1089
|
|
|
1031
1090
|
/** Get a profile-independent runtime directory for a machine-global daemon service. */
|