emdash-emit 0.0.0-stage → 0.1.2
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/README.md +798 -2
- package/dist/bin/emit.d.ts +5 -0
- package/dist/bin/emit.d.ts.map +1 -0
- package/dist/bin/emit.js +63 -0
- package/dist/bin/emit.js.map +1 -0
- package/dist/cli/commands/new.d.ts +4 -0
- package/dist/cli/commands/new.d.ts.map +1 -0
- package/dist/cli/commands/new.js +49 -0
- package/dist/cli/commands/new.js.map +1 -0
- package/dist/cli/commands/profile.d.ts +3 -0
- package/dist/cli/commands/profile.d.ts.map +1 -0
- package/dist/cli/commands/profile.js +104 -0
- package/dist/cli/commands/profile.js.map +1 -0
- package/dist/cli/commands/pull.d.ts +4 -0
- package/dist/cli/commands/pull.d.ts.map +1 -0
- package/dist/cli/commands/pull.js +157 -0
- package/dist/cli/commands/pull.js.map +1 -0
- package/dist/cli/commands/push.d.ts +4 -0
- package/dist/cli/commands/push.d.ts.map +1 -0
- package/dist/cli/commands/push.js +107 -0
- package/dist/cli/commands/push.js.map +1 -0
- package/dist/cli/commands/status.d.ts +4 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +100 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/context.d.ts +15 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/context.js +75 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/client/api.d.ts +19 -0
- package/dist/client/api.d.ts.map +1 -0
- package/dist/client/api.js +96 -0
- package/dist/client/api.js.map +1 -0
- package/dist/client/auth.d.ts +48 -0
- package/dist/client/auth.d.ts.map +1 -0
- package/dist/client/auth.js +165 -0
- package/dist/client/auth.js.map +1 -0
- package/dist/client/error.d.ts +22 -0
- package/dist/client/error.d.ts.map +1 -0
- package/dist/client/error.js +57 -0
- package/dist/client/error.js.map +1 -0
- package/dist/client/http.d.ts +31 -0
- package/dist/client/http.d.ts.map +1 -0
- package/dist/client/http.js +97 -0
- package/dist/client/http.js.map +1 -0
- package/dist/client/lock.d.ts +40 -0
- package/dist/client/lock.d.ts.map +1 -0
- package/dist/client/lock.js +104 -0
- package/dist/client/lock.js.map +1 -0
- package/dist/client/mapper.d.ts +37 -0
- package/dist/client/mapper.d.ts.map +1 -0
- package/dist/client/mapper.js +142 -0
- package/dist/client/mapper.js.map +1 -0
- package/dist/client/portable-text.d.ts +94 -0
- package/dist/client/portable-text.d.ts.map +1 -0
- package/dist/client/portable-text.js +428 -0
- package/dist/client/portable-text.js.map +1 -0
- package/dist/client/schemas.d.ts +1928 -0
- package/dist/client/schemas.d.ts.map +1 -0
- package/dist/client/schemas.js +123 -0
- package/dist/client/schemas.js.map +1 -0
- package/dist/db/cache.d.ts +60 -0
- package/dist/db/cache.d.ts.map +1 -0
- package/dist/db/cache.js +303 -0
- package/dist/db/cache.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/context.d.ts +14 -0
- package/dist/mcp/context.d.ts.map +1 -0
- package/dist/mcp/context.js +23 -0
- package/dist/mcp/context.js.map +1 -0
- package/dist/mcp/server.d.ts +12 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +106 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/profile.d.ts +6 -0
- package/dist/mcp/tools/profile.d.ts.map +1 -0
- package/dist/mcp/tools/profile.js +81 -0
- package/dist/mcp/tools/profile.js.map +1 -0
- package/dist/mcp/tools/sync-diff.d.ts +4 -0
- package/dist/mcp/tools/sync-diff.d.ts.map +1 -0
- package/dist/mcp/tools/sync-diff.js +115 -0
- package/dist/mcp/tools/sync-diff.js.map +1 -0
- package/dist/mcp/tools/sync-new.d.ts +4 -0
- package/dist/mcp/tools/sync-new.d.ts.map +1 -0
- package/dist/mcp/tools/sync-new.js +54 -0
- package/dist/mcp/tools/sync-new.js.map +1 -0
- package/dist/mcp/tools/sync-pull.d.ts +4 -0
- package/dist/mcp/tools/sync-pull.d.ts.map +1 -0
- package/dist/mcp/tools/sync-pull.js +180 -0
- package/dist/mcp/tools/sync-pull.js.map +1 -0
- package/dist/mcp/tools/sync-push.d.ts +4 -0
- package/dist/mcp/tools/sync-push.d.ts.map +1 -0
- package/dist/mcp/tools/sync-push.js +105 -0
- package/dist/mcp/tools/sync-push.js.map +1 -0
- package/dist/mcp/tools/sync-status.d.ts +4 -0
- package/dist/mcp/tools/sync-status.d.ts.map +1 -0
- package/dist/mcp/tools/sync-status.js +45 -0
- package/dist/mcp/tools/sync-status.js.map +1 -0
- package/dist/sync/bylines.d.ts +23 -0
- package/dist/sync/bylines.d.ts.map +1 -0
- package/dist/sync/bylines.js +112 -0
- package/dist/sync/bylines.js.map +1 -0
- package/dist/sync/diff.d.ts +16 -0
- package/dist/sync/diff.d.ts.map +1 -0
- package/dist/sync/diff.js +68 -0
- package/dist/sync/diff.js.map +1 -0
- package/dist/sync/media.d.ts +37 -0
- package/dist/sync/media.d.ts.map +1 -0
- package/dist/sync/media.js +191 -0
- package/dist/sync/media.js.map +1 -0
- package/dist/sync/merge.d.ts +36 -0
- package/dist/sync/merge.d.ts.map +1 -0
- package/dist/sync/merge.js +112 -0
- package/dist/sync/merge.js.map +1 -0
- package/dist/sync/new.d.ts +24 -0
- package/dist/sync/new.d.ts.map +1 -0
- package/dist/sync/new.js +75 -0
- package/dist/sync/new.js.map +1 -0
- package/dist/sync/orchestrator.d.ts +55 -0
- package/dist/sync/orchestrator.d.ts.map +1 -0
- package/dist/sync/orchestrator.js +222 -0
- package/dist/sync/orchestrator.js.map +1 -0
- package/dist/sync/preflight.d.ts +25 -0
- package/dist/sync/preflight.d.ts.map +1 -0
- package/dist/sync/preflight.js +128 -0
- package/dist/sync/preflight.js.map +1 -0
- package/dist/sync/pull.d.ts +67 -0
- package/dist/sync/pull.d.ts.map +1 -0
- package/dist/sync/pull.js +405 -0
- package/dist/sync/pull.js.map +1 -0
- package/dist/sync/push.d.ts +33 -0
- package/dist/sync/push.d.ts.map +1 -0
- package/dist/sync/push.js +432 -0
- package/dist/sync/push.js.map +1 -0
- package/dist/sync/status.d.ts +24 -0
- package/dist/sync/status.d.ts.map +1 -0
- package/dist/sync/status.js +85 -0
- package/dist/sync/status.js.map +1 -0
- package/dist/sync/taxonomies.d.ts +49 -0
- package/dist/sync/taxonomies.d.ts.map +1 -0
- package/dist/sync/taxonomies.js +220 -0
- package/dist/sync/taxonomies.js.map +1 -0
- package/dist/utils/config.d.ts +18 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +79 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/frontmatter.d.ts +115 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +147 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/dist/utils/naming.d.ts +28 -0
- package/dist/utils/naming.d.ts.map +1 -0
- package/dist/utils/naming.js +94 -0
- package/dist/utils/naming.js.map +1 -0
- package/dist/utils/profile.d.ts +72 -0
- package/dist/utils/profile.d.ts.map +1 -0
- package/dist/utils/profile.js +180 -0
- package/dist/utils/profile.js.map +1 -0
- package/dist/utils/version.d.ts +5 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +43 -0
- package/dist/utils/version.js.map +1 -0
- package/dist/utils/workspace.d.ts +22 -0
- package/dist/utils/workspace.d.ts.map +1 -0
- package/dist/utils/workspace.js +74 -0
- package/dist/utils/workspace.js.map +1 -0
- package/package.json +68 -4
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 SPORT-SERVICE RS★R
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import { z } from 'zod';
|
|
23
|
+
import { ApiError } from '../../client/error.js';
|
|
24
|
+
import { pushArticle, PushValidationError } from '../../sync/push.js';
|
|
25
|
+
export function registerSyncPushTool(server, resolveContext) {
|
|
26
|
+
server.tool('sync_push', {
|
|
27
|
+
filePath: z
|
|
28
|
+
.string()
|
|
29
|
+
.describe('Target markdown file path relative to workspace root (e.g. "posts/draft/20261103_01...-my-article.md")'),
|
|
30
|
+
allowPublish: z
|
|
31
|
+
.boolean()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Explicit confirmation required to change status from draft to published'),
|
|
34
|
+
profile: z.string().optional().describe('Target profile name for multi-site setups'),
|
|
35
|
+
}, async (args) => {
|
|
36
|
+
const ctx = resolveContext(args.profile);
|
|
37
|
+
const origin = ctx.origin;
|
|
38
|
+
try {
|
|
39
|
+
const res = await pushArticle(args.filePath, {
|
|
40
|
+
workspaceRoot: ctx.root,
|
|
41
|
+
cache: ctx.cache,
|
|
42
|
+
origin,
|
|
43
|
+
token: ctx.auth.token,
|
|
44
|
+
refreshToken: ctx.auth.refreshToken,
|
|
45
|
+
allowPublish: args.allowPublish,
|
|
46
|
+
});
|
|
47
|
+
if (res.conflict) {
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: 'text',
|
|
52
|
+
text: [
|
|
53
|
+
'⚠️ Conflict detected and could not be resolved automatically.',
|
|
54
|
+
`Conflict markers (<<<<<<< LOCAL ... ======= ... >>>>>>> REMOTE) have been inserted into "${res.filePath}".`,
|
|
55
|
+
'Please resolve the conflicting lines in the file and retry sync_push.',
|
|
56
|
+
].join('\n'),
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const warningLines = res.warnings && res.warnings.length > 0
|
|
62
|
+
? ['', '⚠️ Warnings:', ...res.warnings.map((w) => ` - ${w}`)]
|
|
63
|
+
: [];
|
|
64
|
+
return {
|
|
65
|
+
content: [
|
|
66
|
+
{
|
|
67
|
+
type: 'text',
|
|
68
|
+
text: [
|
|
69
|
+
`✅ Successfully pushed '${res.id}' to remote (${res.filePath}, rev: ${res.rev}).`,
|
|
70
|
+
...warningLines,
|
|
71
|
+
].join('\n'),
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
if (err instanceof PushValidationError) {
|
|
78
|
+
return {
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: 'text',
|
|
82
|
+
text: JSON.stringify({
|
|
83
|
+
error: 'PREFLIGHT_VALIDATION_FAILED',
|
|
84
|
+
message: err.message,
|
|
85
|
+
errors: err.errors,
|
|
86
|
+
}, null, 2),
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (err instanceof ApiError) {
|
|
92
|
+
return {
|
|
93
|
+
content: [
|
|
94
|
+
{
|
|
95
|
+
type: 'text',
|
|
96
|
+
text: err.toActionableString(),
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=sync-push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-push.js","sourceRoot":"","sources":["../../../src/mcp/tools/sync-push.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { ContextResolver } from '../context.js';
|
|
3
|
+
export declare function registerSyncStatusTool(server: McpServer, resolveContext: ContextResolver): void;
|
|
4
|
+
//# sourceMappingURL=sync-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-status.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/sync-status.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGrD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,GAAG,IAAI,CAyB/F"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 SPORT-SERVICE RS★R
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import { z } from 'zod';
|
|
23
|
+
import { getWorkspaceStatus } from '../../sync/status.js';
|
|
24
|
+
export function registerSyncStatusTool(server, resolveContext) {
|
|
25
|
+
server.tool('sync_status', {
|
|
26
|
+
collection: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Collection name to inspect (defaults to "posts")'),
|
|
30
|
+
profile: z.string().optional().describe('Target profile name for multi-site setups'),
|
|
31
|
+
}, async (args) => {
|
|
32
|
+
const ctx = resolveContext(args.profile);
|
|
33
|
+
const collection = args.collection || ctx.config.defaultCollection;
|
|
34
|
+
const status = getWorkspaceStatus(ctx.root, ctx.cache, collection);
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: 'text',
|
|
39
|
+
text: JSON.stringify(status, null, 2),
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=sync-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-status.js","sourceRoot":"","sources":["../../../src/mcp/tools/sync-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,UAAU,sBAAsB,CAAC,MAAiB,EAAE,cAA+B;IACvF,MAAM,CAAC,IAAI,CACT,aAAa,EACb;QACE,UAAU,EAAE,CAAC;aACV,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,kDAAkD,CAAC;QAC/D,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;KACrF,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,GAAG,CAAC,MAAM,CAAC,iBAAiB,CAAC;QACnE,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QAEnE,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;iBACtC;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type CacheDatabase } from '../db/cache.js';
|
|
2
|
+
export interface BylineMaps {
|
|
3
|
+
slugToId: Record<string, string>;
|
|
4
|
+
idToSlug: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export interface SyncBylinesOptions {
|
|
7
|
+
origin: string;
|
|
8
|
+
token: string;
|
|
9
|
+
refreshToken?: string;
|
|
10
|
+
cache: CacheDatabase;
|
|
11
|
+
fetchFn?: typeof fetch;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Synchronizes remote bylines from EmDash into the local SQLite database cache.
|
|
15
|
+
* Automatically called on pull commands to ensure local author metadata is always fresh.
|
|
16
|
+
* Gracefully falls back to existing cache if remote is unreachable.
|
|
17
|
+
*/
|
|
18
|
+
export declare function syncRemoteBylines(options: SyncBylinesOptions): Promise<BylineMaps>;
|
|
19
|
+
/**
|
|
20
|
+
* Returns cached slugToId and idToSlug maps directly from local DB.
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadLocalBylineMaps(cache: CacheDatabase): BylineMaps;
|
|
23
|
+
//# sourceMappingURL=bylines.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bylines.d.ts","sourceRoot":"","sources":["../../src/sync/bylines.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,gBAAgB,CAAC;AAGvE,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AA4CD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,CAAC,CAsDxF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,GAAG,UAAU,CAEpE"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 SPORT-SERVICE RS★R
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import { createAuthedFetch } from '../client/http.js';
|
|
23
|
+
/**
|
|
24
|
+
* Parses raw JSON-RPC result from EmDash /_emdash/api/mcp endpoint.
|
|
25
|
+
* Handles both Server-Sent Events (SSE) format ("data: {...}") and plain JSON responses.
|
|
26
|
+
*/
|
|
27
|
+
function parseMcpToolCallText(rawBody) {
|
|
28
|
+
const trimmed = rawBody.trim();
|
|
29
|
+
if (trimmed.startsWith('{')) {
|
|
30
|
+
try {
|
|
31
|
+
const json = JSON.parse(trimmed);
|
|
32
|
+
return json.result?.content?.[0]?.text || null;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Handle SSE data: lines
|
|
39
|
+
const dataLine = rawBody.split('\n').find((l) => l.startsWith('data:'));
|
|
40
|
+
if (dataLine) {
|
|
41
|
+
try {
|
|
42
|
+
const jsonStr = dataLine.replace(/^data:\s*/, '').trim();
|
|
43
|
+
const json = JSON.parse(jsonStr);
|
|
44
|
+
return json.result?.content?.[0]?.text || null;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Synchronizes remote bylines from EmDash into the local SQLite database cache.
|
|
54
|
+
* Automatically called on pull commands to ensure local author metadata is always fresh.
|
|
55
|
+
* Gracefully falls back to existing cache if remote is unreachable.
|
|
56
|
+
*/
|
|
57
|
+
export async function syncRemoteBylines(options) {
|
|
58
|
+
const fetchFn = createAuthedFetch(options);
|
|
59
|
+
try {
|
|
60
|
+
const mcpUrl = new URL('/_emdash/api/mcp', options.origin).toString();
|
|
61
|
+
const res = await fetchFn(mcpUrl, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: {
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
Accept: 'application/json, text/event-stream',
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify({
|
|
68
|
+
jsonrpc: '2.0',
|
|
69
|
+
id: 1,
|
|
70
|
+
method: 'tools/call',
|
|
71
|
+
params: {
|
|
72
|
+
name: 'byline_list',
|
|
73
|
+
arguments: { limit: 100 },
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
if (res.ok) {
|
|
78
|
+
const responseText = await res.text();
|
|
79
|
+
const toolText = parseMcpToolCallText(responseText);
|
|
80
|
+
if (toolText) {
|
|
81
|
+
const parsed = JSON.parse(toolText);
|
|
82
|
+
const rawItems = Array.isArray(parsed.items) ? parsed.items : [];
|
|
83
|
+
const records = [];
|
|
84
|
+
const now = Math.floor(Date.now() / 1000);
|
|
85
|
+
for (const item of rawItems) {
|
|
86
|
+
if (item.id && item.slug) {
|
|
87
|
+
records.push({
|
|
88
|
+
id: item.id,
|
|
89
|
+
slug: item.slug,
|
|
90
|
+
displayName: item.displayName || item.slug,
|
|
91
|
+
isGuest: Boolean(item.isGuest),
|
|
92
|
+
rawJson: JSON.stringify(item),
|
|
93
|
+
updatedAt: now,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
options.cache.saveBylines(records, true);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Non-fatal: if remote byline sync fails (e.g. offline, permissions), proceed with existing cache
|
|
103
|
+
}
|
|
104
|
+
return options.cache.getBylineMaps();
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Returns cached slugToId and idToSlug maps directly from local DB.
|
|
108
|
+
*/
|
|
109
|
+
export function loadLocalBylineMaps(cache) {
|
|
110
|
+
return cache.getBylineMaps();
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=bylines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bylines.js","sourceRoot":"","sources":["../../src/sync/bylines.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface DiffResult {
|
|
2
|
+
hasChanges: boolean;
|
|
3
|
+
unifiedDiff: string;
|
|
4
|
+
additions: number;
|
|
5
|
+
deletions: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Computes a unified-like diff between base text and local text without dumping full contents.
|
|
9
|
+
* Conserves LLM context tokens by outputting only changed regions with line context.
|
|
10
|
+
*/
|
|
11
|
+
export declare function computeContextDiff(baseText: string, localText: string, options?: {
|
|
12
|
+
contextLines?: number;
|
|
13
|
+
baseLabel?: string;
|
|
14
|
+
localLabel?: string;
|
|
15
|
+
}): DiffResult;
|
|
16
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/sync/diff.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3E,UAAU,CAiDZ"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 SPORT-SERVICE RS★R
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import { diffPatch } from 'node-diff3';
|
|
23
|
+
/**
|
|
24
|
+
* Computes a unified-like diff between base text and local text without dumping full contents.
|
|
25
|
+
* Conserves LLM context tokens by outputting only changed regions with line context.
|
|
26
|
+
*/
|
|
27
|
+
export function computeContextDiff(baseText, localText, options) {
|
|
28
|
+
if (baseText === localText) {
|
|
29
|
+
return {
|
|
30
|
+
hasChanges: false,
|
|
31
|
+
unifiedDiff: 'No changes detected.',
|
|
32
|
+
additions: 0,
|
|
33
|
+
deletions: 0,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const baseLines = baseText.split('\n');
|
|
37
|
+
const localLines = localText.split('\n');
|
|
38
|
+
const patches = diffPatch(baseLines, localLines);
|
|
39
|
+
const contextLines = options?.contextLines ?? 3;
|
|
40
|
+
const baseLabel = options?.baseLabel ?? 'base';
|
|
41
|
+
const localLabel = options?.localLabel ?? 'local';
|
|
42
|
+
let additions = 0;
|
|
43
|
+
let deletions = 0;
|
|
44
|
+
const output = [`--- ${baseLabel}`, `+++ ${localLabel}`];
|
|
45
|
+
for (const patch of patches) {
|
|
46
|
+
const b1 = patch.buffer1;
|
|
47
|
+
const b2 = patch.buffer2;
|
|
48
|
+
deletions += b1.length;
|
|
49
|
+
additions += b2.length;
|
|
50
|
+
// Output hunk header
|
|
51
|
+
output.push(`@@ -${b1.offset + 1},${b1.length} +${b2.offset + 1},${b2.length} @@`);
|
|
52
|
+
// Removed lines
|
|
53
|
+
for (const line of b1.chunk) {
|
|
54
|
+
output.push(`-${line}`);
|
|
55
|
+
}
|
|
56
|
+
// Added lines
|
|
57
|
+
for (const line of b2.chunk) {
|
|
58
|
+
output.push(`+${line}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
hasChanges: true,
|
|
63
|
+
unifiedDiff: output.join('\n'),
|
|
64
|
+
additions,
|
|
65
|
+
deletions,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/sync/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AASvC;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAgB,EAChB,SAAiB,EACjB,OAA4E;IAE5E,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO;YACL,UAAU,EAAE,KAAK;YACjB,WAAW,EAAE,sBAAsB;YACnC,SAAS,EAAE,CAAC;YACZ,SAAS,EAAE,CAAC;SACb,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,SAAS,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAEjD,MAAM,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,MAAM,CAAC;IAC/C,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,OAAO,CAAC;IAElD,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,MAAM,GAAa,CAAC,OAAO,SAAS,EAAE,EAAE,OAAO,UAAU,EAAE,CAAC,CAAC;IAEnE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC;QACzB,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC;QAEzB,SAAS,IAAI,EAAE,CAAC,MAAM,CAAC;QACvB,SAAS,IAAI,EAAE,CAAC,MAAM,CAAC;QAEvB,qBAAqB;QACrB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC;QAEnF,gBAAgB;QAChB,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QAC1B,CAAC;QAED,cAAc;QACd,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,OAAO;QACL,UAAU,EAAE,IAAI;QAChB,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;QAC9B,SAAS;QACT,SAAS;KACV,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface EmDashMediaItem {
|
|
2
|
+
id: string;
|
|
3
|
+
filename: string;
|
|
4
|
+
mimeType: string;
|
|
5
|
+
size: number;
|
|
6
|
+
width?: number | null;
|
|
7
|
+
height?: number | null;
|
|
8
|
+
alt?: string | null;
|
|
9
|
+
caption?: string | null;
|
|
10
|
+
url?: string;
|
|
11
|
+
storageKey?: string;
|
|
12
|
+
createdAt?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface PullMediaOptions {
|
|
15
|
+
workspaceRoot: string;
|
|
16
|
+
origin: string;
|
|
17
|
+
token: string;
|
|
18
|
+
refreshToken?: string;
|
|
19
|
+
limit?: number;
|
|
20
|
+
all?: boolean;
|
|
21
|
+
downloadFiles?: boolean;
|
|
22
|
+
prune?: boolean;
|
|
23
|
+
fetchFn?: typeof fetch;
|
|
24
|
+
}
|
|
25
|
+
export interface PullMediaResult {
|
|
26
|
+
total: number;
|
|
27
|
+
downloaded: number;
|
|
28
|
+
skipped: number;
|
|
29
|
+
deleted?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Pulls media metadata and files from EmDash API into media/ directory with cursor pagination.
|
|
33
|
+
* media/<id>-<filename>.yaml
|
|
34
|
+
* media/<id>-<filename>.<ext>
|
|
35
|
+
*/
|
|
36
|
+
export declare function pullMedia(options: PullMediaOptions): Promise<PullMediaResult>;
|
|
37
|
+
//# sourceMappingURL=media.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/sync/media.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAkLnF"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 SPORT-SERVICE RS★R
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import yaml from 'js-yaml';
|
|
25
|
+
import { formatDatePrefix, sanitizeTitleForFilename } from '../utils/frontmatter.js';
|
|
26
|
+
import { ApiError } from '../client/error.js';
|
|
27
|
+
import { createAuthedFetch } from '../client/http.js';
|
|
28
|
+
import { unwrapEnvelope } from '../client/api.js';
|
|
29
|
+
/**
|
|
30
|
+
* Pulls media metadata and files from EmDash API into media/ directory with cursor pagination.
|
|
31
|
+
* media/<id>-<filename>.yaml
|
|
32
|
+
* media/<id>-<filename>.<ext>
|
|
33
|
+
*/
|
|
34
|
+
export async function pullMedia(options) {
|
|
35
|
+
const fetchFn = createAuthedFetch(options);
|
|
36
|
+
const targetTotal = options.all ? Infinity : (options.limit ?? 300);
|
|
37
|
+
const downloadFiles = options.downloadFiles !== false;
|
|
38
|
+
const batchSize = 100;
|
|
39
|
+
const mediaDir = path.join(options.workspaceRoot, 'media');
|
|
40
|
+
fs.mkdirSync(mediaDir, { recursive: true });
|
|
41
|
+
const activeMediaBaseNames = new Set();
|
|
42
|
+
const activeMediaIds = new Set();
|
|
43
|
+
let totalProcessed = 0;
|
|
44
|
+
let downloadedCount = 0;
|
|
45
|
+
let skippedCount = 0;
|
|
46
|
+
let cursor = undefined;
|
|
47
|
+
while (totalProcessed < targetTotal) {
|
|
48
|
+
const remaining = targetTotal - totalProcessed;
|
|
49
|
+
const currentBatch = Math.min(remaining, batchSize);
|
|
50
|
+
const url = new URL(`/_emdash/api/media`, options.origin);
|
|
51
|
+
url.searchParams.set('limit', String(currentBatch));
|
|
52
|
+
if (cursor) {
|
|
53
|
+
url.searchParams.set('cursor', cursor);
|
|
54
|
+
}
|
|
55
|
+
const res = await fetchFn(url.toString(), {
|
|
56
|
+
method: 'GET',
|
|
57
|
+
headers: {
|
|
58
|
+
Authorization: `Bearer ${options.token}`,
|
|
59
|
+
Accept: 'application/json',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
throw new ApiError({
|
|
64
|
+
message: `Failed to list media: ${res.statusText}`,
|
|
65
|
+
code: 'MEDIA_LIST_FAILED',
|
|
66
|
+
status: res.status,
|
|
67
|
+
endpoint: url.toString(),
|
|
68
|
+
suggestion: 'Verify network connection and token permissions.',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const json = await res.json();
|
|
72
|
+
const data = unwrapEnvelope(json);
|
|
73
|
+
const rawItems = (Array.isArray(data.items) ? data.items : Array.isArray(data) ? data : []);
|
|
74
|
+
if (rawItems.length === 0) {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
for (const item of rawItems) {
|
|
78
|
+
const ext = path.extname(item.filename);
|
|
79
|
+
const baseWithoutExt = path.basename(item.filename, ext);
|
|
80
|
+
const sanitizedBase = sanitizeTitleForFilename(baseWithoutExt);
|
|
81
|
+
const datePrefix = formatDatePrefix(item.createdAt);
|
|
82
|
+
const baseName = `${datePrefix}${item.id}-${sanitizedBase}`;
|
|
83
|
+
const oldBaseName = `${item.id}-${sanitizedBase}`;
|
|
84
|
+
activeMediaBaseNames.add(baseName);
|
|
85
|
+
activeMediaBaseNames.add(oldBaseName);
|
|
86
|
+
activeMediaIds.add(item.id);
|
|
87
|
+
// Clean up old un-prefixed YAML if migrating
|
|
88
|
+
const oldYamlPath = path.join(mediaDir, `${oldBaseName}.yaml`);
|
|
89
|
+
const yamlPath = path.join(mediaDir, `${baseName}.yaml`);
|
|
90
|
+
if (oldYamlPath !== yamlPath && fs.existsSync(oldYamlPath)) {
|
|
91
|
+
try {
|
|
92
|
+
fs.unlinkSync(oldYamlPath);
|
|
93
|
+
}
|
|
94
|
+
catch { }
|
|
95
|
+
}
|
|
96
|
+
// 1. Write YAML metadata
|
|
97
|
+
const metaDoc = {
|
|
98
|
+
id: item.id,
|
|
99
|
+
filename: item.filename,
|
|
100
|
+
mimeType: item.mimeType,
|
|
101
|
+
size: item.size,
|
|
102
|
+
width: item.width ?? null,
|
|
103
|
+
height: item.height ?? null,
|
|
104
|
+
alt: item.alt ?? '',
|
|
105
|
+
caption: item.caption ?? '',
|
|
106
|
+
url: item.url ? new URL(item.url, options.origin).toString() : '',
|
|
107
|
+
createdAt: item.createdAt || '',
|
|
108
|
+
};
|
|
109
|
+
fs.writeFileSync(yamlPath, yaml.dump(metaDoc, { indent: 2, quotingType: '"', forceQuotes: false }), 'utf8');
|
|
110
|
+
// 2. Download binary file if enabled and url is present
|
|
111
|
+
if (downloadFiles && item.url) {
|
|
112
|
+
const fileExt = ext || (item.mimeType.includes('png') ? '.png' : '.jpg');
|
|
113
|
+
const oldFilePath = path.join(mediaDir, `${oldBaseName}${fileExt}`);
|
|
114
|
+
const targetFilePath = path.join(mediaDir, `${baseName}${fileExt}`);
|
|
115
|
+
// Migrate/rename old file to new name if already downloaded
|
|
116
|
+
if (oldFilePath !== targetFilePath && fs.existsSync(oldFilePath)) {
|
|
117
|
+
try {
|
|
118
|
+
const stat = fs.statSync(oldFilePath);
|
|
119
|
+
if (stat.size === item.size) {
|
|
120
|
+
fs.renameSync(oldFilePath, targetFilePath);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch { }
|
|
124
|
+
}
|
|
125
|
+
// Avoid re-downloading if already present and matches size
|
|
126
|
+
let shouldDownload = true;
|
|
127
|
+
if (fs.existsSync(targetFilePath)) {
|
|
128
|
+
const stat = fs.statSync(targetFilePath);
|
|
129
|
+
if (stat.size === item.size) {
|
|
130
|
+
shouldDownload = false;
|
|
131
|
+
skippedCount++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (shouldDownload) {
|
|
135
|
+
try {
|
|
136
|
+
const downloadUrl = new URL(item.url, options.origin).toString();
|
|
137
|
+
const fileRes = await fetchFn(downloadUrl, {
|
|
138
|
+
headers: {
|
|
139
|
+
Authorization: `Bearer ${options.token}`,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
if (fileRes.ok) {
|
|
143
|
+
const buffer = Buffer.from(await fileRes.arrayBuffer());
|
|
144
|
+
fs.writeFileSync(targetFilePath, buffer);
|
|
145
|
+
downloadedCount++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Ignore individual download failures
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
totalProcessed++;
|
|
154
|
+
if (totalProcessed >= targetTotal) {
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!data.nextCursor || typeof data.nextCursor !== 'string') {
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
cursor = data.nextCursor;
|
|
162
|
+
}
|
|
163
|
+
// Pruning logic for media: if --prune or --all is enabled, delete local media not present on remote
|
|
164
|
+
let deletedCount = 0;
|
|
165
|
+
if ((options.prune || options.all) && fs.existsSync(mediaDir)) {
|
|
166
|
+
const files = fs.readdirSync(mediaDir);
|
|
167
|
+
for (const file of files) {
|
|
168
|
+
// Check if file is related to any active media ID
|
|
169
|
+
const match = file.replace(/^\d{8}_/, '').match(/^([0-9A-Z]{26})/);
|
|
170
|
+
if (match) {
|
|
171
|
+
const id = match[1];
|
|
172
|
+
if (!activeMediaIds.has(id)) {
|
|
173
|
+
try {
|
|
174
|
+
fs.unlinkSync(path.join(mediaDir, file));
|
|
175
|
+
deletedCount++;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Ignore unlink errors
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
total: totalProcessed,
|
|
186
|
+
downloaded: downloadedCount,
|
|
187
|
+
skipped: skippedCount,
|
|
188
|
+
deleted: deletedCount,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=media.js.map
|