@cloudglab/confluence-cli 0.0.1
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/AGENTS.md +34 -0
- package/CHANGELOG.md +26 -0
- package/README.md +147 -0
- package/assets/readme/confluence-cli-hero.png +0 -0
- package/assets/readme/confluence-cli-hero.svg +7 -0
- package/assets/readme/prompts/01-cover-confluence-cli.md +61 -0
- package/dist/api/endpoints.d.ts +404 -0
- package/dist/api/endpoints.js +85 -0
- package/dist/api/index.d.ts +148 -0
- package/dist/api/index.js +143 -0
- package/dist/bin/confluence-reader.d.ts +2 -0
- package/dist/bin/confluence-reader.js +8 -0
- package/dist/bin/confluence-writer.d.ts +2 -0
- package/dist/bin/confluence-writer.js +8 -0
- package/dist/bin/confluence.d.ts +2 -0
- package/dist/bin/confluence.js +11 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +154 -0
- package/dist/core/api-provider.d.ts +3 -0
- package/dist/core/api-provider.js +13 -0
- package/dist/core/changelog.d.ts +7 -0
- package/dist/core/changelog.js +42 -0
- package/dist/core/cli-output.d.ts +16 -0
- package/dist/core/cli-output.js +318 -0
- package/dist/core/cli-registry.d.ts +20 -0
- package/dist/core/cli-registry.js +148 -0
- package/dist/core/command-groups.generated.d.ts +2 -0
- package/dist/core/command-groups.generated.js +88 -0
- package/dist/core/config.d.ts +5 -0
- package/dist/core/config.js +108 -0
- package/dist/core/http-error.d.ts +2 -0
- package/dist/core/http-error.js +4 -0
- package/dist/core/http.d.ts +28 -0
- package/dist/core/http.js +124 -0
- package/dist/core/inline-comment.d.ts +23 -0
- package/dist/core/inline-comment.js +27 -0
- package/dist/core/list-result.d.ts +14 -0
- package/dist/core/list-result.js +81 -0
- package/dist/core/manifest.d.ts +11 -0
- package/dist/core/manifest.js +42 -0
- package/dist/core/pagination.d.ts +26 -0
- package/dist/core/pagination.js +45 -0
- package/dist/core/roles.d.ts +4 -0
- package/dist/core/roles.js +12 -0
- package/dist/core/tool-registry.d.ts +9 -0
- package/dist/core/tool-registry.js +60 -0
- package/dist/core/validation.d.ts +2 -0
- package/dist/core/validation.js +10 -0
- package/dist/core/value.d.ts +2 -0
- package/dist/core/value.js +19 -0
- package/dist/core/write-guard.d.ts +25 -0
- package/dist/core/write-guard.js +49 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/install.d.ts +3 -0
- package/dist/install.js +407 -0
- package/dist/manifest.json +122 -0
- package/dist/tools/attachments.d.ts +2 -0
- package/dist/tools/attachments.js +46 -0
- package/dist/tools/content.d.ts +2 -0
- package/dist/tools/content.js +45 -0
- package/dist/tools/convert.d.ts +2 -0
- package/dist/tools/convert.js +63 -0
- package/dist/tools/init.d.ts +2 -0
- package/dist/tools/init.js +24 -0
- package/dist/tools/install.d.ts +2 -0
- package/dist/tools/install.js +52 -0
- package/dist/tools/labels.d.ts +2 -0
- package/dist/tools/labels.js +22 -0
- package/dist/tools/metadata.d.ts +2 -0
- package/dist/tools/metadata.js +26 -0
- package/dist/tools/rest.d.ts +2 -0
- package/dist/tools/rest.js +52 -0
- package/dist/tools/spaces.d.ts +2 -0
- package/dist/tools/spaces.js +18 -0
- package/dist/tools/transfer.d.ts +2 -0
- package/dist/tools/transfer.js +407 -0
- package/dist/types/common.d.ts +17 -0
- package/dist/types/common.js +2 -0
- package/dist/update-probe.d.ts +2 -0
- package/dist/update-probe.js +142 -0
- package/dist/utils/mark-metadata.d.ts +9 -0
- package/dist/utils/mark-metadata.js +16 -0
- package/dist/utils/markdown.d.ts +9 -0
- package/dist/utils/markdown.js +220 -0
- package/dist/utils/result.d.ts +3 -0
- package/dist/utils/result.js +7 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +2 -0
- package/docs/confluence-7.13.7-api.md +183 -0
- package/docs/index.html +608 -0
- package/docs/release.md +41 -0
- package/package.json +63 -0
- package/skills/confluence-cli/SKILL.md +63 -0
- package/skills/confluence-cli/reference/cli.md +36 -0
- package/skills/confluence-cli/reference/commands.md +41 -0
- package/skills/confluence-cli/reference/content.md +23 -0
- package/skills/confluence-cli/reference/overview.md +23 -0
- package/skills/confluence-cli/reference/rest.md +19 -0
- package/skills/confluence-cli/reference/transfer.md +27 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { getApi } from "../core/api-provider.js";
|
|
5
|
+
import { previewOrAssertWriteAllowed } from "../core/write-guard.js";
|
|
6
|
+
import { jsonResult, textResult } from "../utils/result.js";
|
|
7
|
+
export function registerAttachmentTools(registry) {
|
|
8
|
+
registry.tool("listAttachments", z.object({ id: z.coerce.string(), limit: z.number().int().positive().max(100).default(100) }), async ({ id, limit }) => {
|
|
9
|
+
return jsonResult(await getApi().listAttachments(id, limit));
|
|
10
|
+
}, "List page attachments");
|
|
11
|
+
registry.tool("uploadAttachment", z.object({ id: z.coerce.string(), file: z.string(), comment: z.string().optional(), minorEdit: z.boolean().default(true), confirm: z.boolean().default(false) }), async ({ id, file, comment, minorEdit, confirm }) => {
|
|
12
|
+
const preview = previewOrAssertWriteAllowed({ action: "uploadAttachment", confirm, payload: { id, file, comment, minorEdit } });
|
|
13
|
+
if (preview)
|
|
14
|
+
return jsonResult(preview);
|
|
15
|
+
return jsonResult(await getApi().uploadAttachment({ pageId: id, filename: basename(file), data: readFileSync(file), comment, minorEdit }));
|
|
16
|
+
}, "Upload an attachment to a page; requires confirm=true");
|
|
17
|
+
registry.tool("updateAttachment", z.object({ id: z.coerce.string(), attachmentId: z.coerce.string(), file: z.string(), comment: z.string().optional(), minorEdit: z.boolean().default(true), confirm: z.boolean().default(false) }), async ({ id, attachmentId, file, comment, minorEdit, confirm }) => {
|
|
18
|
+
const preview = previewOrAssertWriteAllowed({ action: "updateAttachment", confirm, payload: { id, attachmentId, file, comment, minorEdit } });
|
|
19
|
+
if (preview)
|
|
20
|
+
return jsonResult(preview);
|
|
21
|
+
return jsonResult(await getApi().updateAttachmentData({ pageId: id, attachmentId, filename: basename(file), data: readFileSync(file), comment, minorEdit }));
|
|
22
|
+
}, "Update attachment data; requires confirm=true");
|
|
23
|
+
registry.tool("downloadAttachment", z.object({ id: z.coerce.string(), attachmentId: z.coerce.string().optional(), title: z.string().optional(), outputDir: z.string().default(".") }).refine((input) => input.attachmentId || input.title, "Provide attachmentId or title"), async ({ id, attachmentId, title, outputDir }) => {
|
|
24
|
+
const targetDir = outputDir ?? ".";
|
|
25
|
+
const api = getApi();
|
|
26
|
+
const attachments = await api.listAttachments(id, 100);
|
|
27
|
+
const attachment = attachments.results.find((item) => matchesAttachment(item, attachmentId, title));
|
|
28
|
+
if (!attachment)
|
|
29
|
+
throw new Error("Attachment not found.");
|
|
30
|
+
const downloadPath = attachment._links?.download;
|
|
31
|
+
if (!downloadPath)
|
|
32
|
+
throw new Error("Attachment has no download link.");
|
|
33
|
+
mkdirSync(targetDir, { recursive: true });
|
|
34
|
+
const outputPath = join(targetDir, safeFileName(attachment.title));
|
|
35
|
+
const downloaded = await api.downloadAttachment(downloadPath);
|
|
36
|
+
writeFileSync(outputPath, downloaded.data);
|
|
37
|
+
return textResult(outputPath);
|
|
38
|
+
}, "Download one page attachment by id or title");
|
|
39
|
+
}
|
|
40
|
+
function matchesAttachment(attachment, attachmentId, title) {
|
|
41
|
+
return (attachmentId !== undefined && attachment.id === attachmentId) || (title !== undefined && attachment.title === title);
|
|
42
|
+
}
|
|
43
|
+
function safeFileName(value) {
|
|
44
|
+
return value.replace(/[\\/:*?"<>|]/g, "_");
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=attachments.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getApi } from "../core/api-provider.js";
|
|
3
|
+
import { previewOrAssertWriteAllowed } from "../core/write-guard.js";
|
|
4
|
+
import { jsonResult } from "../utils/result.js";
|
|
5
|
+
export function registerContentTools(registry) {
|
|
6
|
+
registry.tool("searchContent", z.object({ cql: z.string(), limit: z.number().int().positive().max(100).default(25) }), async ({ cql, limit }) => {
|
|
7
|
+
return jsonResult(await getApi().search(cql, limit));
|
|
8
|
+
}, "Search Confluence content with CQL");
|
|
9
|
+
registry.tool("getContent", z.object({ id: z.coerce.string(), expand: z.string().optional() }), async ({ id, expand }) => {
|
|
10
|
+
return jsonResult(await getApi().getContent(id, expand));
|
|
11
|
+
}, "Get one Confluence page/content by id");
|
|
12
|
+
registry.tool("findContent", z.object({ space: z.string().optional(), title: z.string().optional(), type: z.string().default("page"), limit: z.number().int().positive().max(100).default(25), expand: z.string().optional() }), async ({ space, title, type, limit, expand }) => {
|
|
13
|
+
return jsonResult(await getApi().findContent({ space, title, type, limit, expand }));
|
|
14
|
+
}, "Find content by title, space or type");
|
|
15
|
+
registry.tool("deleteContent", z.object({ id: z.coerce.string(), confirm: z.boolean().default(false) }), async ({ id, confirm }) => {
|
|
16
|
+
const preview = previewOrAssertWriteAllowed({ action: "deleteContent", confirm, payload: { id } });
|
|
17
|
+
if (preview)
|
|
18
|
+
return jsonResult(preview);
|
|
19
|
+
return jsonResult(await getApi().deleteContent(id));
|
|
20
|
+
}, "Delete Confluence content; requires confirm=true");
|
|
21
|
+
registry.tool("getPageChildren", z.object({ id: z.coerce.string(), type: z.string().optional(), expand: z.string().default("space,version"), limit: z.number().int().positive().max(100).default(25) }), async ({ id, type, expand, limit }) => {
|
|
22
|
+
return jsonResult(await getApi().getChildren(id, type, expand, limit));
|
|
23
|
+
}, "Get page children, optionally filtered by child type");
|
|
24
|
+
registry.tool("getComments", z.object({ id: z.coerce.string(), expand: z.string().default("body.storage,version"), limit: z.number().int().positive().max(100).default(25) }), async ({ id, expand, limit }) => {
|
|
25
|
+
return jsonResult(await getApi().getComments(id, expand, limit));
|
|
26
|
+
}, "Get page comments");
|
|
27
|
+
registry.tool("addComment", z.object({
|
|
28
|
+
id: z.coerce.string(),
|
|
29
|
+
body: z.string(),
|
|
30
|
+
inline: z.string().optional().describe("Selected text on the page to annotate (inline comment)"),
|
|
31
|
+
representation: z.enum(["wiki", "storage"]).default("wiki"),
|
|
32
|
+
confirm: z.boolean().default(false),
|
|
33
|
+
}), async ({ id, body, inline, representation, confirm }) => {
|
|
34
|
+
const bodyRepresentation = representation ?? "wiki";
|
|
35
|
+
const action = inline ? "addInlineComment" : "addComment";
|
|
36
|
+
const preview = previewOrAssertWriteAllowed({ action, confirm, payload: { id, inline: inline ?? undefined, representation: bodyRepresentation, bodyPreview: body.slice(0, 500) } });
|
|
37
|
+
if (preview)
|
|
38
|
+
return jsonResult(preview);
|
|
39
|
+
if (inline) {
|
|
40
|
+
return jsonResult(await getApi().addInlineComment({ pageId: id, selection: inline, body, representation: bodyRepresentation }));
|
|
41
|
+
}
|
|
42
|
+
return jsonResult(await getApi().addComment({ pageId: id, body, representation: bodyRepresentation }));
|
|
43
|
+
}, "Add a comment to a page; use --inline to annotate selected text; requires confirm=true");
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, extname, join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { convert as convertDiagram } from "@whitebite/diagram-converter";
|
|
5
|
+
import { markdownToWiki } from "../utils/markdown.js";
|
|
6
|
+
import { jsonResult, textResult } from "../utils/result.js";
|
|
7
|
+
export function registerConvertTools(registry) {
|
|
8
|
+
registry.tool("convertMarkdownToWiki", z.object({ file: z.string().optional(), text: z.string().optional() }).refine((input) => input.file || input.text, "Provide file or text"), ({ file, text }) => textResult(markdownToWiki(text ?? readFileSync(file, "utf8"))), "Convert Markdown to Confluence Wiki Markup");
|
|
9
|
+
registry.tool("convertMermaidToDrawio", z
|
|
10
|
+
.object({
|
|
11
|
+
file: z.string().optional(),
|
|
12
|
+
text: z.string().optional(),
|
|
13
|
+
output: z.string().optional(),
|
|
14
|
+
mode: z.enum(["auto", "mermaid", "markdown"]).default("auto"),
|
|
15
|
+
})
|
|
16
|
+
.refine((input) => input.file || input.text, "Provide file or text"), (input) => {
|
|
17
|
+
const sourceText = input.text ?? readFileSync(input.file, "utf8");
|
|
18
|
+
const mermaid = extractMermaidSource(sourceText, input.mode ?? "auto", input.file);
|
|
19
|
+
const converted = convertDiagram(mermaid, { from: "mermaid", to: "drawio", layout: { algorithm: "dagre" } });
|
|
20
|
+
const drawioXml = converted.output;
|
|
21
|
+
const outputPath = resolveOutputPath(input.output, input.file);
|
|
22
|
+
if (outputPath) {
|
|
23
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
24
|
+
writeFileSync(outputPath, drawioXml, "utf8");
|
|
25
|
+
}
|
|
26
|
+
return jsonResult({
|
|
27
|
+
outputPath,
|
|
28
|
+
bytes: Buffer.byteLength(drawioXml, "utf8"),
|
|
29
|
+
preview: outputPath ? undefined : drawioXml.slice(0, 2000),
|
|
30
|
+
warnings: converted.warnings ?? [],
|
|
31
|
+
});
|
|
32
|
+
}, "Convert Mermaid to draw.io .drawio file");
|
|
33
|
+
}
|
|
34
|
+
function extractMermaidSource(content, mode, file) {
|
|
35
|
+
if (mode === "mermaid")
|
|
36
|
+
return content.trim();
|
|
37
|
+
if (mode === "markdown" || isMarkdownFile(file) || /```\s*mermaid\b/i.test(content)) {
|
|
38
|
+
const block = findFirstMermaidBlock(content);
|
|
39
|
+
if (!block)
|
|
40
|
+
throw new Error("No mermaid fenced block found in markdown content.");
|
|
41
|
+
return block.trim();
|
|
42
|
+
}
|
|
43
|
+
return content.trim();
|
|
44
|
+
}
|
|
45
|
+
function findFirstMermaidBlock(content) {
|
|
46
|
+
const match = content.match(/```\s*mermaid\s*\n([\s\S]*?)```/i);
|
|
47
|
+
return match?.[1];
|
|
48
|
+
}
|
|
49
|
+
function isMarkdownFile(file) {
|
|
50
|
+
if (!file)
|
|
51
|
+
return false;
|
|
52
|
+
const suffix = extname(file).toLowerCase();
|
|
53
|
+
return suffix === ".md" || suffix === ".markdown" || suffix === ".mdown";
|
|
54
|
+
}
|
|
55
|
+
function resolveOutputPath(output, file) {
|
|
56
|
+
if (output)
|
|
57
|
+
return output;
|
|
58
|
+
if (!file)
|
|
59
|
+
return undefined;
|
|
60
|
+
const fileName = basename(file, extname(file));
|
|
61
|
+
return join(dirname(file), `${fileName}.drawio`);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=convert.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ConfluenceApi } from "../api/index.js";
|
|
3
|
+
import { loadConfluenceConfig, maskConfig, normalizeConfig, saveConfig } from "../core/config.js";
|
|
4
|
+
import { jsonResult } from "../utils/result.js";
|
|
5
|
+
export function registerInitTools(registry) {
|
|
6
|
+
registry.tool("initConfluence", z.object({
|
|
7
|
+
url: z.string().optional().describe("Confluence 根域名,如 https://cf.cloudglab.cn"),
|
|
8
|
+
pat: z.string().optional().describe("Personal Access Token (Confluence 7.13.7 推荐)"),
|
|
9
|
+
username: z.string().optional().describe("用户名(Basic Auth 兼容)"),
|
|
10
|
+
password: z.string().optional().describe("密码(Basic Auth 兼容)"),
|
|
11
|
+
save: z.boolean().optional().default(false).describe("是否写入 ~/.confluence/config.json"),
|
|
12
|
+
}), async (input) => {
|
|
13
|
+
const config = input.url || input.pat || input.username
|
|
14
|
+
? normalizeConfig({ url: input.url, personalToken: input.pat, username: input.username, password: input.password })
|
|
15
|
+
: loadConfluenceConfig();
|
|
16
|
+
if (input.save)
|
|
17
|
+
saveConfig(config);
|
|
18
|
+
// 验证连接有效
|
|
19
|
+
const api = new ConfluenceApi(config);
|
|
20
|
+
await api.getCurrentUser();
|
|
21
|
+
return jsonResult({ ok: true, saved: Boolean(input.save), config: maskConfig(config) });
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runInstallCommand, runUninstallCommand, runUpdateCommand } from "../install.js";
|
|
3
|
+
import { jsonResult } from "../utils/result.js";
|
|
4
|
+
const installSchema = z.object({
|
|
5
|
+
"skill-source": z.enum(["local", "git", "npm"]).optional().default("local").describe("技能来源"),
|
|
6
|
+
"skill-local-path": z.string().optional().describe("本地 skill 目录"),
|
|
7
|
+
"skip-config-check": z.boolean().optional().default(false).describe("跳过配置校验"),
|
|
8
|
+
"cli-only": z.boolean().optional().default(false).describe("只安装 CLI"),
|
|
9
|
+
"skill-only": z.boolean().optional().default(false).describe("只安装 skill"),
|
|
10
|
+
});
|
|
11
|
+
const uninstallSchema = z.object({
|
|
12
|
+
confirm: z.boolean().optional().default(false).describe("确认真实卸载"),
|
|
13
|
+
"keep-config": z.boolean().optional().default(false).describe("保留本地配置"),
|
|
14
|
+
"cli-only": z.boolean().optional().default(false).describe("只卸载 CLI"),
|
|
15
|
+
"skill-only": z.boolean().optional().default(false).describe("只卸载 skill"),
|
|
16
|
+
});
|
|
17
|
+
export function registerInstallTools(registry) {
|
|
18
|
+
registry.tool("install", installSchema, async (input) => {
|
|
19
|
+
const args = toInstallArgs(input);
|
|
20
|
+
await runInstallCommand(args);
|
|
21
|
+
return jsonResult({ ok: true, command: "install", args });
|
|
22
|
+
}, "Install CLI and skill");
|
|
23
|
+
registry.tool("update", installSchema, async (input) => {
|
|
24
|
+
const args = toInstallArgs(input);
|
|
25
|
+
await runUpdateCommand(args);
|
|
26
|
+
return jsonResult({ ok: true, command: "update", args });
|
|
27
|
+
}, "Update CLI and skill");
|
|
28
|
+
registry.tool("uninstall", uninstallSchema, async (input) => {
|
|
29
|
+
const args = toInstallArgs(input);
|
|
30
|
+
await runUninstallCommand(args);
|
|
31
|
+
return jsonResult({ ok: true, command: "uninstall", args });
|
|
32
|
+
}, "Uninstall CLI and skill");
|
|
33
|
+
registry.tool("remove", uninstallSchema, async (input) => {
|
|
34
|
+
const args = toInstallArgs(input);
|
|
35
|
+
await runUninstallCommand(args);
|
|
36
|
+
return jsonResult({ ok: true, command: "remove", args });
|
|
37
|
+
}, "Remove CLI and skill");
|
|
38
|
+
}
|
|
39
|
+
function toInstallArgs(input) {
|
|
40
|
+
const args = [];
|
|
41
|
+
for (const [key, value] of Object.entries(input)) {
|
|
42
|
+
if (typeof value === "undefined")
|
|
43
|
+
continue;
|
|
44
|
+
if (typeof value === "boolean") {
|
|
45
|
+
args.push(`--${key}`, value ? "true" : "false");
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
args.push(`--${key}`, String(value));
|
|
49
|
+
}
|
|
50
|
+
return args;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getApi } from "../core/api-provider.js";
|
|
3
|
+
import { previewOrAssertWriteAllowed } from "../core/write-guard.js";
|
|
4
|
+
import { jsonResult } from "../utils/result.js";
|
|
5
|
+
export function registerLabelTools(registry) {
|
|
6
|
+
registry.tool("getLabels", z.object({ id: z.coerce.string(), limit: z.number().int().positive().max(100).default(100) }), async ({ id, limit }) => {
|
|
7
|
+
return jsonResult(await getApi().getLabels(id, limit));
|
|
8
|
+
}, "Get labels for one page/content");
|
|
9
|
+
registry.tool("addLabels", z.object({ id: z.coerce.string(), labels: z.array(z.string()).min(1), confirm: z.boolean().default(false) }), async ({ id, labels, confirm }) => {
|
|
10
|
+
const preview = previewOrAssertWriteAllowed({ action: "addLabels", confirm, payload: { id, labels } });
|
|
11
|
+
if (preview)
|
|
12
|
+
return jsonResult(preview);
|
|
13
|
+
return jsonResult(await getApi().addLabels(id, labels));
|
|
14
|
+
}, "Add labels to one page/content; requires confirm=true");
|
|
15
|
+
registry.tool("deleteLabel", z.object({ id: z.coerce.string(), label: z.string(), confirm: z.boolean().default(false) }), async ({ id, label, confirm }) => {
|
|
16
|
+
const preview = previewOrAssertWriteAllowed({ action: "deleteLabel", confirm, payload: { id, label } });
|
|
17
|
+
if (preview)
|
|
18
|
+
return jsonResult(preview);
|
|
19
|
+
return jsonResult(await getApi().deleteLabel(id, label));
|
|
20
|
+
}, "Delete one label from page/content; requires confirm=true");
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=labels.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, extname } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { parseMarkdown } from "../utils/markdown.js";
|
|
5
|
+
import { createMarkMetadata, removeMarkMetadata } from "../utils/mark-metadata.js";
|
|
6
|
+
import { textResult } from "../utils/result.js";
|
|
7
|
+
export function registerMetadataTools(registry) {
|
|
8
|
+
registry.tool("generateMarkMetadata", z.object({
|
|
9
|
+
file: z.string(),
|
|
10
|
+
space: z.string(),
|
|
11
|
+
title: z.string().optional(),
|
|
12
|
+
parents: z.array(z.string()).optional(),
|
|
13
|
+
labels: z.array(z.string()).optional(),
|
|
14
|
+
attachments: z.array(z.string()).optional(),
|
|
15
|
+
write: z.boolean().default(false),
|
|
16
|
+
}), ({ file, space, title, parents, labels, attachments, write }) => {
|
|
17
|
+
const content = readFileSync(file, "utf8");
|
|
18
|
+
const parsed = parseMarkdown(content, basename(file, extname(file)));
|
|
19
|
+
const header = createMarkMetadata({ space, title: title ?? parsed.title, parents, labels, attachments });
|
|
20
|
+
if (!write)
|
|
21
|
+
return textResult(header);
|
|
22
|
+
writeFileSync(file, `${header}\n\n${removeMarkMetadata(content)}`);
|
|
23
|
+
return textResult(`Updated ${file}`);
|
|
24
|
+
}, "Generate or write mark-compatible metadata comments");
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=metadata.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CONFLUENCE_7_13_7_ENDPOINTS, findEndpoint } from "../api/endpoints.js";
|
|
3
|
+
import { getApi } from "../core/api-provider.js";
|
|
4
|
+
import { previewOrAssertWriteAllowed } from "../core/write-guard.js";
|
|
5
|
+
import { jsonResult } from "../utils/result.js";
|
|
6
|
+
const jsonRecordSchema = z.record(z.unknown());
|
|
7
|
+
export function registerRestTools(registry) {
|
|
8
|
+
registry.tool("listRestApis", z.object({ method: z.enum(["GET", "POST", "PUT", "DELETE"]).optional(), group: z.string().optional(), write: z.boolean().optional(), limit: z.number().int().positive().max(500).optional() }), ({ method, group, write, limit }) => {
|
|
9
|
+
const endpoints = CONFLUENCE_7_13_7_ENDPOINTS.filter((endpoint) => {
|
|
10
|
+
if (method && endpoint.method !== method)
|
|
11
|
+
return false;
|
|
12
|
+
if (group && endpoint.group !== group)
|
|
13
|
+
return false;
|
|
14
|
+
if (write !== undefined && endpoint.write !== write)
|
|
15
|
+
return false;
|
|
16
|
+
return true;
|
|
17
|
+
});
|
|
18
|
+
return jsonResult({ version: "7.13.7", total: endpoints.length, endpoints: limit ? endpoints.slice(0, limit) : endpoints });
|
|
19
|
+
}, "List Confluence 7.13.7 REST API endpoints");
|
|
20
|
+
registry.tool("callRestApi", z.object({
|
|
21
|
+
method: z.enum(["GET", "POST", "PUT", "DELETE"]),
|
|
22
|
+
path: z.string(),
|
|
23
|
+
pathParams: jsonRecordSchema.default({}),
|
|
24
|
+
query: jsonRecordSchema.default({}),
|
|
25
|
+
body: z.unknown().optional(),
|
|
26
|
+
confirm: z.boolean().default(false),
|
|
27
|
+
}), async (input) => {
|
|
28
|
+
const method = input.method;
|
|
29
|
+
const endpoint = findEndpoint(method, input.path);
|
|
30
|
+
if (!endpoint) {
|
|
31
|
+
throw new Error(`Unsupported Confluence 7.13.7 endpoint: ${method} ${input.path}. Use listRestApis to inspect supported templates.`);
|
|
32
|
+
}
|
|
33
|
+
const pathParams = input.pathParams ?? {};
|
|
34
|
+
const query = input.query ?? {};
|
|
35
|
+
const resolvedPath = applyPathParams(endpoint.path, pathParams);
|
|
36
|
+
if (endpoint.write) {
|
|
37
|
+
const preview = previewOrAssertWriteAllowed({ action: `callRestApi:${method} ${endpoint.path}`, confirm: input.confirm, payload: { method, path: endpoint.path, resolvedPath, query, body: input.body } });
|
|
38
|
+
if (preview)
|
|
39
|
+
return jsonResult(preview);
|
|
40
|
+
}
|
|
41
|
+
return jsonResult(await getApi().request(method, resolvedPath, query, input.body));
|
|
42
|
+
}, "Call any listed Confluence 7.13.7 REST API endpoint; writes require confirm=true");
|
|
43
|
+
}
|
|
44
|
+
function applyPathParams(path, params) {
|
|
45
|
+
return path.replace(/\{([^}]+)\}/g, (_match, key) => {
|
|
46
|
+
const value = params[key];
|
|
47
|
+
if (value === undefined || value === null || value === "")
|
|
48
|
+
throw new Error(`Missing path parameter: ${key}`);
|
|
49
|
+
return encodeURIComponent(String(value));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=rest.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getApi } from "../core/api-provider.js";
|
|
3
|
+
import { jsonResult } from "../utils/result.js";
|
|
4
|
+
export function registerSpaceTools(registry) {
|
|
5
|
+
registry.tool("listSpaces", z.object({ limit: z.number().int().positive().max(100).default(25) }), async ({ limit }) => {
|
|
6
|
+
return jsonResult(await getApi().listSpaces(limit));
|
|
7
|
+
}, "List Confluence spaces");
|
|
8
|
+
registry.tool("getSpace", z.object({ spaceKey: z.string() }), async ({ spaceKey }) => {
|
|
9
|
+
return jsonResult(await getApi().getSpace(spaceKey));
|
|
10
|
+
}, "Get one Confluence space by key");
|
|
11
|
+
registry.tool("getCurrentUser", z.object({}), async () => {
|
|
12
|
+
return jsonResult(await getApi().getCurrentUser());
|
|
13
|
+
}, "Get current authenticated Confluence user");
|
|
14
|
+
registry.tool("convertContentBody", z.object({ to: z.enum(["storage", "view", "export_view", "styled_view"]), value: z.string(), representation: z.enum(["wiki", "storage", "view"]).default("wiki") }), async ({ to, value, representation }) => {
|
|
15
|
+
return jsonResult(await getApi().convertBody(to, { value, representation: representation ?? "wiki" }));
|
|
16
|
+
}, "Convert Confluence content body representation");
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=spaces.js.map
|