@moxn/kb-migrate 0.4.41 → 0.6.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/dist/api-error.d.ts +7 -0
- package/dist/api-error.js +21 -0
- package/dist/blocks-to-grammar.d.ts +51 -0
- package/dist/blocks-to-grammar.js +133 -0
- package/dist/client-http.test.d.ts +1 -0
- package/dist/client-http.test.js +89 -0
- package/dist/client.d.ts +76 -3
- package/dist/client.js +180 -154
- package/dist/export.js +94 -4
- package/dist/export.test.js +121 -0
- package/dist/http.d.ts +1 -0
- package/dist/http.js +16 -0
- package/dist/import-local.js +1 -0
- package/dist/import-local.test.js +96 -0
- package/dist/index.js +39 -12
- package/dist/migrate-document.test.js +100 -20
- package/dist/output.d.ts +17 -0
- package/dist/output.js +65 -0
- package/dist/output.test.d.ts +1 -0
- package/dist/output.test.js +59 -0
- package/dist/sources/onenote/__tests__/onenote-tree.test.d.ts +1 -0
- package/dist/sources/onenote/__tests__/onenote-tree.test.js +43 -0
- package/dist/targets/notion.js +3 -2
- package/dist/types.d.ts +6 -1
- package/package.json +3 -2
package/dist/api-error.d.ts
CHANGED
|
@@ -12,3 +12,10 @@ export declare function formatApiError(status: number, body: {
|
|
|
12
12
|
details?: unknown;
|
|
13
13
|
[key: string]: unknown;
|
|
14
14
|
} | null | undefined): string;
|
|
15
|
+
/**
|
|
16
|
+
* Parse a response body that should be JSON. When it isn't — typically the
|
|
17
|
+
* Vercel Auth wall's HTML page on a protected deployment — say what arrived
|
|
18
|
+
* (status, content-type) instead of surfacing a raw `JSON.parse` error like
|
|
19
|
+
* `Unexpected token '<', "<!DOCTYPE "... is not valid JSON`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readJson<T = any>(response: Response): Promise<T>;
|
package/dist/api-error.js
CHANGED
|
@@ -17,3 +17,24 @@ export function formatApiError(status, body) {
|
|
|
17
17
|
: '';
|
|
18
18
|
return detail ? `API error ${status}: ${detail}` : `API error: ${status}`;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Parse a response body that should be JSON. When it isn't — typically the
|
|
22
|
+
* Vercel Auth wall's HTML page on a protected deployment — say what arrived
|
|
23
|
+
* (status, content-type) instead of surfacing a raw `JSON.parse` error like
|
|
24
|
+
* `Unexpected token '<', "<!DOCTYPE "... is not valid JSON`.
|
|
25
|
+
*/
|
|
26
|
+
export async function readJson(response) {
|
|
27
|
+
try {
|
|
28
|
+
return (await response.json());
|
|
29
|
+
}
|
|
30
|
+
catch (cause) {
|
|
31
|
+
if (!(cause instanceof SyntaxError))
|
|
32
|
+
throw cause;
|
|
33
|
+
const contentType = response.headers?.get?.('content-type') ?? 'no content-type';
|
|
34
|
+
const where = response.url ? ` from ${response.url}` : '';
|
|
35
|
+
const hint = /html/i.test(contentType)
|
|
36
|
+
? ' — an HTML page usually means the Vercel protection wall: set MOXN_VERCEL_BYPASS, with MOXN_BASE_URL on this origin'
|
|
37
|
+
: '';
|
|
38
|
+
throw new Error(`Expected JSON${where} but got a non-JSON response (HTTP ${response.status}, ${contentType})${hint}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContentBlock[] → grammar markdown serializer (Phase B of the write-surface
|
|
3
|
+
* tightening: the REST block-input path is retired; every import lands via
|
|
4
|
+
* `import_markdown`, the same grammar the export emits and the app's codec
|
|
5
|
+
* round-trips).
|
|
6
|
+
*
|
|
7
|
+
* The extractors (Notion / OneNote) keep producing ContentBlock sections —
|
|
8
|
+
* that stays a useful internal representation. This module is the WRITE
|
|
9
|
+
* BOUNDARY: it assumes media blocks have already been uploaded/re-hosted to
|
|
10
|
+
* storage keys (see `MoxnClient.processSections`, which now also re-hosts
|
|
11
|
+
* `url`/`base64` media) and reduces the sections to the `## H2` + `:::`
|
|
12
|
+
* directive grammar.
|
|
13
|
+
*
|
|
14
|
+
* Directive attr serialization REPLICATES the app codec
|
|
15
|
+
* (`src/lib/kb/grammar/attrs.ts` — kb-migrate is a separate package and
|
|
16
|
+
* cannot import it, same posture as grammar-media.ts): string → double-quoted
|
|
17
|
+
* with `&`→`&`, `"`→`"`; number → bare; string[] → `[a,b]` with
|
|
18
|
+
* `\` escaping for `\`, `,`, `]`.
|
|
19
|
+
*/
|
|
20
|
+
import type { ContentBlock } from './types.js';
|
|
21
|
+
type AttrValue = string | number | string[] | null | undefined;
|
|
22
|
+
/** Mirror of the app codec's `serializeAttrs` (src/lib/kb/grammar/attrs.ts). */
|
|
23
|
+
export declare function serializeAttrs(obj: Record<string, AttrValue>): string;
|
|
24
|
+
/**
|
|
25
|
+
* One ContentBlock → grammar chunk (markdown text or a `:::` directive line).
|
|
26
|
+
* Returns null for a block that cannot be expressed (e.g. base64 media that
|
|
27
|
+
* was not uploaded — the caller is expected to have re-hosted those first).
|
|
28
|
+
*/
|
|
29
|
+
export declare function blockToGrammar(block: ContentBlock): string | null;
|
|
30
|
+
export interface SectionsToGrammarResult {
|
|
31
|
+
markdown: string;
|
|
32
|
+
/** Media blocks that could not be expressed (no storage key / URL). */
|
|
33
|
+
dropped: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Sections → grammar markdown BODY (no front-matter — the caller prepends
|
|
37
|
+
* it). Each section becomes an `## <name>` chunk; blocks join with blank
|
|
38
|
+
* lines, matching the H2-split the server applies on the way back in.
|
|
39
|
+
*/
|
|
40
|
+
export declare function sectionsToGrammarMarkdown(sections: Array<{
|
|
41
|
+
name: string;
|
|
42
|
+
content: ContentBlock[];
|
|
43
|
+
}>): SectionsToGrammarResult;
|
|
44
|
+
/**
|
|
45
|
+
* Minimal YAML front-matter for an import: `name` (+ optional description).
|
|
46
|
+
* `import_markdown` HONORS the front-matter (replace_document semantics on
|
|
47
|
+
* update — the import source is the source of truth for metadata).
|
|
48
|
+
* Values are JSON-quoted, which is valid YAML for scalar strings.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildImportFrontMatter(name: string, description?: string | null): string;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContentBlock[] → grammar markdown serializer (Phase B of the write-surface
|
|
3
|
+
* tightening: the REST block-input path is retired; every import lands via
|
|
4
|
+
* `import_markdown`, the same grammar the export emits and the app's codec
|
|
5
|
+
* round-trips).
|
|
6
|
+
*
|
|
7
|
+
* The extractors (Notion / OneNote) keep producing ContentBlock sections —
|
|
8
|
+
* that stays a useful internal representation. This module is the WRITE
|
|
9
|
+
* BOUNDARY: it assumes media blocks have already been uploaded/re-hosted to
|
|
10
|
+
* storage keys (see `MoxnClient.processSections`, which now also re-hosts
|
|
11
|
+
* `url`/`base64` media) and reduces the sections to the `## H2` + `:::`
|
|
12
|
+
* directive grammar.
|
|
13
|
+
*
|
|
14
|
+
* Directive attr serialization REPLICATES the app codec
|
|
15
|
+
* (`src/lib/kb/grammar/attrs.ts` — kb-migrate is a separate package and
|
|
16
|
+
* cannot import it, same posture as grammar-media.ts): string → double-quoted
|
|
17
|
+
* with `&`→`&`, `"`→`"`; number → bare; string[] → `[a,b]` with
|
|
18
|
+
* `\` escaping for `\`, `,`, `]`.
|
|
19
|
+
*/
|
|
20
|
+
function escapeArrayElement(v) {
|
|
21
|
+
return v.replace(/\\/g, '\\\\').replace(/,/g, '\\,').replace(/]/g, '\\]');
|
|
22
|
+
}
|
|
23
|
+
/** Mirror of the app codec's `serializeAttrs` (src/lib/kb/grammar/attrs.ts). */
|
|
24
|
+
export function serializeAttrs(obj) {
|
|
25
|
+
const parts = [];
|
|
26
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
27
|
+
if (value === null || value === undefined)
|
|
28
|
+
continue;
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
parts.push(`${key}=[${value.map(escapeArrayElement).join(',')}]`);
|
|
31
|
+
}
|
|
32
|
+
else if (typeof value === 'number') {
|
|
33
|
+
parts.push(`${key}=${value}`);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const escaped = value.replace(/&/g, '&').replace(/"/g, '"');
|
|
37
|
+
parts.push(`${key}="${escaped}"`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return parts.join(' ');
|
|
41
|
+
}
|
|
42
|
+
/** The `ref` a media block serializes: storage key first, else its URL. */
|
|
43
|
+
function blockRef(block) {
|
|
44
|
+
if (block.type === 'storage' && block.key)
|
|
45
|
+
return block.key;
|
|
46
|
+
if (block.type === 'url' && block.url)
|
|
47
|
+
return block.url;
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* One ContentBlock → grammar chunk (markdown text or a `:::` directive line).
|
|
52
|
+
* Returns null for a block that cannot be expressed (e.g. base64 media that
|
|
53
|
+
* was not uploaded — the caller is expected to have re-hosted those first).
|
|
54
|
+
*/
|
|
55
|
+
export function blockToGrammar(block) {
|
|
56
|
+
switch (block.blockType) {
|
|
57
|
+
case 'text':
|
|
58
|
+
return block.text;
|
|
59
|
+
case 'image': {
|
|
60
|
+
const ref = blockRef(block);
|
|
61
|
+
if (!ref)
|
|
62
|
+
return null;
|
|
63
|
+
return `:::image{${serializeAttrs({
|
|
64
|
+
ref,
|
|
65
|
+
alt: block.alt,
|
|
66
|
+
mime: block.mediaType,
|
|
67
|
+
})}}`;
|
|
68
|
+
}
|
|
69
|
+
case 'document':
|
|
70
|
+
case 'file': {
|
|
71
|
+
const ref = blockRef(block);
|
|
72
|
+
if (!ref)
|
|
73
|
+
return null;
|
|
74
|
+
return `:::file{${serializeAttrs({
|
|
75
|
+
ref,
|
|
76
|
+
filename: 'filename' in block ? block.filename : undefined,
|
|
77
|
+
mime: block.mediaType,
|
|
78
|
+
})}}`;
|
|
79
|
+
}
|
|
80
|
+
case 'csv': {
|
|
81
|
+
const ref = blockRef(block);
|
|
82
|
+
if (!ref)
|
|
83
|
+
return null;
|
|
84
|
+
return `:::csv{${serializeAttrs({
|
|
85
|
+
ref,
|
|
86
|
+
filename: block.filename,
|
|
87
|
+
mime: 'text/csv',
|
|
88
|
+
headers: block.headers,
|
|
89
|
+
rowCount: block.rowCount,
|
|
90
|
+
})}}`;
|
|
91
|
+
}
|
|
92
|
+
case 'database_embed':
|
|
93
|
+
return `:::db{${serializeAttrs({ ref: block.databaseId })}}`;
|
|
94
|
+
default:
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Sections → grammar markdown BODY (no front-matter — the caller prepends
|
|
100
|
+
* it). Each section becomes an `## <name>` chunk; blocks join with blank
|
|
101
|
+
* lines, matching the H2-split the server applies on the way back in.
|
|
102
|
+
*/
|
|
103
|
+
export function sectionsToGrammarMarkdown(sections) {
|
|
104
|
+
let dropped = 0;
|
|
105
|
+
const chunks = [];
|
|
106
|
+
for (const section of sections) {
|
|
107
|
+
const parts = [`## ${section.name}`];
|
|
108
|
+
for (const block of section.content) {
|
|
109
|
+
const chunk = blockToGrammar(block);
|
|
110
|
+
if (chunk === null) {
|
|
111
|
+
dropped++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (chunk.trim().length > 0)
|
|
115
|
+
parts.push(chunk.trim());
|
|
116
|
+
}
|
|
117
|
+
chunks.push(parts.join('\n\n'));
|
|
118
|
+
}
|
|
119
|
+
return { markdown: chunks.join('\n\n'), dropped };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Minimal YAML front-matter for an import: `name` (+ optional description).
|
|
123
|
+
* `import_markdown` HONORS the front-matter (replace_document semantics on
|
|
124
|
+
* update — the import source is the source of truth for metadata).
|
|
125
|
+
* Values are JSON-quoted, which is valid YAML for scalar strings.
|
|
126
|
+
*/
|
|
127
|
+
export function buildImportFrontMatter(name, description) {
|
|
128
|
+
const lines = [`name: ${JSON.stringify(name)}`];
|
|
129
|
+
if (description !== undefined && description !== null && description !== '') {
|
|
130
|
+
lines.push(`description: ${JSON.stringify(description)}`);
|
|
131
|
+
}
|
|
132
|
+
return `---\n${lines.join('\n')}\n---\n\n`;
|
|
133
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kb-migrate's HTTP client against a Vercel-protected deployment (staging).
|
|
3
|
+
*
|
|
4
|
+
* `export-local` against https://staging.moxn.dev failed with
|
|
5
|
+
* `Unexpected token '<', "<!DOCTYPE "... is not valid JSON`: kb-migrate never
|
|
6
|
+
* sent the protection-bypass header, and the auth wall's HTML page reached a
|
|
7
|
+
* bare `response.json()`. The client now attaches the SAME
|
|
8
|
+
* `vercelBypassHeaders` kb-cli uses (@moxn/auth — env-gated, scoped to
|
|
9
|
+
* MOXN_BASE_URL's origin, so signed storage URLs never see the secret), and a
|
|
10
|
+
* non-JSON body is reported as what it is.
|
|
11
|
+
*/
|
|
12
|
+
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as os from 'os';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { MoxnClient } from './client.js';
|
|
17
|
+
const STAGING = 'https://staging.moxn.dev';
|
|
18
|
+
const HEADER = 'x-vercel-protection-bypass';
|
|
19
|
+
function jsonResponse(body) {
|
|
20
|
+
return new Response(JSON.stringify(body), {
|
|
21
|
+
status: 200,
|
|
22
|
+
headers: { 'content-type': 'application/json' },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function headersOf(init) {
|
|
26
|
+
return new Headers(init?.headers).has(HEADER)
|
|
27
|
+
? { [HEADER]: new Headers(init?.headers).get(HEADER) }
|
|
28
|
+
: {};
|
|
29
|
+
}
|
|
30
|
+
describe('MoxnClient — Vercel protection bypass', () => {
|
|
31
|
+
const saved = { ...process.env };
|
|
32
|
+
let fetchSpy;
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
fetchSpy = vi.spyOn(globalThis, 'fetch');
|
|
35
|
+
});
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
vi.restoreAllMocks();
|
|
38
|
+
process.env = { ...saved };
|
|
39
|
+
});
|
|
40
|
+
function client() {
|
|
41
|
+
return new MoxnClient({
|
|
42
|
+
apiUrl: STAGING,
|
|
43
|
+
apiKey: 'k',
|
|
44
|
+
basePath: '/',
|
|
45
|
+
onConflict: 'skip',
|
|
46
|
+
dryRun: false,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
it('attaches the bypass header to requests for the MOXN_BASE_URL origin only', async () => {
|
|
50
|
+
process.env.MOXN_VERCEL_BYPASS = 'bypass-secret';
|
|
51
|
+
process.env.MOXN_BASE_URL = STAGING;
|
|
52
|
+
fetchSpy.mockImplementation(async (url) => String(url).startsWith(STAGING)
|
|
53
|
+
? jsonResponse({ documents: [], pagination: { hasMore: false } })
|
|
54
|
+
: new Response('blob', { status: 200 }));
|
|
55
|
+
await client().listDocuments('/qa');
|
|
56
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-bypass-'));
|
|
57
|
+
await client().downloadFile('https://storage.example/signed/f.png', path.join(dir, 'f.png'));
|
|
58
|
+
const [apiCall, storageCall] = fetchSpy.mock.calls;
|
|
59
|
+
expect(String(apiCall[0])).toContain(`${STAGING}/api/v1/kb/documents`);
|
|
60
|
+
expect(headersOf(apiCall[1])).toEqual({ [HEADER]: 'bypass-secret' });
|
|
61
|
+
// The API key still rides alongside.
|
|
62
|
+
expect(new Headers(apiCall[1].headers).get('x-api-key')).toBe('k');
|
|
63
|
+
// A different origin (signed storage URL) never receives the secret.
|
|
64
|
+
expect(headersOf(storageCall[1])).toEqual({});
|
|
65
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
66
|
+
});
|
|
67
|
+
it('sends no bypass header when MOXN_VERCEL_BYPASS is unset', async () => {
|
|
68
|
+
delete process.env.MOXN_VERCEL_BYPASS;
|
|
69
|
+
process.env.MOXN_BASE_URL = STAGING;
|
|
70
|
+
fetchSpy.mockResolvedValue(jsonResponse({ documents: [], pagination: { hasMore: false } }));
|
|
71
|
+
await client().listDocuments('/qa');
|
|
72
|
+
expect(headersOf(fetchSpy.mock.calls[0][1])).toEqual({});
|
|
73
|
+
});
|
|
74
|
+
it('reports a non-JSON (auth-wall HTML) body by status + content-type, not a raw JSON.parse error', async () => {
|
|
75
|
+
fetchSpy.mockResolvedValue(new Response('<!DOCTYPE html><html>Vercel Authentication</html>', {
|
|
76
|
+
status: 200,
|
|
77
|
+
headers: { 'content-type': 'text/html; charset=utf-8' },
|
|
78
|
+
}));
|
|
79
|
+
const err = await client()
|
|
80
|
+
.listDocuments('/qa')
|
|
81
|
+
.then(() => null, (e) => e);
|
|
82
|
+
expect(err).toBeInstanceOf(Error);
|
|
83
|
+
expect(err.message).not.toMatch(/Unexpected token/);
|
|
84
|
+
expect(err.message).toMatch(/non-JSON/i);
|
|
85
|
+
expect(err.message).toContain('200');
|
|
86
|
+
expect(err.message).toContain('text/html');
|
|
87
|
+
expect(err.message).toMatch(/MOXN_VERCEL_BYPASS/);
|
|
88
|
+
});
|
|
89
|
+
});
|
package/dist/client.d.ts
CHANGED
|
@@ -32,17 +32,86 @@ export interface ImportMarkdownResult {
|
|
|
32
32
|
* lenient fallback recovered the name. Carries the js-yaml error message.
|
|
33
33
|
*/
|
|
34
34
|
frontMatterError?: string;
|
|
35
|
+
/** Section anchor ids in POSITION ORDER after the upsert (for cross-refs). */
|
|
36
|
+
sectionIds?: string[];
|
|
37
|
+
/** The branch the upsert landed on (for branch-scoped follow-ups like tags). */
|
|
38
|
+
branchId?: string;
|
|
39
|
+
/**
|
|
40
|
+
* The permissions a CREATED document got. Absent on update/skip (an import
|
|
41
|
+
* never re-permissions an existing document) and from servers that predate
|
|
42
|
+
* the importer permission fields.
|
|
43
|
+
*/
|
|
44
|
+
permissions?: {
|
|
45
|
+
defaultPermission: 'edit' | 'read' | 'none';
|
|
46
|
+
aiAccess: 'edit' | 'read' | 'none';
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The warning (if any) for an import whose `--default-permission` /
|
|
51
|
+
* `--ai-access` did not take effect: an existing document is never
|
|
52
|
+
* re-permissioned by a re-import, and a server that predates the fields
|
|
53
|
+
* creates documents with its defaults without echoing `permissions`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function importPermissionWarning(requested: {
|
|
56
|
+
defaultPermission?: string;
|
|
57
|
+
aiAccess?: string;
|
|
58
|
+
}, result: Pick<ImportMarkdownResult, 'outcome' | 'permissions'>): string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* One item of a `read` tool response, narrowed to the fields the export uses.
|
|
61
|
+
* `kind` is absent on an error item and on older md reads.
|
|
62
|
+
*/
|
|
63
|
+
export interface ReadDocumentItem {
|
|
64
|
+
type: string;
|
|
65
|
+
documentId: string;
|
|
66
|
+
error?: string;
|
|
67
|
+
kind?: string;
|
|
68
|
+
/** report: the HTML; slides: the deck-marker HTML. */
|
|
69
|
+
content?: Array<{
|
|
70
|
+
type: string;
|
|
71
|
+
text?: string;
|
|
72
|
+
}>;
|
|
73
|
+
/** kind='file' only. */
|
|
74
|
+
file?: {
|
|
75
|
+
filename: string;
|
|
76
|
+
download: {
|
|
77
|
+
url: string;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
/** kind='slides' only. */
|
|
81
|
+
deck?: {
|
|
82
|
+
themeCss?: string | null;
|
|
83
|
+
};
|
|
35
84
|
}
|
|
36
85
|
export declare class MoxnClient {
|
|
37
86
|
private apiUrl;
|
|
38
87
|
private apiKey;
|
|
39
88
|
private defaultPermission?;
|
|
40
89
|
private aiAccess?;
|
|
90
|
+
/** Every request goes through {@link moxnFetch} (bypass-header aware). */
|
|
91
|
+
private fetch;
|
|
41
92
|
constructor(options: MigrationOptions | ExportOptions);
|
|
42
93
|
/**
|
|
43
94
|
* Migrate a single document
|
|
44
95
|
*/
|
|
96
|
+
/**
|
|
97
|
+
* Migrate one extracted document via `import_markdown` (Phase B of the
|
|
98
|
+
* write-surface tightening — the REST block-input path is retired).
|
|
99
|
+
*
|
|
100
|
+
* Flow: upload/re-host every media block (local files, base64 payloads,
|
|
101
|
+
* remote URLs) → serialize the sections to grammar markdown → prepend a
|
|
102
|
+
* name/description front-matter → UPSERT via `import_markdown`
|
|
103
|
+
* (create-or-replace_document; `onConflict: 'skip'` skips an existing
|
|
104
|
+
* path). Idempotent: a byte-identical re-import is reported as a no-op
|
|
105
|
+
* update by the server.
|
|
106
|
+
*/
|
|
45
107
|
migrateDocument(doc: ExtractedDocument, basePath: string, onConflict: 'skip' | 'update', dryRun: boolean): Promise<MigrationResult>;
|
|
108
|
+
/**
|
|
109
|
+
* {@link importPermissionWarning} for this client's requested permissions,
|
|
110
|
+
* logged once and returned as a spreadable `{ warning }`.
|
|
111
|
+
*/
|
|
112
|
+
permissionWarningFor(result: Pick<ImportMarkdownResult, 'outcome' | 'permissions'>, sourcePath: string): {
|
|
113
|
+
warning?: string;
|
|
114
|
+
};
|
|
46
115
|
/**
|
|
47
116
|
* List all documents, optionally filtered by path prefix.
|
|
48
117
|
* Handles pagination automatically.
|
|
@@ -64,6 +133,13 @@ export declare class MoxnClient {
|
|
|
64
133
|
getDocumentMarkdown(documentId: string, opts?: {
|
|
65
134
|
forEdit?: boolean;
|
|
66
135
|
}): Promise<DocumentMarkdownResponse | null>;
|
|
136
|
+
/**
|
|
137
|
+
* Read one document through the `read` tool (`/api/v1/kb/tools`) — the same
|
|
138
|
+
* serialization agents and the CLI see. The export uses it for the kinds the
|
|
139
|
+
* grammar export has no body for (report / file / slides). Returns the single
|
|
140
|
+
* read item: a document shape carrying `kind`, or an `{ error }` item.
|
|
141
|
+
*/
|
|
142
|
+
readDocument(documentId: string): Promise<ReadDocumentItem>;
|
|
67
143
|
/**
|
|
68
144
|
* Build a `storageKey → signedDownloadUrl` map for a document's media, by
|
|
69
145
|
* reusing the `get_document_content` export action (the blocks read, which
|
|
@@ -102,8 +178,6 @@ export declare class MoxnClient {
|
|
|
102
178
|
key: string;
|
|
103
179
|
}>;
|
|
104
180
|
private getUploadUrl;
|
|
105
|
-
private createDocument;
|
|
106
|
-
private updateDocument;
|
|
107
181
|
/**
|
|
108
182
|
* Create a KB database.
|
|
109
183
|
*/
|
|
@@ -263,5 +337,4 @@ export declare class MoxnClient {
|
|
|
263
337
|
id: string;
|
|
264
338
|
name: string;
|
|
265
339
|
} | null>;
|
|
266
|
-
private isConflictError;
|
|
267
340
|
}
|