@sparkelf/dsh-knowledge-base 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/SKILL.md +147 -0
- package/cordis.patch.yml +5 -0
- package/i18n/zh.json +10 -0
- package/lib/client.js +15976 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +5724 -0
- package/lib/types/api.d.ts +574 -0
- package/lib/types/client/KnowledgeAudit.d.ts +39 -0
- package/lib/types/client/KnowledgeContextMenu.d.ts +96 -0
- package/lib/types/client/KnowledgeCreateDialog.d.ts +134 -0
- package/lib/types/client/KnowledgeFileTree.d.ts +88 -0
- package/lib/types/client/KnowledgeHome.d.ts +79 -0
- package/lib/types/client/KnowledgeIndexing.d.ts +60 -0
- package/lib/types/client/KnowledgeListing.d.ts +160 -0
- package/lib/types/client/KnowledgeLoading.d.ts +41 -0
- package/lib/types/client/KnowledgeMenuIcon.d.ts +27 -0
- package/lib/types/client/KnowledgePanel.d.ts +58 -0
- package/lib/types/client/KnowledgePermissionOverview.d.ts +99 -0
- package/lib/types/client/KnowledgePermissions.d.ts +47 -0
- package/lib/types/client/KnowledgeReferencePicker.d.ts +65 -0
- package/lib/types/client/KnowledgeSearch.d.ts +83 -0
- package/lib/types/client/KnowledgeSectionHeader.d.ts +71 -0
- package/lib/types/client/KnowledgeSelectionBar.d.ts +45 -0
- package/lib/types/client/KnowledgeSettings.d.ts +96 -0
- package/lib/types/client/KnowledgeSettingsSection.d.ts +21 -0
- package/lib/types/client/KnowledgeShareDialog.d.ts +70 -0
- package/lib/types/client/KnowledgeTemplates.d.ts +43 -0
- package/lib/types/client/KnowledgeTrash.d.ts +62 -0
- package/lib/types/client/MenuGlyphs.d.ts +37 -0
- package/lib/types/client/departmentFolders.d.ts +57 -0
- package/lib/types/client/deployTarget.d.ts +34 -0
- package/lib/types/client/index.d.ts +27 -0
- package/lib/types/client/knowledgeMenuCopy.d.ts +23 -0
- package/lib/types/client/locales.d.ts +438 -0
- package/lib/types/client/openInSidebar.d.ts +62 -0
- package/lib/types/client/ownerLabel.d.ts +39 -0
- package/lib/types/client/permissionRows.d.ts +94 -0
- package/lib/types/client/roleSummary.d.ts +47 -0
- package/lib/types/client/saveAudit.d.ts +87 -0
- package/lib/types/client/useFavorites.d.ts +49 -0
- package/lib/types/client/usePinnedPaths.d.ts +39 -0
- package/lib/types/client/useTreeState.d.ts +46 -0
- package/lib/types/client/useViewMode.d.ts +39 -0
- package/lib/types/deploy.d.ts +209 -0
- package/lib/types/extract.d.ts +144 -0
- package/lib/types/index.d.ts +846 -0
- package/lib/types/permissions.d.ts +237 -0
- package/lib/types/protocol.d.ts +229 -0
- package/lib/types/remote.d.ts +105 -0
- package/lib/types/trash.d.ts +142 -0
- package/lib/types/viewMetadata.d.ts +20 -0
- package/lib/types/views.d.ts +923 -0
- package/package.json +109 -4
- package/skills/kb-organize/SKILL.md +365 -0
- package/skills/kb-organize/i18n/zh.json +10 -0
- package/README.md +0 -3
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each role in the grant table actually amounts to.
|
|
3
|
+
*
|
|
4
|
+
* A role is not stored anywhere: it exists because grants name it. That is a good
|
|
5
|
+
* property to inherit — a role cannot outlive its resources or appear with none —
|
|
6
|
+
* but it means "list the roles" is a question about the grants, answered by
|
|
7
|
+
* reading them. This module is that reading, kept out of the component so it can
|
|
8
|
+
* be tested without a DOM.
|
|
9
|
+
*
|
|
10
|
+
* @module @sparkelf/dsh-knowledge-base/client/roleSummary
|
|
11
|
+
*/
|
|
12
|
+
import type { GrantView } from '../api.ts';
|
|
13
|
+
/** One role as the overview shows it. */
|
|
14
|
+
export interface RoleSummary {
|
|
15
|
+
/** Role id, which is the `principalId` its grants carry. */
|
|
16
|
+
id: string;
|
|
17
|
+
/** Display name, falling back to the id when no locale entry matches. */
|
|
18
|
+
label: string;
|
|
19
|
+
/** Actions granted anywhere by this role, collapsed so a full house reads `all`. */
|
|
20
|
+
actions: string[];
|
|
21
|
+
/** Distinct paths it reaches. */
|
|
22
|
+
paths: string[];
|
|
23
|
+
/** Whether this is one of the two the product ships with. */
|
|
24
|
+
builtIn: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Collapse raw action strings into the shortest describing list.
|
|
28
|
+
*
|
|
29
|
+
* The client needs this independently of the server: the overview renders grants
|
|
30
|
+
* the server already wrote, so it summarizes what is there rather than what a
|
|
31
|
+
* request asked for.
|
|
32
|
+
*
|
|
33
|
+
* @param actions - raw action strings.
|
|
34
|
+
* @returns `['all']` when all four concrete actions are held, else the subset.
|
|
35
|
+
*/
|
|
36
|
+
export declare function summarizeRoleActions(actions: readonly string[]): string[];
|
|
37
|
+
/**
|
|
38
|
+
* Read the roles out of a grant table.
|
|
39
|
+
*
|
|
40
|
+
* Only `role` grants contribute: a user grant is not a role, and inventing one
|
|
41
|
+
* per user would flood the list with rows that mean nothing.
|
|
42
|
+
*
|
|
43
|
+
* @param grants - the grant table.
|
|
44
|
+
* @param locale - `zh` or `en`, for the built-in role names.
|
|
45
|
+
* @returns one entry per role named by a grant, built-ins first then alphabetical.
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildRoleSummaries(grants: readonly GrantView[], locale?: 'zh' | 'en'): RoleSummary[];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire the knowledge base into the sidebar editor's save events.
|
|
3
|
+
*
|
|
4
|
+
* The knowledge base does not own the editor. Documents are shown in the host's own
|
|
5
|
+
* file window, and that window saves through its own route -- so a save never passes
|
|
6
|
+
* through a knowledge-base code path, and an audit trail written only from this
|
|
7
|
+
* plugin's routes would miss every edit made in the editor.
|
|
8
|
+
*
|
|
9
|
+
* The sidebar therefore accepts two registrations, both optional to it:
|
|
10
|
+
*
|
|
11
|
+
* - A **save actor** answers "who is acting right now". The knowledge base registers
|
|
12
|
+
* one that reports the DataOps identity the Host resolved.
|
|
13
|
+
* - A **save listener** receives every successful save after the bytes landed.
|
|
14
|
+
*
|
|
15
|
+
* The knowledge base registers both. What it does with an event is deliberately
|
|
16
|
+
* duller than it could be: it posts the save to its own append-only audit route and
|
|
17
|
+
* forgets about it. Delivery is fire-and-forget, so a knowledge base that is down,
|
|
18
|
+
* slow, or misconfigured cannot turn a completed save into a failed one -- which is
|
|
19
|
+
* the whole reason the sidebar calls listeners without awaiting them.
|
|
20
|
+
*
|
|
21
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Turn an absolute path back into a knowledge-base relative path.
|
|
25
|
+
*
|
|
26
|
+
* The sidebar reports absolute paths because that is what it edits. The audit route
|
|
27
|
+
* speaks the relative paths every other knowledge-base surface uses, so the mount
|
|
28
|
+
* point is stripped here rather than sending an absolute path the route would have
|
|
29
|
+
* to guess a root for.
|
|
30
|
+
* @param absolutePath - the path the sidebar saved.
|
|
31
|
+
* @param mountPoint - the knowledge root, absolute.
|
|
32
|
+
* @returns The relative path, or undefined when the save was outside the root.
|
|
33
|
+
*/
|
|
34
|
+
export declare function toKnowledgePath(absolutePath: string, mountPoint: string): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Decide whether a save belongs to the knowledge base, and under which path.
|
|
37
|
+
*
|
|
38
|
+
* Two shapes show up in practice and both mean the same document:
|
|
39
|
+
*
|
|
40
|
+
* - The editor reports an absolute path when it edits outside a session workspace.
|
|
41
|
+
* - It reports a path relative to the session workspace (`kb/test-toc.md`) when the
|
|
42
|
+
* document was reached through the workspace, which is the ordinary case here: the
|
|
43
|
+
* knowledge view is linked into `/workspace/kb`.
|
|
44
|
+
*
|
|
45
|
+
* The knowledge root is reported by the page as its configured mount point, and the
|
|
46
|
+
* view is what the editor actually opens, so the mount point's last segment (`kb`)
|
|
47
|
+
* is the prefix that identifies knowledge-base saves in the relative shape. Matching
|
|
48
|
+
* on that segment rather than on the full absolute path is what makes the workspace
|
|
49
|
+
* route work at all.
|
|
50
|
+
* @param savedPath - the path the editor reported.
|
|
51
|
+
* @param mountPoint - the knowledge root as the page configured it.
|
|
52
|
+
* @returns The knowledge-base relative path, or undefined when the save is elsewhere.
|
|
53
|
+
*/
|
|
54
|
+
export declare function matchKnowledgeSave(savedPath: string, mountPoint: string): string | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Register the knowledge base as a save actor and a save listener.
|
|
57
|
+
*
|
|
58
|
+
* Safe to call on a host without the extension: every access is optional and an
|
|
59
|
+
* absent service simply means nothing is registered. The returned disposer removes
|
|
60
|
+
* both registrations, so a plugin unload cannot leave a listener pointing at a
|
|
61
|
+
* closed page.
|
|
62
|
+
*
|
|
63
|
+
* @param ctx - the client context the page runs in.
|
|
64
|
+
* @param mountPoint - the knowledge root, used to keep saves outside it out of the trail.
|
|
65
|
+
* @returns A disposer, or a no-op when the sidebar service is absent.
|
|
66
|
+
*/
|
|
67
|
+
export declare function registerSaveAudit(ctx: unknown): () => void;
|
|
68
|
+
/**
|
|
69
|
+
* Install the resolver that reports the knowledge root.
|
|
70
|
+
*
|
|
71
|
+
* The page is what knows the mount point (it comes from the Host's config), and the
|
|
72
|
+
* save listener needs it to tell a knowledge-base save from any other file's save.
|
|
73
|
+
* Passing a getter rather than a string keeps the registration alive across a mount
|
|
74
|
+
* point that arrives after the listener is installed.
|
|
75
|
+
* @param resolver - returns the current knowledge root, absolute.
|
|
76
|
+
*/
|
|
77
|
+
export declare function setMountPointResolver(resolver: () => string): void;
|
|
78
|
+
/** Install the resolver the plugin's apply step owns. */
|
|
79
|
+
export declare function setActorResolver(resolver: () => string | undefined): void;
|
|
80
|
+
/**
|
|
81
|
+
* Publish the DataOps identity to the save-actor registration.
|
|
82
|
+
*
|
|
83
|
+
* The page resolves it through its own status call; the registration lives at plugin
|
|
84
|
+
* scope, so the value travels through here rather than being fetched twice.
|
|
85
|
+
* @param id - the caller id DataOps reported, or undefined when none is known.
|
|
86
|
+
*/
|
|
87
|
+
export declare function publishActor(id: string | undefined): void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The favourites store the tree's star column renders.
|
|
3
|
+
*
|
|
4
|
+
* A star is a document-level preference, so it lives beside `.pinned.json` and
|
|
5
|
+
* `.kb-tree-state.json` in the caller's own view directory rather than in browser
|
|
6
|
+
* storage: a reader who stars a report on one machine expects to find it starred on
|
|
7
|
+
* the next, and a browser profile is not the unit a knowledge base follows.
|
|
8
|
+
*
|
|
9
|
+
* The write path is deliberately the same shape as `useTreeState`'s. A reader who
|
|
10
|
+
* stars a few rows in a row makes one gesture per row, and writing through
|
|
11
|
+
* immediately would put a round trip behind each click; saves are therefore
|
|
12
|
+
* coalesced behind one timer, only one is ever on the wire, and the latest state
|
|
13
|
+
* always wins. The optimistic update exists because a star is a one-click
|
|
14
|
+
* affordance on the row the reader is looking at -- waiting for a round trip
|
|
15
|
+
* before it fills in makes the control feel broken when it is merely slow.
|
|
16
|
+
*
|
|
17
|
+
* Keeping this a separate hook rather than extending `useTreeState` is a
|
|
18
|
+
* consequence of the two having different payload shapes and different failure
|
|
19
|
+
* costs: a lost expansion collapses a folder, while a lost star loses the mark the
|
|
20
|
+
* reader was just told had been saved.
|
|
21
|
+
*
|
|
22
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
23
|
+
*/
|
|
24
|
+
/** What the hook exposes to the tree. */
|
|
25
|
+
export interface FavoritesStore {
|
|
26
|
+
/** Relative paths of the starred rows, as the Host last answered. */
|
|
27
|
+
relPaths: readonly string[];
|
|
28
|
+
/** Whether one path is starred. */
|
|
29
|
+
has: (relPath: string) => boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Star or unstar one path.
|
|
32
|
+
*
|
|
33
|
+
* Idempotent in the direction asked for, like `setPinned`: asking for the state
|
|
34
|
+
* that already holds writes nothing, so a double click cannot toggle a star back
|
|
35
|
+
* off by accident.
|
|
36
|
+
*/
|
|
37
|
+
setFavorite: (relPath: string, favorite: boolean) => void;
|
|
38
|
+
/** Re-read from the Host. */
|
|
39
|
+
refresh: () => void;
|
|
40
|
+
/** Set when the last write was refused, so the surface can say why. */
|
|
41
|
+
error: string | undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Read and write the caller's favourites.
|
|
45
|
+
* @param enabled - false while the mount point is unknown, which is the only state
|
|
46
|
+
* where a favourites request would name a view that does not exist yet.
|
|
47
|
+
* @returns The store; `relPaths` is empty until the first read answers.
|
|
48
|
+
*/
|
|
49
|
+
export declare function useFavorites(enabled: boolean): FavoritesStore;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pinned-path store the rail renders.
|
|
3
|
+
*
|
|
4
|
+
* Pins are a document-level preference, so they live in the caller's own view
|
|
5
|
+
* directory on the share rather than in browser storage; the Host owns that file
|
|
6
|
+
* and this hook only reads and writes it through the two routes. The optimistic
|
|
7
|
+
* update exists because toggling a pin is a one-click gesture on a menu the reader
|
|
8
|
+
* is looking at: waiting for a round trip before the row moves makes the control
|
|
9
|
+
* feel broken when it is merely slow. A failed write reloads from the Host rather
|
|
10
|
+
* than leaving the guess on screen.
|
|
11
|
+
*
|
|
12
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
13
|
+
*/
|
|
14
|
+
/** What the hook exposes to the rail. */
|
|
15
|
+
export interface PinnedStore {
|
|
16
|
+
/** Pinned paths, in the order the Host returned them. */
|
|
17
|
+
relPaths: readonly string[];
|
|
18
|
+
/** Whether one path is pinned. */
|
|
19
|
+
has: (relPath: string) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Add or remove one path.
|
|
22
|
+
*
|
|
23
|
+
* Idempotent in the direction asked for: pinning an already-pinned path leaves
|
|
24
|
+
* the list alone rather than toggling it off, so a double click cannot silently
|
|
25
|
+
* undo the first one.
|
|
26
|
+
*/
|
|
27
|
+
setPinned: (relPath: string, pinned: boolean) => void;
|
|
28
|
+
/** Re-read from the Host. */
|
|
29
|
+
refresh: () => void;
|
|
30
|
+
/** Set when the last write was refused, so the surface can say why. */
|
|
31
|
+
error: string | undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Read and write the caller's pins.
|
|
35
|
+
* @param enabled - false while the mount point is unknown, which is the only state
|
|
36
|
+
* where a pin request would name a tree that does not exist yet.
|
|
37
|
+
* @returns The store.
|
|
38
|
+
*/
|
|
39
|
+
export declare function usePinnedPaths(enabled: boolean): PinnedStore;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The set of folders the reader has open in the explorer, remembered across visits.
|
|
3
|
+
*
|
|
4
|
+
* Expansion is a document-level preference, so it lives beside `.pinned.json` and
|
|
5
|
+
* `.view-mode.json` in the caller's view directory rather than in browser storage: a
|
|
6
|
+
* reader who opens `department/platform` on one machine expects to find it open on the
|
|
7
|
+
* next, and a browser profile is not the unit a knowledge base follows.
|
|
8
|
+
*
|
|
9
|
+
* Two costs shape the implementation.
|
|
10
|
+
*
|
|
11
|
+
* The read is a Host round trip, so the tree renders collapsed first and expands once
|
|
12
|
+
* the answer lands. Rendering it collapsed is the honest initial state: it is exactly
|
|
13
|
+
* what a reader who never opened a folder sees, and the alternative -- holding the whole
|
|
14
|
+
* tree back until a preference resolves -- would trade a flicker for a blank panel.
|
|
15
|
+
*
|
|
16
|
+
* The write happens on every toggle, and a reader opening a deep path toggles once per
|
|
17
|
+
* level, so writing through immediately would put four round trips behind one gesture.
|
|
18
|
+
* Saves are therefore coalesced: the first toggle arms a timer, later toggles inside the
|
|
19
|
+
* window replace the pending payload, and the timer sends whatever the latest state is.
|
|
20
|
+
* A save still in flight when the reader keeps clicking leaves a dirty flag rather than
|
|
21
|
+
* queueing a second request, so the wire never carries more than one save at a time and
|
|
22
|
+
* the last state always wins.
|
|
23
|
+
*
|
|
24
|
+
* A failed write is not surfaced as a page error. This is a view preference whose worst
|
|
25
|
+
* outcome is a folder that comes back closed, and shouting at a reader for it would be
|
|
26
|
+
* worse than the outcome; the next successful save repairs it.
|
|
27
|
+
*
|
|
28
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
29
|
+
*/
|
|
30
|
+
/** What the hook exposes to the rail. */
|
|
31
|
+
export interface TreeStateStore {
|
|
32
|
+
/** Absolute paths of the expanded folders, as the Host last answered. */
|
|
33
|
+
expanded: readonly string[];
|
|
34
|
+
/** Toggle one directory open or closed. */
|
|
35
|
+
toggle: (path: string) => void;
|
|
36
|
+
/** Set the whole set at once (a reveal, or a Host round trip). */
|
|
37
|
+
setExpanded: (paths: readonly string[]) => void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Read and write the caller's expanded folders.
|
|
41
|
+
* @param enabled - false while the mount point is unknown, which is the only state
|
|
42
|
+
* where there is no view directory holding the preference.
|
|
43
|
+
* @param root - the knowledge root, used to bound what is stored; '' while unknown.
|
|
44
|
+
* @returns The store; `expanded` is empty until the first read answers.
|
|
45
|
+
*/
|
|
46
|
+
export declare function useTreeState(enabled: boolean, root?: string): TreeStateStore;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The list/grid preference the rail renders.
|
|
3
|
+
*
|
|
4
|
+
* A document-level preference stored beside the pins, for the same reason they are:
|
|
5
|
+
* a reader who switches to the grid on one machine expects it on the next. The
|
|
6
|
+
* optimistic update exists because the toggle is a one-click gesture on a control
|
|
7
|
+
* the reader is looking at, and waiting for a round trip before the layout changes
|
|
8
|
+
* makes it feel broken when it is merely slow. A failed write reloads from the Host
|
|
9
|
+
* rather than leaving the guess on screen.
|
|
10
|
+
*
|
|
11
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Which layout the rail is showing.
|
|
15
|
+
*
|
|
16
|
+
* `explorer` renders the product's own file explorer (see `KnowledgeFileTree`), which is
|
|
17
|
+
* the tree a reader already knows from the workspace sidebar. It is a third option rather
|
|
18
|
+
* than a replacement because the two layouts answer different questions: the listing shows
|
|
19
|
+
* knowledge-base state the explorer has no concept of (whether a row is readable, its
|
|
20
|
+
* pinned mark, multi-select for a batch grant), while the explorer brings the affordances a
|
|
21
|
+
* file tree is expected to have (sort, inline rename, drag-and-drop, upload strip).
|
|
22
|
+
*/
|
|
23
|
+
export type ViewMode = 'list' | 'grid' | 'explorer';
|
|
24
|
+
/** What the hook exposes to the rail. */
|
|
25
|
+
export interface ViewModeStore {
|
|
26
|
+
/** The stored layout. */
|
|
27
|
+
mode: ViewMode;
|
|
28
|
+
/** Switch layouts, writing through to the Host. */
|
|
29
|
+
setMode: (mode: ViewMode) => void;
|
|
30
|
+
/** Set when the last write was refused, so the surface can say why. */
|
|
31
|
+
error: string | undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Read and write the caller's layout preference.
|
|
35
|
+
* @param enabled - false while the mount point is unknown, which is the only state
|
|
36
|
+
* where there is no view directory to hold the preference.
|
|
37
|
+
* @returns The store.
|
|
38
|
+
*/
|
|
39
|
+
export declare function useViewMode(enabled: boolean): ViewModeStore;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotent deployment recipes for the knowledge base.
|
|
3
|
+
*
|
|
4
|
+
* Every step here is a *plan*: a named sequence of shell commands plus the
|
|
5
|
+
* predicate that decides whether the step already ran. The Settings panel shows
|
|
6
|
+
* each step, runs it over the {@link RemoteRunner} seam, and reports the output.
|
|
7
|
+
*
|
|
8
|
+
* Rules these recipes must satisfy:
|
|
9
|
+
* - Re-runnable. Clicking twice must not install twice, start two servers, or
|
|
10
|
+
* fail because something already exists.
|
|
11
|
+
* - Read-only probes first. A status step never mutates anything.
|
|
12
|
+
* - The commands are quoted for a POSIX shell and never interpolate untrusted
|
|
13
|
+
* input without quoting.
|
|
14
|
+
*
|
|
15
|
+
* @module @sparkelf/dsh-knowledge-base/deploy
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* MinerU deployment tiers this plugin can prepare.
|
|
19
|
+
*
|
|
20
|
+
* MinerU 4.x recognises exactly these two (`mineru/types.py`: `DEPLOYMENT_TIERS`), and
|
|
21
|
+
* `models download --tier` validates against that tuple. The `flash` name that also exists in
|
|
22
|
+
* MinerU is a *quality* tier for the parsers that need no local weights, not a deployment
|
|
23
|
+
* tier, so it cannot be prepared and is not offered here.
|
|
24
|
+
*/
|
|
25
|
+
export type MineruTier = 'basic' | 'standard';
|
|
26
|
+
/** Small-model backend for MinerU. */
|
|
27
|
+
export type MineruBackend = 'onnx' | 'torch';
|
|
28
|
+
/** One step of a deployment plan. */
|
|
29
|
+
export interface DeployStep {
|
|
30
|
+
/** Stable id used by the UI. */
|
|
31
|
+
id: string;
|
|
32
|
+
/** Human-facing label key. */
|
|
33
|
+
label: string;
|
|
34
|
+
/**
|
|
35
|
+
* Commands to run in order. Each is a complete shell line.
|
|
36
|
+
* `{{...}}` placeholders are substituted from {@link DeployPlanContext}.
|
|
37
|
+
*/
|
|
38
|
+
commands: string[];
|
|
39
|
+
/** How long this step may take; model downloads are slow. */
|
|
40
|
+
timeoutMs: number;
|
|
41
|
+
}
|
|
42
|
+
/** Values substituted into command templates. */
|
|
43
|
+
export interface DeployPlanContext {
|
|
44
|
+
/** Absolute path of the authoritative knowledge tree. */
|
|
45
|
+
actualRoot: string;
|
|
46
|
+
/** Absolute path of the per-user view tree. */
|
|
47
|
+
viewRoot: string;
|
|
48
|
+
/** Tier to deploy. */
|
|
49
|
+
tier: MineruTier;
|
|
50
|
+
/** Small-model backend. */
|
|
51
|
+
backend: MineruBackend;
|
|
52
|
+
/** TCP port the MinerU API listens on. */
|
|
53
|
+
port: number;
|
|
54
|
+
/** Client hosts allowed to mount the NFS export, as an exports spec. */
|
|
55
|
+
permitted: string;
|
|
56
|
+
/** Host serving the export, as `address` or `address:/path` (the pseudo root). */
|
|
57
|
+
server: string;
|
|
58
|
+
/** Where the client mounts the export, i.e. the browsable knowledge tree. */
|
|
59
|
+
mountPoint: string;
|
|
60
|
+
/** Memory ceiling in MiB for the MinerU service; clamped to half the host's RAM. */
|
|
61
|
+
memoryMaxMiB?: number;
|
|
62
|
+
/**
|
|
63
|
+
* How a remote deployment reaches its share.
|
|
64
|
+
*
|
|
65
|
+
* Decides which transport steps {@link deploymentPlan} emits. Absent means `nfs`, the
|
|
66
|
+
* shape this plan had before the protocol existed.
|
|
67
|
+
*/
|
|
68
|
+
protocol?: 'nfs' | 'sshfs';
|
|
69
|
+
/** SSHFS: remote host. */
|
|
70
|
+
sshHost?: string;
|
|
71
|
+
/** SSHFS: login user on the remote host. */
|
|
72
|
+
sshUser?: string;
|
|
73
|
+
/** SSHFS: TCP port the SSH daemon listens on. */
|
|
74
|
+
sshPort?: number;
|
|
75
|
+
/**
|
|
76
|
+
* SSHFS: path of the private key on the HOST, when the credential store resolves to a
|
|
77
|
+
* file. Optional: an ssh-agent-managed key needs no `-i`.
|
|
78
|
+
*/
|
|
79
|
+
sshKeyPath?: string;
|
|
80
|
+
/** SSHFS: absolute path of the shared directory on the remote host. */
|
|
81
|
+
sshRemotePath?: string;
|
|
82
|
+
/** SSHFS: extra `-o` options, appended to the ones the step requires. */
|
|
83
|
+
sshMountOptions?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Memory ceiling for the MinerU service when the config names none.
|
|
87
|
+
*
|
|
88
|
+
* The `basic` tier peaked at 1.86 GB resident while parsing a one-page PDF (measured
|
|
89
|
+
* 2026-10-15), so a ceiling near the steady-state size leaves `MemoryHigh` no headroom. On a
|
|
90
|
+
* host without swap that reads as a stalled parse rather than a slow one, because throttling
|
|
91
|
+
* has nowhere to spill. The service step clamps this to half of RAM, so the number only has to
|
|
92
|
+
* be generous on a roomy machine.
|
|
93
|
+
*/
|
|
94
|
+
export declare const DEFAULT_MINERU_MEMORY_MAX_MIB = 4096;
|
|
95
|
+
/** Rotate a single-quoted shell string safely. */
|
|
96
|
+
export declare function shquote(value: string): string;
|
|
97
|
+
/** Substitute `{{name}}` placeholders; unknown names are left intact. */
|
|
98
|
+
export declare function renderCommand(template: string, context: DeployPlanContext): string;
|
|
99
|
+
/** Marker the image keeps so a redeploy can prove which tier is present. */
|
|
100
|
+
export declare const KB_STATE_FILE = "/var/lib/dataops-kb/state.json";
|
|
101
|
+
/**
|
|
102
|
+
* Read-only environment probe.
|
|
103
|
+
*
|
|
104
|
+
* Establishes which install path is available so the UI can disable the steps
|
|
105
|
+
* that cannot work: a host without Python cannot take the pip path.
|
|
106
|
+
* @returns the probe step.
|
|
107
|
+
*/
|
|
108
|
+
export declare function probeStep(): DeployStep;
|
|
109
|
+
/**
|
|
110
|
+
* Install the MinerU package and download the selected tier's models.
|
|
111
|
+
*
|
|
112
|
+
* The base package (`mineru>=4.0,<5`) is the CPU path: it brings ONNX and
|
|
113
|
+
* llama.cpp but not vLLM, which needs a GPU this environment does not have.
|
|
114
|
+
* @param context - deployment values.
|
|
115
|
+
* @returns the install step.
|
|
116
|
+
*/
|
|
117
|
+
export declare function mineruInstallStep(context: DeployPlanContext): DeployStep;
|
|
118
|
+
/**
|
|
119
|
+
* Install a systemd unit that serves the MinerU HTTP API.
|
|
120
|
+
*
|
|
121
|
+
* A unit rather than a bare background process so the service survives logout
|
|
122
|
+
* and can be restarted by the same click.
|
|
123
|
+
* @param context - deployment values.
|
|
124
|
+
* @returns the service step.
|
|
125
|
+
*/
|
|
126
|
+
export declare function mineruServiceStep(context: DeployPlanContext): DeployStep;
|
|
127
|
+
/**
|
|
128
|
+
* Verify the MinerU API actually answers.
|
|
129
|
+
*
|
|
130
|
+
* This is the check that catches the parameter-compatibility question: the
|
|
131
|
+
* plugin posts multipart form fields while MinerU 4.x reads a request body, so
|
|
132
|
+
* a pass here is the evidence that the two agree.
|
|
133
|
+
* @param context - deployment values.
|
|
134
|
+
* @returns the verification step.
|
|
135
|
+
*/
|
|
136
|
+
export declare function mineruVerifyStep(context: DeployPlanContext): DeployStep;
|
|
137
|
+
/**
|
|
138
|
+
* Deploy the NFS export of the knowledge tree.
|
|
139
|
+
*
|
|
140
|
+
* Uses the container image rather than host packages: Docker is already present
|
|
141
|
+
* on the target, the image carries the userspace tools, and its default export
|
|
142
|
+
* template already includes `no_root_squash`, which hardlinking across NFS
|
|
143
|
+
* requires.
|
|
144
|
+
* @param context - deployment values.
|
|
145
|
+
* @returns the NFS step.
|
|
146
|
+
*/
|
|
147
|
+
export declare function nfsDeployStep(context: DeployPlanContext): DeployStep;
|
|
148
|
+
/**
|
|
149
|
+
* Prove hardlinks work across the NFS export.
|
|
150
|
+
*
|
|
151
|
+
* Creating a hard link between the authoritative tree and a view directory is
|
|
152
|
+
* the single operation the whole permission model depends on, and it is also
|
|
153
|
+
* the operation most likely to fail (wrong export scope, `root_squash`, split
|
|
154
|
+
* filesystems). A pass here is the acceptance test for the storage side.
|
|
155
|
+
* @param context - deployment values.
|
|
156
|
+
* @returns the storage verification step.
|
|
157
|
+
*/
|
|
158
|
+
export declare function nfsVerifyStep(context: DeployPlanContext): DeployStep;
|
|
159
|
+
/**
|
|
160
|
+
* Mount the exported share at the mount point, with the option this deployment needs.
|
|
161
|
+
*
|
|
162
|
+
* `noresvport` is not decorative here. This host runs WSL2 with
|
|
163
|
+
* `networkingMode=mirrored`, where an NFS mount without it hangs until timeout
|
|
164
|
+
* (`mount.nfs: Connection timed out`) while `rpcinfo`, `showmount -e` and a plain TCP
|
|
165
|
+
* connect to 2049 all still succeed. Measured 2026-10-16 against a server that mounts
|
|
166
|
+
* fine from its own kernel, and A/B verified: without the option the mount fails, with
|
|
167
|
+
* it the mount succeeds, repeatably. Microsoft documents the same failure for mirrored
|
|
168
|
+
* networking (WSL issue 12508), which is still open.
|
|
169
|
+
*
|
|
170
|
+
* The NFS client normally opts into a reserved source port, which requires a reply the
|
|
171
|
+
* mirrored networking stack does not deliver; `noresvport` drops that requirement.
|
|
172
|
+
*
|
|
173
|
+
* @param context - Deployment paths and the export address.
|
|
174
|
+
* @returns The mount step.
|
|
175
|
+
*/
|
|
176
|
+
export declare function nfsMountStep(context: DeployPlanContext): DeployStep;
|
|
177
|
+
/**
|
|
178
|
+
* Mount a remote directory over SSHFS on the HOST.
|
|
179
|
+
*
|
|
180
|
+
* The same shape as {@link nfsMountStep}, for a host that cannot mount NFS: the container
|
|
181
|
+
* has no `CAP_SYS_ADMIN` and cannot mount anything itself, so this runs on the Host and the
|
|
182
|
+
* resulting directory is bound in at `mountPoint` like any other.
|
|
183
|
+
*
|
|
184
|
+
* Two details are not incidental:
|
|
185
|
+
* - `StrictHostKeyChecking=accept-new` is limited to this invocation. Accepting an unknown
|
|
186
|
+
* host key globally would silence the one warning that catches a swapped host, so the
|
|
187
|
+
* trust-on-first-use decision is scoped to the connection whose key the operator is
|
|
188
|
+
* establishing.
|
|
189
|
+
* - The reconnect options are not optional here. An SSHFS mount whose connection drops
|
|
190
|
+
* returns EIO to every reader until someone unmounts it; `reconnect` plus a keepalive is
|
|
191
|
+
* what makes a transient network blip a stall instead of a broken knowledge base.
|
|
192
|
+
* @param context - transport and target paths from the deployment config.
|
|
193
|
+
* @returns the mount step.
|
|
194
|
+
*/
|
|
195
|
+
export declare function sshfsMountStep(context: DeployPlanContext): DeployStep;
|
|
196
|
+
/**
|
|
197
|
+
* Verify the SSHFS mount is present, readable, and actually a FUSE mount.
|
|
198
|
+
*
|
|
199
|
+
* Separate from the mount step because "the command exited 0" and "the reader will see
|
|
200
|
+
* files" are different claims: a stale empty directory satisfies the first and not the
|
|
201
|
+
* second, and the filesystem type is what tells them apart.
|
|
202
|
+
* @param context - transport and target paths from the deployment config.
|
|
203
|
+
* @returns the verification step.
|
|
204
|
+
*/
|
|
205
|
+
export declare function sshfsVerifyStep(context: DeployPlanContext): DeployStep;
|
|
206
|
+
/** The full ordered plan the Settings panel presents. */
|
|
207
|
+
export declare function deploymentPlan(context: DeployPlanContext): DeployStep[];
|
|
208
|
+
/** Look up one step by id. */
|
|
209
|
+
export declare function findStep(id: string, context: DeployPlanContext): DeployStep | undefined;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert an uploaded document into Markdown so its text becomes greppable.
|
|
3
|
+
*
|
|
4
|
+
* The knowledge tree doubles as a document store and as what an agent greps. A
|
|
5
|
+
* `.docx` on disk is neither: its bytes answer no query. Every upload therefore
|
|
6
|
+
* lands twice — the original, byte for byte, plus a `.md` sibling holding the
|
|
7
|
+
* extracted text — so a reader can still open the real file while a search sees
|
|
8
|
+
* the words inside it.
|
|
9
|
+
*
|
|
10
|
+
* Extraction is delegated rather than implemented here. Three authorities
|
|
11
|
+
* already exist in this deployment and each owns one format family:
|
|
12
|
+
*
|
|
13
|
+
* - MinerU (`/file_parse`) parses PDF and office documents with layout models.
|
|
14
|
+
* It is the best answer for a scanned PDF and the one authority that can
|
|
15
|
+
* recognise tables inside one.
|
|
16
|
+
* - LibreOffice converts the legacy binary formats (`.doc`, `.xls`, `.ppt`) that
|
|
17
|
+
* MinerU and the Python readers refuse.
|
|
18
|
+
* - `python-docx` / `openpyxl` / `python-pptx` / `pypdf` read the modern formats
|
|
19
|
+
* directly, without a service round trip.
|
|
20
|
+
*
|
|
21
|
+
* None of them is reimplemented in TypeScript: a `.docx` is a ZIP of XML and a
|
|
22
|
+
* second parser would only disagree with the first. This module decides WHICH
|
|
23
|
+
* authority to ask and what to do when it cannot answer, which is the part that
|
|
24
|
+
* is specific to this plugin.
|
|
25
|
+
*
|
|
26
|
+
* A failed extraction never fails the upload. The original is already written and
|
|
27
|
+
* remains the source of truth; the caller learns that no `.md` was produced, and
|
|
28
|
+
* the file stays readable through the original.
|
|
29
|
+
*
|
|
30
|
+
* @module @sparkelf/dsh-knowledge-base/extract
|
|
31
|
+
*/
|
|
32
|
+
/** Formats whose text this module knows how to extract. */
|
|
33
|
+
export declare const EXTRACTABLE_EXTENSIONS: readonly [".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".odt", ".ods", ".odp", ".rtf"];
|
|
34
|
+
/** One extractable extension. */
|
|
35
|
+
export type ExtractableExtension = typeof EXTRACTABLE_EXTENSIONS[number];
|
|
36
|
+
/** How one request to the extraction authorities ended. */
|
|
37
|
+
export interface ExtractResult {
|
|
38
|
+
/** Markdown text, when extraction produced any. */
|
|
39
|
+
markdown?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Which authority answered. Reported so a bad conversion can be traced.
|
|
42
|
+
*
|
|
43
|
+
* `mineru-ocr` means the first pass classified the document as text and came back
|
|
44
|
+
* too thin to search, so the same parser was asked again with OCR forced. It is a
|
|
45
|
+
* separate value because the two passes can disagree, and a reader debugging a
|
|
46
|
+
* missing line needs to know which one produced the file.
|
|
47
|
+
*/
|
|
48
|
+
via?: 'mineru' | 'mineru-ocr' | 'libreoffice' | 'python' | 'native';
|
|
49
|
+
/** Why no markdown was produced. Present whenever {@link markdown} is absent. */
|
|
50
|
+
reason?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Whether this path names a document worth extracting.
|
|
54
|
+
*
|
|
55
|
+
* Compared case-insensitively because a browser reports the extension as the
|
|
56
|
+
* user typed it, and a `.PDF` is the same format as a `.pdf`.
|
|
57
|
+
*
|
|
58
|
+
* @param relPath - the uploaded path, relative to the knowledge root.
|
|
59
|
+
* @returns true when {@link extractDocument} has an authority for it.
|
|
60
|
+
*/
|
|
61
|
+
export declare function isExtractable(relPath: string): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* The Markdown sibling an extracted document lands beside.
|
|
64
|
+
*
|
|
65
|
+
* Appending rather than replacing the extension keeps two same-named uploads
|
|
66
|
+
* from colliding: `报告.docx` becomes `报告.docx.md`, so it cannot overwrite a
|
|
67
|
+
* `报告.md` the user wrote by hand.
|
|
68
|
+
*
|
|
69
|
+
* @param relPath - the uploaded document's path.
|
|
70
|
+
* @returns the markdown path to write.
|
|
71
|
+
*/
|
|
72
|
+
export declare function markdownSiblingOf(relPath: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Whether MinerU should be asked before the local readers.
|
|
75
|
+
*
|
|
76
|
+
* PDF only: a PDF carries no recoverable structure beyond its layout, which is
|
|
77
|
+
* exactly what MinerU models. An office document still has its XML, and the
|
|
78
|
+
* direct readers keep more of it (a spreadsheet's cell grid survives
|
|
79
|
+
* `openpyxl` and is flattened by a layout parser).
|
|
80
|
+
*
|
|
81
|
+
* @param ext - lowercased extension including the dot.
|
|
82
|
+
* @returns true when the remote parser is the better authority.
|
|
83
|
+
*/
|
|
84
|
+
export declare function prefersRemoteParser(ext: string): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Ask MinerU to parse one file and return its Markdown.
|
|
87
|
+
*
|
|
88
|
+
* The service answers with a task handle for large files and the body directly
|
|
89
|
+
* for small ones, so both shapes are accepted. A non-200 answer is a miss, not
|
|
90
|
+
* a failure of the upload.
|
|
91
|
+
*
|
|
92
|
+
* @param baseUrl - MinerU service base URL, without a trailing slash.
|
|
93
|
+
* @param fullPath - absolute path of the document on this host.
|
|
94
|
+
* @param fileName - name to report to the service.
|
|
95
|
+
* @returns the Markdown, or undefined when the service could not parse it.
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* Whether an extraction came back too small to be worth searching.
|
|
99
|
+
*
|
|
100
|
+
* Not a judgement about document quality: a 200-character memo is a real document,
|
|
101
|
+
* and treating it as a failure would re-run OCR over something already correct. The
|
|
102
|
+
* test targets the specific shape of a mis-classified scan -- pages of content that
|
|
103
|
+
* came back as a title and a page number -- so it counts what is left after markup
|
|
104
|
+
* rather than trusting a single threshold on the whole answer.
|
|
105
|
+
*
|
|
106
|
+
* @param markdown - what the parser returned.
|
|
107
|
+
* @returns true when the answer is short enough that a classifier mistake is likely.
|
|
108
|
+
*/
|
|
109
|
+
export declare function looksTooThinToSearch(markdown: string): boolean;
|
|
110
|
+
/** Services and commands extraction may use, as configured for this deployment. */
|
|
111
|
+
export interface ExtractOptions {
|
|
112
|
+
/** MinerU base URL. Empty disables the remote parser. */
|
|
113
|
+
mineruUrl?: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Extract one document to Markdown.
|
|
117
|
+
*
|
|
118
|
+
* Authorities are tried in an order that puts the best answer first for each
|
|
119
|
+
* format, and every step degrades to the next rather than failing: a PDF whose
|
|
120
|
+
* MinerU call times out still gets `pypdf`'s text layer.
|
|
121
|
+
*
|
|
122
|
+
* @param fullPath - absolute path of the written document.
|
|
123
|
+
* @param relPath - the same document's knowledge-relative path, for the title.
|
|
124
|
+
* @param options - services this deployment exposes.
|
|
125
|
+
* @returns the Markdown and the authority that produced it, or the reason none did.
|
|
126
|
+
*/
|
|
127
|
+
export declare function extractDocument(fullPath: string, relPath: string, options?: ExtractOptions): Promise<ExtractResult>;
|
|
128
|
+
/**
|
|
129
|
+
* Whether a file exists and is a regular file.
|
|
130
|
+
*
|
|
131
|
+
* @param path - absolute path to test.
|
|
132
|
+
* @returns true when it is there.
|
|
133
|
+
*/
|
|
134
|
+
export declare function fileExists(path: string): Promise<boolean>;
|
|
135
|
+
/**
|
|
136
|
+
* Write extracted markdown beside its document.
|
|
137
|
+
*
|
|
138
|
+
* Kept separate from {@link extractDocument} so the write is testable without an
|
|
139
|
+
* extraction service.
|
|
140
|
+
*
|
|
141
|
+
* @param markdownPath - absolute path of the `.md` sibling.
|
|
142
|
+
* @param markdown - the text to write.
|
|
143
|
+
*/
|
|
144
|
+
export declare function writeExtractedMarkdown(markdownPath: string, markdown: string): Promise<void>;
|