@p11-core/cli 0.0.16 → 0.0.18
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/index.js +260 -7
- package/docs/index.md +5 -2
- package/docs/sharing.md +10 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3524,6 +3524,7 @@ var P11_HISTORY_LOCK_TIMEOUT_MS = 5e3;
|
|
|
3524
3524
|
var P11_HISTORY_DEFAULT_LIMIT = 20;
|
|
3525
3525
|
var P11_RUNTIME_VERSION = "0.0.1";
|
|
3526
3526
|
var P11_MANIFEST_FILE = "p11-manifest.json";
|
|
3527
|
+
var P11_SOCIAL_PREVIEW_IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp"]);
|
|
3527
3528
|
var cliPackageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
3528
3529
|
var docsTopics = /* @__PURE__ */ new Map([
|
|
3529
3530
|
["index", "docs/index.md"],
|
|
@@ -3699,6 +3700,22 @@ Environment:
|
|
|
3699
3700
|
);
|
|
3700
3701
|
});
|
|
3701
3702
|
allowLegacyParserBehavior(commentsCommand);
|
|
3703
|
+
const replyCommand = program2.command("reply").description("Reply to a comment on a read URL or id.").argument("[readUrl|readId]").argument("[commentId]").option("--name [name]", "Reply author name. Defaults to AI Agent.").option("--body [text]", "Plain-text reply body. Newlines are preserved.").option("--body-file [file]", "Read plain-text reply body from a file, or '-' for stdin.").option("--json", "Print the API response as JSON.").option("--version [n]", "Reply on a specific page version.").option("--api-url [url]", "Override the p11 API URL.").action(async (target, commentId, options2) => {
|
|
3704
|
+
await runCliAction(
|
|
3705
|
+
"reply",
|
|
3706
|
+
{
|
|
3707
|
+
has_target: Boolean(target),
|
|
3708
|
+
has_comment_id: Boolean(commentId),
|
|
3709
|
+
json: Boolean(options2.json),
|
|
3710
|
+
body: typeof options2.body === "string",
|
|
3711
|
+
body_file: typeof options2.bodyFile === "string",
|
|
3712
|
+
version: typeof options2.version === "string",
|
|
3713
|
+
api_url_override: typeof options2.apiUrl === "string"
|
|
3714
|
+
},
|
|
3715
|
+
() => reply(target, commentId, normalizeReplyOptions(options2))
|
|
3716
|
+
);
|
|
3717
|
+
});
|
|
3718
|
+
allowLegacyParserBehavior(replyCommand);
|
|
3702
3719
|
const deleteCommand = program2.command("delete").description("Delete a document and all of its versions and comments.").argument("[editUrl|editId]").option("--json", "Print the API response as JSON.").option("--api-url [url]", "Override the p11 API URL.").action(async (target, options2) => {
|
|
3703
3720
|
await runCliAction(
|
|
3704
3721
|
"delete",
|
|
@@ -3791,6 +3808,16 @@ function normalizeCommentsOptions(options) {
|
|
|
3791
3808
|
apiUrl: optionString(options.apiUrl, "--api-url requires a URL.")
|
|
3792
3809
|
};
|
|
3793
3810
|
}
|
|
3811
|
+
function normalizeReplyOptions(options) {
|
|
3812
|
+
return {
|
|
3813
|
+
json: options.json,
|
|
3814
|
+
name: optionString(options.name, "--name requires a value."),
|
|
3815
|
+
body: optionString(options.body, "--body requires reply text."),
|
|
3816
|
+
bodyFile: optionString(options.bodyFile, "--body-file requires a file path or '-'."),
|
|
3817
|
+
version: optionString(options.version, "--version requires a positive integer."),
|
|
3818
|
+
apiUrl: optionString(options.apiUrl, "--api-url requires a URL.")
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3794
3821
|
function normalizeDeleteOptions(options) {
|
|
3795
3822
|
return {
|
|
3796
3823
|
json: options.json,
|
|
@@ -3938,6 +3965,57 @@ async function comments(target, options) {
|
|
|
3938
3965
|
printComments(data);
|
|
3939
3966
|
}
|
|
3940
3967
|
}
|
|
3968
|
+
async function reply(target, commentId, options) {
|
|
3969
|
+
if (!target || !commentId) {
|
|
3970
|
+
throw new Error("Usage: p11 reply <readUrl|readId> <commentId> --body TEXT [--name NAME] [--json] [--version N]");
|
|
3971
|
+
}
|
|
3972
|
+
validateCommentId(commentId);
|
|
3973
|
+
const body = await replyBody(options);
|
|
3974
|
+
if (!body.trim()) throw new Error("Reply body is required. Use --body TEXT or --body-file FILE.");
|
|
3975
|
+
const version = options.version === void 0 ? void 0 : parseVersionFlag(String(options.version));
|
|
3976
|
+
const url = replyApiUrlForTarget(target, {
|
|
3977
|
+
apiUrl: options.apiUrl,
|
|
3978
|
+
version
|
|
3979
|
+
});
|
|
3980
|
+
const response = await fetchForOperation("Reply", url, {
|
|
3981
|
+
method: "POST",
|
|
3982
|
+
headers: {
|
|
3983
|
+
"content-type": "application/json"
|
|
3984
|
+
},
|
|
3985
|
+
body: JSON.stringify({
|
|
3986
|
+
name: options.name ?? "AI Agent",
|
|
3987
|
+
body,
|
|
3988
|
+
parentId: commentId
|
|
3989
|
+
})
|
|
3990
|
+
});
|
|
3991
|
+
const text = await response.text();
|
|
3992
|
+
if (!response.ok) {
|
|
3993
|
+
throw new Error(`Reply failed (${response.status}): ${responseErrorMessage(text)}`);
|
|
3994
|
+
}
|
|
3995
|
+
const data = JSON.parse(text);
|
|
3996
|
+
if (options.json) {
|
|
3997
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3998
|
+
return;
|
|
3999
|
+
}
|
|
4000
|
+
console.log(`Replied ${data.comment?.id ?? "comment"}`);
|
|
4001
|
+
if (data.comment?.parentId) console.log(`parentId: ${data.comment.parentId}`);
|
|
4002
|
+
if (data.comment?.createdAt) console.log(`createdAt: ${data.comment.createdAt}`);
|
|
4003
|
+
}
|
|
4004
|
+
async function replyBody(options) {
|
|
4005
|
+
if (options.body !== void 0 && options.bodyFile !== void 0) {
|
|
4006
|
+
throw new Error("Use either --body or --body-file, not both.");
|
|
4007
|
+
}
|
|
4008
|
+
if (options.body !== void 0) return options.body;
|
|
4009
|
+
if (options.bodyFile === "-") return readStdin();
|
|
4010
|
+
if (options.bodyFile !== void 0) return readFile(path.resolve(options.bodyFile), "utf8");
|
|
4011
|
+
return "";
|
|
4012
|
+
}
|
|
4013
|
+
async function readStdin() {
|
|
4014
|
+
let body = "";
|
|
4015
|
+
process.stdin.setEncoding("utf8");
|
|
4016
|
+
for await (const chunk of process.stdin) body += chunk;
|
|
4017
|
+
return body;
|
|
4018
|
+
}
|
|
3941
4019
|
async function deleteDocument(target, options) {
|
|
3942
4020
|
if (!target) {
|
|
3943
4021
|
throw new Error("Usage: p11 delete <editUrl|editId> [--json] [--api-url URL]");
|
|
@@ -4270,10 +4348,10 @@ function printComments(data) {
|
|
|
4270
4348
|
console.log(`[${comment.createdAt}]${target}${resolved} ${comment.name}`);
|
|
4271
4349
|
if (comment.quote) console.log(`> ${comment.quote}`);
|
|
4272
4350
|
console.log(comment.body);
|
|
4273
|
-
for (const
|
|
4274
|
-
const replyResolved =
|
|
4275
|
-
console.log(` [${
|
|
4276
|
-
console.log(` ${
|
|
4351
|
+
for (const reply2 of comment.replies ?? []) {
|
|
4352
|
+
const replyResolved = reply2.resolvedAt ? " [resolved]" : "";
|
|
4353
|
+
console.log(` [${reply2.createdAt}] reply${replyResolved} ${reply2.name}`);
|
|
4354
|
+
console.log(` ${reply2.body}`);
|
|
4277
4355
|
}
|
|
4278
4356
|
console.log("");
|
|
4279
4357
|
}
|
|
@@ -4339,7 +4417,12 @@ async function buildPageModule(inputFile, outDir) {
|
|
|
4339
4417
|
const pageSource = await readFile(inputFile, "utf8");
|
|
4340
4418
|
const pageAst = parsePageSource(pageSource);
|
|
4341
4419
|
validatePageAst(pageAst, inputFile);
|
|
4420
|
+
const pageMetadata = extractPageMetadata(pageAst, inputFile);
|
|
4421
|
+
rewriteRelativeModuleSpecifiers(pageAst, path.dirname(inputFile));
|
|
4422
|
+
await mkdir(tempDir, { recursive: true });
|
|
4342
4423
|
await writeFile(pagePath, addSourceLineAttributes(pageAst, inputFile));
|
|
4424
|
+
const metadataAssetTracker = createSocialPreviewAssetTracker(pageMetadata, tempDir);
|
|
4425
|
+
const pageImport = pageMetadata ? 'import Page, { metadata as __p11PageMetadata } from "./Page";' : 'import Page from "./Page";';
|
|
4343
4426
|
await writeFile(
|
|
4344
4427
|
entryPath,
|
|
4345
4428
|
[
|
|
@@ -4353,7 +4436,8 @@ async function buildPageModule(inputFile, outDir) {
|
|
|
4353
4436
|
" __P11_CREATE_TEXT_ANNOTATOR__: createTextAnnotator,",
|
|
4354
4437
|
" __P11_RANGE_TO_SELECTOR__: rangeToSelector",
|
|
4355
4438
|
"});",
|
|
4356
|
-
|
|
4439
|
+
pageImport,
|
|
4440
|
+
...pageMetadata ? ["void __p11PageMetadata;"] : [],
|
|
4357
4441
|
"",
|
|
4358
4442
|
'createRoot(document.getElementById("root")!).render(',
|
|
4359
4443
|
" <React.StrictMode>",
|
|
@@ -4387,7 +4471,7 @@ async function buildPageModule(inputFile, outDir) {
|
|
|
4387
4471
|
base: "./",
|
|
4388
4472
|
configFile: false,
|
|
4389
4473
|
logLevel: "warn",
|
|
4390
|
-
plugins: [react(), tailwindcss()],
|
|
4474
|
+
plugins: [react(), tailwindcss(), ...metadataAssetTracker.plugin ? [metadataAssetTracker.plugin] : []],
|
|
4391
4475
|
resolve: {
|
|
4392
4476
|
alias: {
|
|
4393
4477
|
"@p11-core/components/styles.css": componentStyles,
|
|
@@ -4406,6 +4490,7 @@ async function buildPageModule(inputFile, outDir) {
|
|
|
4406
4490
|
outDir: path.relative(tempDir, outDir),
|
|
4407
4491
|
emptyOutDir: true,
|
|
4408
4492
|
assetsDir: "assets",
|
|
4493
|
+
assetsInlineLimit: 0,
|
|
4409
4494
|
rollupOptions: {
|
|
4410
4495
|
input: {
|
|
4411
4496
|
index: "index.html"
|
|
@@ -4416,11 +4501,167 @@ async function buildPageModule(inputFile, outDir) {
|
|
|
4416
4501
|
} finally {
|
|
4417
4502
|
process.chdir(previousCwd);
|
|
4418
4503
|
}
|
|
4419
|
-
|
|
4504
|
+
const socialPreviewImage = pageMetadata?.imageSourcePath ? socialPreviewImageForMetadata(pageMetadata, metadataAssetTracker.fileName()) : await firstSocialPreviewImage(outDir);
|
|
4505
|
+
await writeFile(
|
|
4506
|
+
path.join(outDir, P11_MANIFEST_FILE),
|
|
4507
|
+
JSON.stringify(
|
|
4508
|
+
{
|
|
4509
|
+
runtimeVersion: P11_RUNTIME_VERSION,
|
|
4510
|
+
...pageMetadata?.title ? { title: pageMetadata.title } : {},
|
|
4511
|
+
...pageMetadata?.description ? { description: pageMetadata.description } : {},
|
|
4512
|
+
...socialPreviewImage ? { socialPreviewImage } : {}
|
|
4513
|
+
},
|
|
4514
|
+
null,
|
|
4515
|
+
2
|
|
4516
|
+
)
|
|
4517
|
+
);
|
|
4518
|
+
}
|
|
4519
|
+
function createSocialPreviewAssetTracker(metadata, buildRoot) {
|
|
4520
|
+
let fileName;
|
|
4521
|
+
return {
|
|
4522
|
+
plugin: socialPreviewAssetPlugin(metadata, buildRoot, (nextFileName) => {
|
|
4523
|
+
fileName = nextFileName;
|
|
4524
|
+
}),
|
|
4525
|
+
fileName: () => fileName
|
|
4526
|
+
};
|
|
4527
|
+
}
|
|
4528
|
+
function socialPreviewAssetPlugin(metadata, buildRoot, onAssetPath) {
|
|
4529
|
+
if (!metadata?.imageSourcePath) return null;
|
|
4530
|
+
const metadataImagePath = normalizeFilePath(realpathSync(metadata.imageSourcePath));
|
|
4531
|
+
return {
|
|
4532
|
+
name: "p11-social-preview-asset",
|
|
4533
|
+
generateBundle(_options, bundle) {
|
|
4534
|
+
const matches = Object.values(bundle).filter((output) => {
|
|
4535
|
+
if (output.type !== "asset") return false;
|
|
4536
|
+
return (output.originalFileNames ?? []).some((fileName) => originalFileNameMatches(fileName, metadataImagePath, buildRoot));
|
|
4537
|
+
});
|
|
4538
|
+
if (matches.length === 1) {
|
|
4539
|
+
onAssetPath(matches[0].fileName);
|
|
4540
|
+
return;
|
|
4541
|
+
}
|
|
4542
|
+
if (matches.length > 1) {
|
|
4543
|
+
throw new Error(`metadata.image matched multiple built assets for ${metadata.imageSourcePath}`);
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
};
|
|
4547
|
+
}
|
|
4548
|
+
function originalFileNameMatches(fileName, metadataImagePath, buildRoot) {
|
|
4549
|
+
const candidate = path.isAbsolute(fileName) ? fileName : path.resolve(buildRoot, fileName);
|
|
4550
|
+
try {
|
|
4551
|
+
return normalizeFilePath(realpathSync(candidate)) === metadataImagePath;
|
|
4552
|
+
} catch (_error) {
|
|
4553
|
+
return false;
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
function normalizeFilePath(filePath) {
|
|
4557
|
+
return path.normalize(filePath).split(path.sep).join("/");
|
|
4558
|
+
}
|
|
4559
|
+
function socialPreviewImageForMetadata(metadata, builtAssetPath) {
|
|
4560
|
+
if (builtAssetPath) return builtAssetPath;
|
|
4561
|
+
throw new Error(`metadata.image was not emitted as a built asset: ${metadata.imageSourcePath}`);
|
|
4562
|
+
}
|
|
4563
|
+
async function firstSocialPreviewImage(outDir) {
|
|
4564
|
+
const imagePaths = await socialPreviewImagesInDir(outDir);
|
|
4565
|
+
return imagePaths.sort()[0];
|
|
4566
|
+
}
|
|
4567
|
+
async function socialPreviewImagesInDir(dir, rootDir = dir) {
|
|
4568
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => null);
|
|
4569
|
+
if (!entries) return [];
|
|
4570
|
+
const images = [];
|
|
4571
|
+
for (const entry of entries) {
|
|
4572
|
+
const entryPath = path.join(dir, entry.name);
|
|
4573
|
+
if (entry.isDirectory()) {
|
|
4574
|
+
images.push(...await socialPreviewImagesInDir(entryPath, rootDir));
|
|
4575
|
+
continue;
|
|
4576
|
+
}
|
|
4577
|
+
if (!entry.isFile() || !P11_SOCIAL_PREVIEW_IMAGE_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) continue;
|
|
4578
|
+
images.push(path.relative(rootDir, entryPath).split(path.sep).join("/"));
|
|
4579
|
+
}
|
|
4580
|
+
return images;
|
|
4420
4581
|
}
|
|
4421
4582
|
function resolveBuildParentDir(buildDir) {
|
|
4422
4583
|
return path.resolve(buildDir || process.env.P11_BUILD_DIR || tmpdir());
|
|
4423
4584
|
}
|
|
4585
|
+
function rewriteRelativeModuleSpecifiers(ast, sourceDir) {
|
|
4586
|
+
for (const statement of ast.program.body) {
|
|
4587
|
+
if (statement.type === "ImportDeclaration" || statement.type === "ExportNamedDeclaration" || statement.type === "ExportAllDeclaration") {
|
|
4588
|
+
const source = statement.source;
|
|
4589
|
+
if (!source || !isRelativeModuleSpecifier(source.value)) continue;
|
|
4590
|
+
source.value = path.resolve(sourceDir, source.value);
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
function isRelativeModuleSpecifier(value) {
|
|
4595
|
+
return value.startsWith("./") || value.startsWith("../");
|
|
4596
|
+
}
|
|
4597
|
+
function extractPageMetadata(ast, inputFile) {
|
|
4598
|
+
const sourceDir = path.dirname(inputFile);
|
|
4599
|
+
const importSources = importedDefaultSpecifiers(ast);
|
|
4600
|
+
for (const statement of ast.program.body) {
|
|
4601
|
+
if (statement.type !== "ExportNamedDeclaration") continue;
|
|
4602
|
+
const declaration = statement.declaration;
|
|
4603
|
+
if (!declaration || declaration.type !== "VariableDeclaration") continue;
|
|
4604
|
+
for (const declarator of declaration.declarations) {
|
|
4605
|
+
if (declarator.id.type !== "Identifier" || declarator.id.name !== "metadata") continue;
|
|
4606
|
+
if (!declarator.init || declarator.init.type !== "ObjectExpression") {
|
|
4607
|
+
throw new Error("metadata export must be an object literal.");
|
|
4608
|
+
}
|
|
4609
|
+
return metadataFromObjectExpression(declarator.init, importSources, sourceDir);
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
return null;
|
|
4613
|
+
}
|
|
4614
|
+
function importedDefaultSpecifiers(ast) {
|
|
4615
|
+
const imports = /* @__PURE__ */ new Map();
|
|
4616
|
+
for (const statement of ast.program.body) {
|
|
4617
|
+
if (statement.type !== "ImportDeclaration") continue;
|
|
4618
|
+
for (const specifier of statement.specifiers) {
|
|
4619
|
+
if (specifier.type === "ImportDefaultSpecifier") {
|
|
4620
|
+
imports.set(specifier.local.name, statement.source.value);
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
}
|
|
4624
|
+
return imports;
|
|
4625
|
+
}
|
|
4626
|
+
function metadataFromObjectExpression(objectExpression, importSources, sourceDir) {
|
|
4627
|
+
const metadata = {};
|
|
4628
|
+
for (const property of objectExpression.properties) {
|
|
4629
|
+
if (property.type !== "ObjectProperty" || property.computed) continue;
|
|
4630
|
+
const key = objectPropertyName(property.key);
|
|
4631
|
+
if (!key || !["title", "description", "image"].includes(key)) continue;
|
|
4632
|
+
if (key === "title" || key === "description") {
|
|
4633
|
+
metadata[key] = metadataStringValue(property.value, key);
|
|
4634
|
+
continue;
|
|
4635
|
+
}
|
|
4636
|
+
metadata.imageSourcePath = metadataImageSourcePath(property.value, importSources, sourceDir);
|
|
4637
|
+
}
|
|
4638
|
+
return metadata;
|
|
4639
|
+
}
|
|
4640
|
+
function objectPropertyName(key) {
|
|
4641
|
+
if (key.type === "Identifier") return key.name;
|
|
4642
|
+
if (key.type === "StringLiteral") return key.value;
|
|
4643
|
+
return null;
|
|
4644
|
+
}
|
|
4645
|
+
function metadataStringValue(value, key) {
|
|
4646
|
+
if (value.type === "StringLiteral") return value.value.trim();
|
|
4647
|
+
if (value.type === "TemplateLiteral" && value.expressions.length === 0) {
|
|
4648
|
+
return value.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw).join("").trim();
|
|
4649
|
+
}
|
|
4650
|
+
throw new Error(`metadata.${key} must be a string literal.`);
|
|
4651
|
+
}
|
|
4652
|
+
function metadataImageSourcePath(value, importSources, sourceDir) {
|
|
4653
|
+
if (value.type !== "Identifier") {
|
|
4654
|
+
throw new Error("metadata.image must reference a default-imported PNG, JPEG, or WebP asset.");
|
|
4655
|
+
}
|
|
4656
|
+
const importSource = importSources.get(value.name);
|
|
4657
|
+
if (!importSource) {
|
|
4658
|
+
throw new Error("metadata.image must reference a default-imported PNG, JPEG, or WebP asset.");
|
|
4659
|
+
}
|
|
4660
|
+
if (!P11_SOCIAL_PREVIEW_IMAGE_EXTENSIONS.has(path.extname(importSource).toLowerCase())) {
|
|
4661
|
+
throw new Error("metadata.image must reference a PNG, JPEG, or WebP asset.");
|
|
4662
|
+
}
|
|
4663
|
+
return isRelativeModuleSpecifier(importSource) ? path.resolve(sourceDir, importSource) : importSource;
|
|
4664
|
+
}
|
|
4424
4665
|
function validatePageSource(code, inputFile = "page.tsx") {
|
|
4425
4666
|
validatePageAst(parsePageSource(code), inputFile);
|
|
4426
4667
|
}
|
|
@@ -4642,6 +4883,14 @@ function commentsApiUrlForTarget(value, options = {}) {
|
|
|
4642
4883
|
if (version !== void 0) url.searchParams.set("v", String(version));
|
|
4643
4884
|
return url;
|
|
4644
4885
|
}
|
|
4886
|
+
function replyApiUrlForTarget(value, options = {}) {
|
|
4887
|
+
const target = parseAccessTarget(value, ["read"], "a read URL with /r/<readId> or a read id", { parseVersion: true });
|
|
4888
|
+
const apiUrl = normalizeApiUrl(String(options.apiUrl ?? target.apiUrl ?? defaultApiUrl));
|
|
4889
|
+
const url = new URL(`/api/read/${encodeURIComponent(target.id)}/comments`, apiUrl);
|
|
4890
|
+
const version = options.version ?? target.version;
|
|
4891
|
+
if (version !== void 0) url.searchParams.set("v", String(version));
|
|
4892
|
+
return url;
|
|
4893
|
+
}
|
|
4645
4894
|
function deleteApiUrlForTarget(value, options = {}) {
|
|
4646
4895
|
const target = parseEditTarget(value);
|
|
4647
4896
|
const apiUrl = normalizeApiUrl(String(options.apiUrl ?? target.apiUrl ?? defaultApiUrl));
|
|
@@ -4682,6 +4931,9 @@ function validateAccessTarget(target, expected) {
|
|
|
4682
4931
|
const prefix = target.kind === "read" ? "read" : "edit";
|
|
4683
4932
|
if (!new RegExp(`^${prefix}_[A-Za-z0-9_-]{6,80}$`).test(target.id)) throw new Error(`Invalid ${expected}.`);
|
|
4684
4933
|
}
|
|
4934
|
+
function validateCommentId(commentId) {
|
|
4935
|
+
if (!/^cmt_[A-Za-z0-9_-]{8,64}$/.test(commentId)) throw new Error("Invalid comment id.");
|
|
4936
|
+
}
|
|
4685
4937
|
function normalizeApiUrl(value) {
|
|
4686
4938
|
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
4687
4939
|
}
|
|
@@ -4769,6 +5021,7 @@ export {
|
|
|
4769
5021
|
parseReadId,
|
|
4770
5022
|
publishOutputLines,
|
|
4771
5023
|
readHistoryEntries,
|
|
5024
|
+
replyApiUrlForTarget,
|
|
4772
5025
|
resolveBuildParentDir,
|
|
4773
5026
|
runCli,
|
|
4774
5027
|
validatePageSource
|
package/docs/index.md
CHANGED
|
@@ -11,10 +11,13 @@ p11 share <page.tsx>
|
|
|
11
11
|
p11 share <page.tsx> --edit-url <editUrl>
|
|
12
12
|
p11 history
|
|
13
13
|
p11 comments <readUrl|editUrl|readId|editId>
|
|
14
|
+
p11 reply <readUrl|readId> <commentId> --body "Reply text"
|
|
14
15
|
p11 delete <editUrl|editId>
|
|
15
16
|
```
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
Replies are plain text; newlines are preserved. Markdown and HTML are not rendered.
|
|
19
|
+
|
|
20
|
+
For `share`, `history`, `comments`, `reply`, and `delete`, add `--json` when scripting or when exact structured fields are needed.
|
|
18
21
|
|
|
19
22
|
## Docs Topics
|
|
20
23
|
|
|
@@ -32,4 +35,4 @@ p11 example all
|
|
|
32
35
|
p11 example all --output ./all.tsx
|
|
33
36
|
```
|
|
34
37
|
|
|
35
|
-
Use `p11 share --help`, `p11 comments --help`, `p11 delete --help`, and `p11 history --help` for command-specific flags.
|
|
38
|
+
Use `p11 share --help`, `p11 comments --help`, `p11 reply --help`, `p11 delete --help`, and `p11 history --help` for command-specific flags.
|
package/docs/sharing.md
CHANGED
|
@@ -26,6 +26,16 @@ p11 comments <readUrl|editUrl|readId|editId> --version 1
|
|
|
26
26
|
p11 comments <readUrl|editUrl|readId|editId> --output comments.json
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
Reply to a top-level comment:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
p11 reply <readUrl|readId> <commentId> --body "Reply text"
|
|
33
|
+
p11 reply <readUrl|readId> <commentId> --body-file reply.txt
|
|
34
|
+
p11 reply <readUrl|readId> <commentId> --body-file - --json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Replies are plain text; newlines are preserved. Markdown and HTML are not rendered.
|
|
38
|
+
|
|
29
39
|
Delete a shared document and all of its versions, comments, and replies:
|
|
30
40
|
|
|
31
41
|
```bash
|