
Shareone
- 1 installs
- 404 repo stars
- Updated August 5, 2026
- aiskillstore/marketplace
shareone is a skill that publishes local files to the ShareOne platform, returns a public share link, and manages downloads, settings, and comments.
About
shareone is a skill that publishes locally generated HTML, Markdown, TXT, PDF, Word, or PPTX files to the ShareOne platform and returns a public short link. It also downloads files, updates share settings such as passwords and watermarks, and pulls or processes comments from a ShareOne link. A developer or user invokes it to share generated content or manage an existing ShareOne link. The documentation is written in Chinese.
- Publish HTML, Markdown, PDF, Word, or PPTX to a public share link
- Add passwords, watermarks, and custom slugs to shares
- Download files and pull/process comments from ShareOne links
Shareone by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,983 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
shareone capabilities & compatibility
Requires a ShareOne API key; content hosted free for 90 days
- Capabilities
- file publishing · link generation · comment processing
- Use cases
- documentation · presentations
- Pricing
- Freemium
What shareone says it does
node scripts/upload_page.js <file>
node scripts/check_api_key.js
npx skills add https://github.com/aiskillstore/marketplace --skill shareoneAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 404 |
| Last updated | August 5, 2026 |
| Repository | aiskillstore/marketplace ↗ |
What it does
Publish a generated file to ShareOne and get a public link, or manage an existing share.
Who is it for?
Publishing generated documents to a public link and managing existing ShareOne shares
Skip if: Using the local shareone CLI unless the user explicitly asks for it
When should I use this skill?
The user wants to publish, share, generate a link, download a ShareOne file, or process its comments
What you get
A public ShareOne link for the file, with optional password, watermark, and comments.
- Public share link
- Downloaded files
- Processed comments
By the numbers
- Content hosted free for 90 days
Files
AI Agent 技能:发布到 ShareOne (shareone)
这个 Skill 允许 AI Agent(如 Openclaw 等)将当前生成的历史会话以及 HTML/Markdown/TXT/PDF/PPT 等文件发布到 ShareOne 线上托管服务,并为用户生成一个持久化的公网分享链接。
入口隔离规则
本 skill 和用户本机可能安装的 shareone CLI 是两个独立入口。除非用户明确要求“使用 CLI”或指定执行 shareone ... 命令,否则不要调用系统 PATH 中的 shareone 命令。
使用本 skill 时,所有 ShareOne 操作都必须调用本 skill 目录内的脚本,例如:
node scripts/check_api_key.js
node scripts/upload_page.js <file>
node scripts/shareone_upload.js <file>
node scripts/update_share_settings.js <share_link_or_id>
node scripts/shareone_api_request.js <api_path>即使 which shareone 能找到 CLI,也不要把自然语言的发布、下载、评论处理任务改走 CLI。
使用说明与触发条件
当用户表达出以下意图时,应主动使用此技能:
- "帮我把
index.html发布到 ShareOne" - "把我刚才生成的网页发布,给我个链接"
- "生成一个可分享的链接给我的团队看"
- "Upload this presentation to ShareOne and protect it with password 'secret'"
- "发布这个 PDF 到 ShareOne,并加上密码 1234"
- "把这个网页发布到 ShareOne,加上水印 '内部绝密'"
- "把这个网页发布到 ShareOne,链接叫 product-demo"
- "发布这份设计稿并开启协同评论模式"
- "用 shareone 分享上一轮对话"
- "把我刚才写的代码/大段文字分享出去"
- "Share your last response as a note"
- "帮我下载这个 ShareOne 链接的文件:https://shareone.app/s/xxx"
- "拉取一下这个链接的评论:https://shareone.app/s/xxx"
- "给这个 ShareOne 链接加水印:https://shareone.app/s/xxx"
- "根据这个链接的评论修改页面:https://shareone.app/s/xxx"
- "修改这个 ShareOne 链接的内容:https://shareone.app/s/xxx"
总规则:先路由,再读取子流程
入口文件只负责判断用户意图、判断环境、选择需要阅读的 workflow。不要一次性读取所有 workflows/*.md 文件;只读取当前任务需要的子文件。
所有需要 ShareOne API 的操作,都先运行:
node scripts/check_api_key.js用输出判断环境与凭据状态:
SUDOWORK_KEY_FOUND:当前在 Sudowork 中,且 Sudowork 已配置 ShareOne API Key。SUDOWORK_KEY_NOT_FOUND:当前在 Sudowork 中,但还没有设置 ShareOne API Key。KEY_FOUND:<api_key>:当前是普通 AI Agent 环境,且已找到 API Key。KEY_NOT_FOUND:当前是普通 AI Agent 环境,且没有找到 API Key。
术语固定:
- Sudowork:发布、评论、通用请求命令不要传
--api-key,凭证由 Auth Proxy secrets 注入。 - 普通 AI Agent:可以使用
--api-key、环境变量SHAREONE_API_KEY或本地凭证文件。
操作路由表
根据用户意图读取对应 workflow:
如果用户提供已有 ShareOne 链接、share_id 或自定义短链 slug,并且只要求修改水印、访问密码、自定义短链接或评论开关,必须优先读取 workflows/update-share-settings.md。这是元数据更新,不要按文件类型路由,不要下载源文件,不要使用 upload_page.js,不要重新上传内容。
其他发布任务先按目标文件类型路由,再按用户动作路由。文件类型优先级最高。
| 目标文件/内容类型 | 必须读取的 workflow |
|---|---|
.ppt、.pptx、.pdf、.doc、.docx、.png、.jpg、.jpeg、.gif、.zip 或其他二进制文件 | workflows/publish-binary-file.md |
.html、.md、.txt、对话内容、大段文本、代码块,或已经包装成 HTML 的内容 | workflows/publish-text-page.md |
| 包含图表、流程图、思维导图、时序图、甘特图等可视化内容的 HTML 页面 | workflows/publish-text-page.md(参考其中的 Mermaid.js 章节) |
| 用户意图 | 需要读取的 workflow |
|---|---|
| 已有 ShareOne 链接只修改水印、访问密码、自定义短链接或评论开关 | 先读 workflows/environment-and-credentials.md,再读 workflows/update-share-settings.md,最后读 workflows/result-and-errors.md |
| 发布、分享、生成链接、上线、分享上一轮对话、大段文本或代码 | 先读 workflows/environment-and-credentials.md,再按文件类型读 workflows/publish-text-page.md 或 workflows/publish-binary-file.md,最后读 workflows/result-and-errors.md |
| 删除、清除、移除、重置 ShareOne API Key | 读 workflows/delete-api-key.md |
| 下载 ShareOne 链接的文件或源内容 | 先读 workflows/environment-and-credentials.md,再读 workflows/download-file.md |
| 查看、拉取、总结 ShareOne 链接评论,但用户没有要求修改 | 先读 workflows/environment-and-credentials.md,再读 workflows/comments-view.md |
| 处理评论、根据评论修改页面、修复 ShareOne 链接内容 | 先读 workflows/environment-and-credentials.md,再读 workflows/comments-process.md,必要时读 workflows/publish-text-page.md,最后读 workflows/result-and-errors.md |
| 发布二进制文件,或更新已上传二进制文件的密码/水印 | 先读 workflows/environment-and-credentials.md,再读 workflows/publish-binary-file.md,最后读 workflows/result-and-errors.md |
ShareOne 链接与 share_id
当用户提供 ShareOne 链接时,可以从完整链接、/s/<share_id> 路径、share_id 或自定义短链 slug 中解析目标。下载任意用户允许下载的链接时,优先使用公开下载接口并把原始输入作为 ref。
/s/<share_id> 是最终给用户访问的分享链接,不是上传 API endpoint。不要把 /s/<share_id> 当作发布地址,也不要直接向 /s/<share_id> PUT/POST 文件。
share_id 可用于查看评论、处理评论、owner 下载源文件、文本页面内容 PUT 更新,以及已有链接的密码/水印/短链/评论开关元数据更新。非 owner 下载必须要求链接已开启允许下载;若接口返回 DOWNLOAD_NOT_ALLOWED,直接提示用户让链接 owner 先开启允许下载。
对已有链接只改水印、密码、自定义短链接或评论开关时,使用 update_share_settings.js。/s/<share_id> 和 /md/<share_id> 走页面设置更新;/pdf/<share_id>、/ppt/<share_id> 和 /word/<share_id> 走文件设置更新。裸 share_id 或 slug 由脚本先尝试页面 endpoint,必要时回退文件 endpoint。整个过程不要下载源文件。
如果目标是 .pptx、.ppt、.pdf、Word、图片、zip 等二进制文件,“发布这个文件”默认必须走文件上传 workflow 和 shareone_upload.js。不要因为会话里存在旧的 /s/<share_id> 就改走文本页面 PUT;二进制文件内容上传不能使用 upload_page.js。
如果你在当前会话中已经为同一个文本/HTML 文件生成过 ShareOne 链接,可复用之前的 share_id 执行文本页面 PUT 更新;否则执行首次创建。
不可跳过的阻塞步骤
以下步骤是阻塞性用户通知,不是可选说明。触发后必须先发给用户,再继续后续发布、下载、评论处理或最终回复。
- 如果
create_guest_key.js输出GUEST_KEY_CREATED:<api_key>,必须立即向用户发送临时 API Key、绑定账号链接和保存提醒。即使 key 已经自动保存到 Sudowork/Auth Proxy 或本地凭证文件,也不能省略该通知。 - 发送临时 API Key 通知前,不得继续执行原任务的上传、下载、评论处理命令。
- 本会话首次向用户展示生成的
share_url时,必须按workflows/result-and-errors.md提示所有未使用的高级功能:自定义短链接名称、访问密码、水印。 - 如果用户已经使用了自定义短链接、访问密码、水印中的某些能力,只提示剩余未使用的能力;如果三项都已使用,则不提示。
不要遗漏的全局约束
- 发布前必须完成凭据检查和必要的凭据配置。
- 每次发布前都必须展示以下安全提示,并等待用户明确回复“同意”或
agree后才能继续:
发布前安全提示:在将页面发布到公网前,请您确认该页面内容符合相关法律法规要求。禁止发布反动、涉政、暴力、色情、侵权或恶意代码。上传的内容将免费托管保留 90 天。
如果您的内容符合要求,请回复“同意”,我将为您发布。
- 发布成功后必须直接使用接口或脚本返回的
share_url,不要自行拼接分享链接;如果返回中包含backend_url,必须同时作为“备用链接”展示给用户。 - 如果用户要求开启评论、允许讨论或协同模式,才添加
--allow-comments true。默认不开启评论。 - 自定义短链接(slug):服务端会根据文件名自动生成可读的 slug(如
quarterly-report),客户端无需额外操作。如果用户明确要求”链接叫 xxx”、”自定义短链接 xxx”、”URL 后缀 xxx”,发布命令添加--slug xxx覆盖自动生成;slug 冲突时把服务端提示反馈给用户,不要静默改名。 - 评论处理必须形成闭环:认领、修改、重新发布、回复、关闭或 dismiss。
最终回复前检查清单
在回复用户前,逐项检查:
- 如果本轮创建了临时 API Key,是否已经把 API Key、绑定账号链接和保存提醒发给用户。
- 如果本轮发布内容,是否已经完成凭据检查,并在执行发布命令前获得用户明确回复“同意”或
agree。 - 如果发布成功,是否直接展示返回的
share_url,没有自行拼接链接。 - 如果返回中包含
backend_url,是否作为备用链接展示给用户。 - 如果返回中包含
custom_slug_warning或custom_slug_suggestions,是否展示给用户。 - 如果这是本会话首次展示生成的
share_url,是否提示所有未使用的高级功能。
const {
hasSudoworkApiKey,
isSudowork,
readLocalApiKey,
} = require('./shareone_client');
async function checkApiKey() {
if (isSudowork()) {
try {
const found = await hasSudoworkApiKey();
console.log(found ? 'SUDOWORK_KEY_FOUND' : 'SUDOWORK_KEY_NOT_FOUND');
} catch (_) {
console.log('SUDOWORK_KEY_NOT_FOUND');
}
return;
}
let apiKey = process.env.SHAREONE_API_KEY || readLocalApiKey();
if (apiKey) {
console.log(`KEY_FOUND:${apiKey}`);
return;
}
console.log("KEY_NOT_FOUND");
}
checkApiKey().catch((error) => {
console.error(`ERROR:${error.message}`);
process.exit(1);
});
const {
isSudowork,
requestPublicShareOneJson,
requestShareOneJson,
saveLocalApiKey,
saveSudoworkApiKey,
} = require('./shareone_client');
async function createGuestKey() {
try {
const requestJson = isSudowork() ? requestPublicShareOneJson : requestShareOneJson;
const result = await requestJson('/api/v1/agent-guest-key', {
method: 'POST',
authRequired: false,
});
if (result.api_key) {
if (isSudowork()) {
try {
await saveSudoworkApiKey(result.api_key);
} catch (error) {
console.log(`ERROR:SUDOWORK_AUTH_PROXY_SAVE_FAILED:${result.api_key}`);
console.log("Auth Proxy 设置 ShareOne API Key 失败。请前往 Sudowork 的密钥管理手动添加 API Key,操作路径:【远程连接】-【密钥管理】。");
if (error && error.message) {
console.log(`DETAIL:${error.message}`);
}
return;
}
} else {
saveLocalApiKey(result.api_key);
}
console.log(`GUEST_KEY_CREATED:${result.api_key}`);
return;
}
console.log("ERROR:INVALID_RESPONSE");
} catch (error) {
if (error.statusCode === 429) {
console.log("ERROR:RATE_LIMIT_EXCEEDED");
} else {
console.log(`ERROR:${error.message}`);
}
}
}
createGuestKey();
#!/usr/bin/env node
const {
deleteLocalApiKey,
deleteSudoworkApiKey,
isSudowork,
} = require('./shareone_client');
async function deleteApiKey() {
if (isSudowork()) {
await deleteSudoworkApiKey();
console.log("SUDOWORK_KEY_DELETED");
return;
}
const deleted = deleteLocalApiKey();
console.log(deleted ? "KEY_DELETED" : "KEY_NOT_FOUND");
}
deleteApiKey().catch((error) => {
console.error(`ERROR:${error.message}`);
process.exit(1);
});
#!/usr/bin/env node
const {
hasSudoworkApiKey,
isSudowork,
requestShareOneBuffer,
resolveDirectApiKey,
} = require('./shareone_client');
const args = process.argv.slice(2);
let ref = null;
let password = null;
let apiKey = null;
let publicOnly = false;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--ref') {
ref = args[++i];
} else if (args[i] === '--password') {
password = args[++i];
} else if (args[i] === '--api-key') {
apiKey = args[++i];
} else if (args[i] === '--public-only') {
publicOnly = true;
} else if (!args[i].startsWith('--') && !ref) {
ref = args[i];
}
}
if (!ref) {
console.error("Usage: node download_share.js <ref> [--password <password>] [--api-key <key>] [--public-only]");
process.exit(1);
}
if (isSudowork() && apiKey && !publicOnly) {
console.error("ERROR:SUDOWORK_MANAGED_KEY");
console.error("Sudowork 模式下不要传 --api-key;请通过本 skill 的 save_api_key.js 或 create_guest_key.js 设置 ShareOne API Key。");
process.exit(1);
}
function extractShareRef(value) {
const raw = String(value || '').trim();
if (!raw) return null;
try {
const parsed = raw.includes('://') ? new URL(raw) : null;
const path = parsed ? parsed.pathname : raw.split('?')[0].split('#')[0];
const parts = path.split('/').filter(Boolean);
if (parts.length === 0) return raw;
if (parts[0] === 'file' && parts.length >= 2) return parts[1];
if (parts[0] === 'api' && parts.includes('shares')) {
const index = parts.indexOf('shares');
return parts[index + 1] || raw;
}
return parts[parts.length - 1] || raw;
} catch (_) {
return raw;
}
}
async function tryOwnerDownload() {
if (publicOnly) return null;
let hasKey = Boolean(resolveDirectApiKey(apiKey));
if (isSudowork()) {
try {
hasKey = await hasSudoworkApiKey();
} catch (_) {
hasKey = false;
}
}
if (!hasKey) return null;
const shareRef = extractShareRef(ref);
if (!shareRef) return null;
try {
return await requestShareOneBuffer(`/api/v1/shares/${encodeURIComponent(shareRef)}/download`, {
method: 'GET',
apiKey,
authRequired: true,
});
} catch (error) {
if ([401, 403, 404].includes(error.statusCode)) return null;
throw error;
}
}
async function publicDownload() {
if (password !== null) {
const body = JSON.stringify({ ref, password });
return requestShareOneBuffer('/api/v1/public-download', {
method: 'POST',
authRequired: false,
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body),
},
}, body);
}
return requestShareOneBuffer(`/api/v1/public-download?ref=${encodeURIComponent(ref)}`, {
method: 'GET',
authRequired: false,
});
}
(async () => {
const ownerResult = await tryOwnerDownload();
const result = ownerResult || await publicDownload();
process.stdout.write(result.data);
})().catch((error) => {
let code = null;
try {
const parsed = JSON.parse(error.responseText || '{}');
const detail = parsed.detail || {};
code = typeof detail === 'string' ? detail : detail.code;
} catch (_) {
// Keep the original HTTP error if the response is not JSON.
}
if (code) {
console.error(`ERROR:${code}`);
} else {
console.error(`ERROR:${error.message}`);
}
process.exit(1);
});
const { isSudowork, saveLocalApiKey, saveSudoworkApiKey } = require('./shareone_client');
const apiKey = process.argv[2];
if (!apiKey) {
console.error("Please provide an API key.");
process.exit(1);
}
async function saveApiKey() {
if (isSudowork()) {
await saveSudoworkApiKey(apiKey);
console.log("SUDOWORK_KEY_SAVED");
return;
}
saveLocalApiKey(apiKey);
console.log("KEY_SAVED");
}
saveApiKey().catch((error) => {
if (isSudowork()) {
console.error("ERROR:SUDOWORK_AUTH_PROXY_SAVE_FAILED");
console.error("Auth Proxy 设置 ShareOne API Key 失败。请前往 Sudowork 的密钥管理手动添加 API Key,操作路径:【远程连接】-【密钥管理】。");
if (error && error.message) {
console.error(`DETAIL:${error.message}`);
}
process.exit(1);
}
console.error(`ERROR:${error.message}`);
process.exit(1);
});
#!/usr/bin/env node
const {
isSudowork,
printShareOneScriptError,
requestShareOneBuffer,
resolveDirectApiKey,
} = require('./shareone_client');
const args = process.argv.slice(2);
let method = 'GET';
let apiPath = null;
let data = null;
let apiKey = null;
let publicRequest = false;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--method') {
method = String(args[++i] || 'GET').toUpperCase();
} else if (args[i] === '--data') {
data = args[++i];
} else if (args[i] === '--api-key') {
apiKey = args[++i];
} else if (args[i] === '--public') {
publicRequest = true;
} else if (!args[i].startsWith('--') && !apiPath) {
apiPath = args[i];
}
}
if (!apiPath) {
console.error("Usage: node shareone_api_request.js <api_path> [--method GET|POST|PUT|DELETE] [--data '<json>'] [--api-key <key>] [--public]");
process.exit(1);
}
if (isSudowork() && apiKey && !publicRequest) {
console.error("ERROR:SUDOWORK_MANAGED_KEY");
console.error("Sudowork 模式下不要传 --api-key;请通过本 skill 的 save_api_key.js 或 create_guest_key.js 设置 ShareOne API Key。");
process.exit(1);
}
if (!publicRequest && !isSudowork() && !resolveDirectApiKey(apiKey)) {
console.error("ERROR:KEY_NOT_FOUND");
process.exit(1);
}
const headers = {};
let body = null;
if (data !== null) {
body = data;
headers['Content-Type'] = 'application/json';
headers['Content-Length'] = Buffer.byteLength(body);
}
requestShareOneBuffer(apiPath, {
method,
apiKey,
authRequired: !publicRequest,
headers,
}, body).then((res) => {
process.stdout.write(res.data);
}).catch((error) => {
printShareOneScriptError(error);
process.exit(1);
});
const http = require('http');
const https = require('https');
const os = require('os');
const fs = require('fs');
const path = require('path');
const DEFAULT_BASE_URL = 'https://shareone.app';
const CREDENTIALS_PATH = path.join(os.homedir(), '.shareone_credentials');
const SUDOWORK_SECRET_NAMESPACE = 'service:shareone';
const SUDOWORK_SECRET_KEY = 'X-API-Key';
function isSudowork() {
return Boolean(process.env.SUDOWORK_AUTH_PROXY_URL && process.env.SUDOWORK_AUTH_PROXY_TOKEN);
}
function getSudoworkBaseUrl() {
return process.env.SUDOWORK_AUTH_PROXY_BASE_URL || String(process.env.SUDOWORK_AUTH_PROXY_URL || '').replace(/\/proxy\/?$/, '');
}
function getBaseUrl() {
return process.env.SHAREONE_BASE_URL || DEFAULT_BASE_URL;
}
function getCredentialsPath() {
return CREDENTIALS_PATH;
}
function readLocalApiKey() {
if (!fs.existsSync(CREDENTIALS_PATH)) return null;
try {
const data = JSON.parse(fs.readFileSync(CREDENTIALS_PATH, 'utf8'));
return data && data.api_key ? data.api_key : null;
} catch (_) {
return null;
}
}
function resolveDirectApiKey(explicitApiKey) {
return explicitApiKey || process.env.SHAREONE_API_KEY || readLocalApiKey();
}
function saveLocalApiKey(apiKey) {
fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify({ api_key: apiKey }));
}
function deleteLocalApiKey() {
if (!fs.existsSync(CREDENTIALS_PATH)) return false;
fs.unlinkSync(CREDENTIALS_PATH);
return true;
}
function appendPath(baseUrl, apiPath) {
const trimmedBase = String(baseUrl).replace(/\/+$/, '');
const normalizedPath = apiPath.startsWith('/') ? apiPath : `/${apiPath}`;
return `${trimmedBase}${normalizedPath}`;
}
function requestBuffer(url, options = {}, body = null) {
return new Promise((resolve, reject) => {
const target = new URL(url);
const client = target.protocol === 'https:' ? https : http;
const req = client.request(target, {
method: options.method || 'GET',
headers: options.headers || {},
}, (res) => {
const chunks = [];
res.on('data', chunk => chunks.push(Buffer.from(chunk)));
res.on('end', () => {
const data = Buffer.concat(chunks);
const text = data.toString('utf8');
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve({ statusCode: res.statusCode, headers: res.headers, data, text });
return;
}
const error = new Error(`HTTP ${res.statusCode}: ${text}`);
error.statusCode = res.statusCode;
error.responseText = text;
reject(error);
});
});
req.on('error', reject);
if (options.timeoutMs) {
req.setTimeout(options.timeoutMs, () => {
req.destroy(new Error('Request timed out'));
});
}
if (body) req.write(body);
req.end();
});
}
function buildJsonRequestBody(payload) {
const body = payload === null ? null : JSON.stringify(payload);
const headers = { 'Content-Type': 'application/json' };
if (body !== null) headers['Content-Length'] = Buffer.byteLength(body);
return { body, headers };
}
async function requestJsonUrl(url, options = {}, payload = null) {
const { body, headers } = buildJsonRequestBody(payload);
const res = await requestBuffer(url, {
...options,
headers: {
...headers,
...(options.headers || {}),
},
}, body);
return JSON.parse(res.text);
}
async function requestPublicShareOneJson(apiPath, options = {}, payload = null) {
return requestJsonUrl(appendPath(getBaseUrl(), apiPath), options, payload);
}
function buildSudoworkSecretsUrl(pathSuffix = '') {
const baseUrl = getSudoworkBaseUrl();
if (!baseUrl) {
throw new Error('SUDOWORK_AUTH_PROXY_BASE_URL is not available');
}
return appendPath(baseUrl, `/secrets${pathSuffix}`);
}
async function requestSudoworkSecrets(pathSuffix = '', options = {}, payload = null) {
if (!isSudowork()) {
throw new Error('Sudowork environment is not available');
}
const { body, headers } = buildJsonRequestBody(payload);
const res = await requestBuffer(buildSudoworkSecretsUrl(pathSuffix), {
...options,
headers: {
...headers,
...(options.headers || {}),
Authorization: `Bearer ${process.env.SUDOWORK_AUTH_PROXY_TOKEN}`,
},
}, body);
return JSON.parse(res.text);
}
async function listSudoworkSecrets(namespace = SUDOWORK_SECRET_NAMESPACE) {
const query = `?namespace=${encodeURIComponent(namespace)}`;
const result = await requestSudoworkSecrets(query, { method: 'GET' }, null);
return Array.isArray(result.data) ? result.data : [];
}
async function hasSudoworkApiKey() {
const secrets = await listSudoworkSecrets(SUDOWORK_SECRET_NAMESPACE);
return secrets.some(secret => secret && secret.namespace === SUDOWORK_SECRET_NAMESPACE && secret.key === SUDOWORK_SECRET_KEY);
}
async function saveSudoworkApiKey(apiKey) {
const pathSuffix = `/${encodeURIComponent(SUDOWORK_SECRET_NAMESPACE)}/${encodeURIComponent(SUDOWORK_SECRET_KEY)}`;
return requestSudoworkSecrets(pathSuffix, { method: 'PUT' }, {
value: apiKey,
description: 'ShareOne API Key',
});
}
async function deleteSudoworkApiKey() {
const pathSuffix = `/${encodeURIComponent(SUDOWORK_SECRET_NAMESPACE)}/${encodeURIComponent(SUDOWORK_SECRET_KEY)}`;
return requestSudoworkSecrets(pathSuffix, { method: 'DELETE' }, null);
}
function buildShareOneRequest(apiPath, options = {}) {
const targetUrl = appendPath(getBaseUrl(), apiPath);
const headers = { ...(options.headers || {}) };
if (isSudowork() && options.authRequired !== false) {
delete headers['X-API-Key'];
delete headers['x-api-key'];
return {
url: process.env.SUDOWORK_AUTH_PROXY_URL,
options: {
...options,
headers: {
...headers,
Authorization: `Bearer ${process.env.SUDOWORK_AUTH_PROXY_TOKEN}`,
'X-Secret-Namespace': 'service:shareone',
'X-Remote-URL': targetUrl,
'X-Auth-Scheme': 'header',
'X-Auth-Header': 'X-API-Key',
'X-Secret-Key': 'X-API-Key'
},
},
};
}
if (options.authRequired !== false) {
const apiKey = resolveDirectApiKey(options.apiKey);
if (apiKey) headers['X-API-Key'] = apiKey;
}
return {
url: targetUrl,
options: {
...options,
headers,
},
};
}
async function requestShareOneBuffer(apiPath, options = {}, body = null) {
const built = buildShareOneRequest(apiPath, options);
return requestBuffer(built.url, built.options, body);
}
async function requestShareOneJson(apiPath, options = {}, payload = null) {
const { body, headers } = buildJsonRequestBody(payload);
const res = await requestShareOneBuffer(apiPath, {
...options,
headers: {
...headers,
...(options.headers || {}),
},
}, body);
return JSON.parse(res.text);
}
function getErrorDetail(error) {
const text = String(error && error.responseText ? error.responseText : '');
if (!text) return '';
try {
const parsed = JSON.parse(text);
return String(parsed.detail || parsed.message || text);
} catch (_) {
return text;
}
}
function isSudoworkMissingKeyError(error) {
if (!error) return false;
const detail = getErrorDetail(error);
if (error.statusCode === 502) {
return /secret|key|credential|not found|missing|未配置|不存在|缺少/i.test(detail || error.message || '');
}
if (error.statusCode !== 401) return false;
return /Missing API Key/i.test(detail);
}
function isAuthFailedError(error) {
if (!error) return false;
if (error.statusCode === 401 || error.statusCode === 403) return true;
const detail = getErrorDetail(error);
return /Invalid API Key|Inactive user|unauthorized|forbidden|权限不足|无效/i.test(detail || error.message || '');
}
function printShareOneScriptError(error) {
if (isSudowork() && isSudoworkMissingKeyError(error)) {
console.error("ERROR:SUDOWORK_KEY_NOT_FOUND");
console.error("请先运行 check_api_key.js,并按提示通过 save_api_key.js 或 create_guest_key.js 设置 ShareOne API Key。");
return;
}
if (isAuthFailedError(error)) {
console.error("ERROR:AUTH_FAILED");
console.error("API Key 无效或权限不足。");
return;
}
console.error(`ERROR:${error.message}`);
}
module.exports = {
DEFAULT_BASE_URL,
SUDOWORK_SECRET_KEY,
SUDOWORK_SECRET_NAMESPACE,
appendPath,
deleteLocalApiKey,
deleteSudoworkApiKey,
getBaseUrl,
getCredentialsPath,
hasSudoworkApiKey,
isSudowork,
isAuthFailedError,
isSudoworkMissingKeyError,
listSudoworkSecrets,
printShareOneScriptError,
readLocalApiKey,
requestBuffer,
requestPublicShareOneJson,
requestShareOneBuffer,
requestShareOneJson,
resolveDirectApiKey,
saveLocalApiKey,
saveSudoworkApiKey,
};
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const {
isSudowork,
printShareOneScriptError,
requestBuffer,
requestShareOneBuffer,
requestShareOneJson,
resolveDirectApiKey,
} = require('./shareone_client');
function getMimeType(filePath) {
const ext = path.extname(filePath).toLowerCase();
const mimeTypes = {
'.html': 'text/html',
'.htm': 'text/html',
'.md': 'text/markdown',
'.txt': 'text/plain',
'.pdf': 'application/pdf',
'.ppt': 'application/vnd.ms-powerpoint',
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'.doc': 'application/msword',
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.zip': 'application/zip'
};
return mimeTypes[ext] || 'application/octet-stream';
}
function buildMultipartBody(fields, filePath, filename, contentType) {
const boundary = '----ShareOneBoundary' + crypto.randomBytes(16).toString('hex');
const fileData = fs.readFileSync(filePath);
const bodyParts = [];
for (const [key, value] of Object.entries(fields || {})) {
if (value === undefined || value === null) continue;
bodyParts.push(Buffer.from(`--${boundary}\r\n`));
bodyParts.push(Buffer.from(`Content-Disposition: form-data; name="${key}"\r\n\r\n`));
bodyParts.push(Buffer.from(`${value}\r\n`));
}
bodyParts.push(Buffer.from(`--${boundary}\r\n`));
bodyParts.push(Buffer.from(`Content-Disposition: form-data; name="file"; filename="${filename}"\r\n`));
bodyParts.push(Buffer.from(`Content-Type: ${contentType}\r\n\r\n`));
bodyParts.push(fileData);
bodyParts.push(Buffer.from('\r\n'));
bodyParts.push(Buffer.from(`--${boundary}--\r\n`));
return { body: Buffer.concat(bodyParts), boundary };
}
async function uploadToAzure(uploadUrl, filePath, contentType) {
const fileData = fs.readFileSync(filePath);
await requestBuffer(uploadUrl, {
method: 'PUT',
headers: {
'x-ms-blob-type': 'BlockBlob',
'Content-Type': contentType,
'Content-Length': fileData.length
}
}, fileData);
}
async function uploadToS3(uploadUrl, uploadFields, filePath, filename, contentType) {
const { body, boundary } = buildMultipartBody(uploadFields, filePath, filename, contentType);
await requestBuffer(uploadUrl, {
method: 'POST',
headers: {
'Content-Type': `multipart/form-data; boundary=${boundary}`,
'Content-Length': body.length
}
}, body);
}
async function uploadMultipartFallback(filePath, filename, contentType, options) {
const fields = {};
if (options.password) fields.password = options.password;
if (options.watermark) fields.watermark = options.watermark;
if (options.slug) fields.custom_slug = options.slug;
const { body, boundary } = buildMultipartBody(fields, filePath, filename, contentType);
const res = await requestShareOneBuffer('/api/v1/files', {
method: 'POST',
apiKey: options.apiKey,
headers: {
'Content-Type': `multipart/form-data; boundary=${boundary}`,
'Content-Length': body.length
}
}, body);
return JSON.parse(res.text);
}
function shouldFallbackToMultipart(error) {
const detail = String(error.responseText || error.message || '');
return error.statusCode === 400 && detail.includes('Direct upload is only supported');
}
async function uploadFile(filePath, options) {
if (!fs.existsSync(filePath)) {
console.error(`Error: File not found: ${filePath}`);
process.exit(1);
}
if (isSudowork() && options.apiKey) {
console.error("ERROR:SUDOWORK_MANAGED_KEY");
console.error("Sudowork 模式下不要传 --api-key;请通过本 skill 的 save_api_key.js 或 create_guest_key.js 设置 ShareOne API Key。");
process.exit(1);
}
if (!isSudowork() && !resolveDirectApiKey(options.apiKey)) {
console.error("ERROR:KEY_NOT_FOUND");
process.exit(1);
}
const filename = options.filename || path.basename(filePath);
const contentType = options.contentType || getMimeType(filePath);
try {
const credential = await requestShareOneJson('/api/v1/files/credential', {
method: 'POST',
apiKey: options.apiKey,
}, {
filename: filename,
content_type: contentType,
custom_slug: options.slug || undefined
});
if (credential.upload_type === 'azure') {
await uploadToAzure(credential.upload_url, filePath, contentType);
} else {
await uploadToS3(credential.upload_url, credential.upload_fields || {}, filePath, filename, contentType);
}
const confirmPayload = {
share_id: credential.share_id,
filename: filename,
content_type: contentType
};
if (options.password) confirmPayload.password = options.password;
if (options.watermark) confirmPayload.watermark = options.watermark;
if (options.slug) confirmPayload.custom_slug = options.slug;
const finalRes = await requestShareOneJson('/api/v1/files/confirm', {
method: 'POST',
apiKey: options.apiKey,
}, confirmPayload);
console.log(JSON.stringify(finalRes));
return finalRes.share_url;
} catch (error) {
if (shouldFallbackToMultipart(error)) {
const finalRes = await uploadMultipartFallback(filePath, filename, contentType, options);
console.log(JSON.stringify(finalRes));
return finalRes.share_url;
}
throw error;
}
}
const args = process.argv.slice(2);
let filePath = null;
const options = {
apiKey: null,
filename: null,
contentType: null,
password: null,
watermark: null,
slug: null,
};
for (let i = 0; i < args.length; i++) {
if (args[i] === '--api-key') {
options.apiKey = args[++i];
} else if (args[i] === '--base-url') {
process.env.SHAREONE_BASE_URL = args[++i];
} else if (args[i] === '--filename') {
options.filename = args[++i];
} else if (args[i] === '--content-type') {
options.contentType = args[++i];
} else if (args[i] === '--password') {
options.password = args[++i];
} else if (args[i] === '--watermark') {
options.watermark = args[++i];
} else if (args[i] === '--slug') {
options.slug = args[++i];
} else if (!args[i].startsWith('--')) {
filePath = args[i];
}
}
if (!filePath) {
console.error("Usage: node shareone_upload.js <file_path> [--api-key <api_key>] [--base-url <base_url>] [--filename <name>] [--content-type <mime>] [--password <password>] [--watermark <watermark>] [--slug <slug>]");
process.exit(1);
}
uploadFile(filePath, options).catch((error) => {
printShareOneScriptError(error);
process.exit(1);
});
#!/usr/bin/env node
const {
getBaseUrl,
isSudowork,
printShareOneScriptError,
requestShareOneBuffer,
resolveDirectApiKey,
} = require('./shareone_client');
const args = process.argv.slice(2);
let ref = null;
let apiKey = null;
let dryRun = false;
let explicitBaseUrl = null;
const payload = {};
function usage() {
console.error('Usage: node update_share_settings.js <share_link_or_id> [--api-key <key>] [--base-url <url>] [--watermark <text>] [--password <pwd>] [--slug <slug>] [--allow-comments <true|false>] [--dry-run]');
}
function nextValue(index, flag) {
const value = args[index + 1];
if (value === undefined) {
console.error(`ERROR:MISSING_VALUE:${flag}`);
usage();
process.exit(1);
}
return value;
}
function parseBoolean(value, flag) {
if (value === 'true') return true;
if (value === 'false') return false;
console.error(`ERROR:INVALID_BOOLEAN:${flag}`);
console.error(`${flag} must be true or false.`);
process.exit(1);
}
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--api-key') {
apiKey = nextValue(i, arg);
i += 1;
} else if (arg === '--base-url') {
explicitBaseUrl = nextValue(i, arg);
process.env.SHAREONE_BASE_URL = explicitBaseUrl;
i += 1;
} else if (arg === '--watermark') {
payload.watermark = nextValue(i, arg);
i += 1;
} else if (arg === '--password') {
payload.password = nextValue(i, arg);
i += 1;
} else if (arg === '--slug' || arg === '--custom-slug') {
payload.custom_slug = nextValue(i, arg);
i += 1;
} else if (arg === '--allow-comments') {
payload.allow_comments = parseBoolean(nextValue(i, arg), arg);
i += 1;
} else if (arg === '--dry-run') {
dryRun = true;
} else if (!arg.startsWith('--') && !ref) {
ref = arg;
} else {
console.error(`ERROR:UNKNOWN_ARGUMENT:${arg}`);
usage();
process.exit(1);
}
}
if (!ref) {
usage();
process.exit(1);
}
if (Object.keys(payload).length === 0) {
console.error('ERROR:NO_SETTINGS_PROVIDED');
console.error('Provide at least one of --watermark, --password, --slug, or --allow-comments.');
process.exit(1);
}
if (isSudowork() && apiKey && !dryRun) {
console.error('ERROR:SUDOWORK_MANAGED_KEY');
console.error('Sudowork 模式下不要传 --api-key;请通过本 skill 的 save_api_key.js 或 create_guest_key.js 设置 ShareOne API Key。');
process.exit(1);
}
if (!dryRun && !isSudowork() && !resolveDirectApiKey(apiKey)) {
console.error('ERROR:KEY_NOT_FOUND');
process.exit(1);
}
function parseRef(input) {
const raw = String(input || '').trim();
let path = raw.split('?')[0].split('#')[0];
try {
if (raw.includes('://')) {
const parsed = new URL(raw);
if (!explicitBaseUrl) {
process.env.SHAREONE_BASE_URL = `${parsed.protocol}//${parsed.host}`;
}
path = parsed.pathname;
}
} catch (_) {
path = raw.split('?')[0].split('#')[0];
}
const parts = path.split('/').filter(Boolean);
const knownPrefixes = new Set(['s', 'md', 'pdf', 'ppt', 'word']);
if (parts.length >= 2 && knownPrefixes.has(parts[0])) {
return { prefix: parts[0], shareRef: parts[1] };
}
return { prefix: null, shareRef: parts[parts.length - 1] || raw };
}
function endpointForPrefix(prefix, shareRef) {
const encodedRef = encodeURIComponent(shareRef);
if (prefix === 's' || prefix === 'md') {
return `/api/v1/pages/${encodedRef}`;
}
if (prefix === 'pdf' || prefix === 'ppt' || prefix === 'word') {
return `/api/v1/files/${encodedRef}`;
}
return null;
}
function buildRequestBody() {
const body = JSON.stringify(payload);
return {
body,
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body),
},
};
}
function shouldRetryAsFile(error) {
const text = `${error && error.message ? error.message : ''}\n${error && error.responseText ? error.responseText : ''}`;
return error && error.statusCode === 400 && /only for HTML\/Markdown|Use \/api\/v1\/files|HTML\/Markdown pages/i.test(text);
}
async function putSettings(apiPath) {
const { body, headers } = buildRequestBody();
return requestShareOneBuffer(apiPath, {
method: 'PUT',
apiKey,
headers,
}, body);
}
(async () => {
const parsed = parseRef(ref);
const explicitApiPath = endpointForPrefix(parsed.prefix, parsed.shareRef);
const pageApiPath = `/api/v1/pages/${encodeURIComponent(parsed.shareRef)}`;
const fileApiPath = `/api/v1/files/${encodeURIComponent(parsed.shareRef)}`;
if (dryRun) {
const result = {
method: 'PUT',
api_path: explicitApiPath || pageApiPath,
payload,
base_url: getBaseUrl(),
};
if (!explicitApiPath) {
result.fallback_api_path = fileApiPath;
}
console.log(JSON.stringify(result, null, 2));
return;
}
if (explicitApiPath) {
const res = await putSettings(explicitApiPath);
process.stdout.write(res.data);
return;
}
try {
const res = await putSettings(pageApiPath);
process.stdout.write(res.data);
} catch (error) {
if (!shouldRetryAsFile(error)) {
throw error;
}
const res = await putSettings(fileApiPath);
process.stdout.write(res.data);
}
})().catch((error) => {
printShareOneScriptError(error);
process.exit(1);
});
const fs = require('fs');
const path = require('path');
const {
isSudowork,
printShareOneScriptError,
requestShareOneBuffer,
resolveDirectApiKey,
} = require('./shareone_client');
const args = process.argv.slice(2);
let filePath = null;
let apiKey = null;
let filename = null;
let password = null;
let watermark = null;
let shareId = null;
let allowComments = null;
let slug = null;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--api-key') {
apiKey = args[++i];
} else if (args[i] === '--filename') {
filename = args[++i];
} else if (args[i] === '--password') {
password = args[++i];
} else if (args[i] === '--watermark') {
watermark = args[++i];
} else if (args[i] === '--share-id') {
shareId = args[++i];
} else if (args[i] === '--slug') {
slug = args[++i];
} else if (args[i] === '--allow-comments') {
allowComments = args[++i] === 'true';
} else if (!args[i].startsWith('--')) {
filePath = args[i];
}
}
if (!filePath) {
console.error("Usage: node upload_page.js <file_path> [--api-key <key>] [--filename <name>] [--password <pwd>] [--watermark <wm>] [--share-id <id>] [--slug <slug>] [--allow-comments <true|false>]");
process.exit(1);
}
if (!filename) {
filename = path.basename(filePath);
}
if (isSudowork() && apiKey) {
console.error("ERROR:SUDOWORK_MANAGED_KEY");
console.error("Sudowork 模式下不要传 --api-key;请通过本 skill 的 save_api_key.js 或 create_guest_key.js 设置 ShareOne API Key。");
process.exit(1);
}
if (!isSudowork() && !resolveDirectApiKey(apiKey)) {
console.error("ERROR:KEY_NOT_FOUND");
process.exit(1);
}
async function uploadPage() {
const content = fs.readFileSync(filePath, "utf-8");
const payload = {
filename: filename,
html_content: content
};
if (password !== null) payload.password = password;
if (watermark !== null) payload.watermark = watermark;
if (slug !== null) payload.custom_slug = slug;
if (allowComments !== null) {
payload.allow_comments = allowComments;
}
const data = JSON.stringify(payload);
const urlPath = shareId
? `/api/v1/pages/${shareId}`
: '/api/v1/pages';
const method = shareId ? 'PUT' : 'POST';
const res = await requestShareOneBuffer(urlPath, {
method: method,
apiKey,
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data)
}
}, data);
if (shareId) {
await verifyUpdatedContent(shareId, content);
}
console.log(res.text);
}
async function verifyUpdatedContent(updatedShareId, expectedContent) {
const res = await requestShareOneBuffer(`/api/v1/shares/${encodeURIComponent(updatedShareId)}/download`, {
method: 'GET',
apiKey,
headers: {
Accept: '*/*',
},
});
if (res.data.toString('utf8') !== expectedContent) {
throw new Error('UPDATE_VERIFY_FAILED: server accepted the update but source content did not match the uploaded file');
}
}
uploadPage().catch((error) => {
printShareOneScriptError(error);
process.exit(1);
});
{
"schema_version": "2.0",
"meta": {
"generated_at": "2026-06-09T02:29:43.839Z",
"slug": "sudoprivacy-shareone",
"source_url": "https://github.com/sudoprivacy/shareone-skill/tree/main/",
"source_ref": "main",
"model": "claude",
"analysis_version": "3.0.0",
"source_type": "community",
"content_hash": "33680c1e7a8c3824c55f7e4dde48a52e0d1f30ba24d7887a18c149894b2e7eaf",
"tree_hash": "d238f7c9737b39482ca68f8cd9dfbef7729361c11aec259f07947a6064eed00d"
},
"skill": {
"name": "shareone",
"description": "Publish locally generated HTML, Markdown, TXT, PDF, Word, or PPTX to the ShareOne platform and generate public share links. Also handles downloading, modifying, and processing comments on existing ShareOne links.",
"summary": "Publish files to ShareOne and manage share links, comments, and credentials from AI agent conversations.",
"icon": "📦",
"version": "1.0.4",
"author": "sudoprivacy",
"license": "MIT",
"category": "productivity",
"tags": [
"file-sharing",
"publish",
"share-links",
"shareone",
"document-hosting"
],
"supported_tools": [
"claude",
"codex",
"claude-code"
],
"risk_factors": [
"scripts",
"network",
"filesystem",
"env_access"
]
},
"security_audit": {
"risk_level": "low",
"is_blocked": false,
"safe_to_publish": true,
"summary": "Evaluation of static findings: the 442 'Ruby/shell backtick execution' matches are false positives from inline code formatting in markdown documentation (SKILL.md and workflows/*.md), not actual shell execution. The 'weak cryptographic algorithm' and 'high file entropy' findings are also false positives triggered by URLs, code samples, and base64-looking strings in markdown. The 'dangerous combination' heuristic warning reflects the skill's legitimate need for network, filesystem, and credential access to call the ShareOne API. The actual Node.js scripts are clean, use standard modules (http, https, fs, crypto), rely on HTTPS for all API calls to the documented shareone.app endpoint, and handle the API key through env var or local file rather than hardcoding it. No eval, child_process, or shell injection vectors were found.",
"risk_factor_evidence": [
{
"factor": "external_commands",
"evidence": [
{
"file": "scripts/check_api_key.js",
"line_start": 20,
"line_end": 20
},
{
"file": "scripts/check_api_key.js",
"line_start": 28,
"line_end": 28
},
{
"file": "scripts/create_guest_key.js",
"line_start": 21,
"line_end": 21
},
{
"file": "scripts/create_guest_key.js",
"line_start": 24,
"line_end": 24
},
{
"file": "scripts/create_guest_key.js",
"line_start": 31,
"line_end": 31
},
{
"file": "scripts/create_guest_key.js",
"line_start": 39,
"line_end": 39
},
{
"file": "scripts/delete_api_key.js",
"line_start": 21,
"line_end": 21
},
{
"file": "scripts/download_share.js",
"line_start": 74,
"line_end": 74
},
{
"file": "scripts/download_share.js",
"line_start": 97,
"line_end": 97
},
{
"file": "scripts/download_share.js",
"line_start": 117,
"line_end": 117
},
{
"file": "scripts/download_share.js",
"line_start": 119,
"line_end": 119
},
{
"file": "scripts/save_api_key.js",
"line_start": 25,
"line_end": 25
},
{
"file": "scripts/save_api_key.js",
"line_start": 29,
"line_end": 29
},
{
"file": "scripts/shareone_client.js",
"line_start": 54,
"line_end": 54
},
{
"file": "scripts/shareone_client.js",
"line_start": 55,
"line_end": 55
},
{
"file": "scripts/shareone_client.js",
"line_start": 75,
"line_end": 75
},
{
"file": "scripts/shareone_client.js",
"line_start": 121,
"line_end": 121
},
{
"file": "scripts/shareone_client.js",
"line_start": 134,
"line_end": 134
},
{
"file": "scripts/shareone_client.js",
"line_start": 141,
"line_end": 141
},
{
"file": "scripts/shareone_client.js",
"line_start": 152,
"line_end": 152
},
{
"file": "scripts/shareone_client.js",
"line_start": 160,
"line_end": 160
},
{
"file": "scripts/shareone_client.js",
"line_start": 177,
"line_end": 177
},
{
"file": "scripts/shareone_client.js",
"line_start": 263,
"line_end": 263
}
]
},
{
"factor": "env_access",
"evidence": [
{
"file": "scripts/check_api_key.js",
"line_start": 18,
"line_end": 18
},
{
"file": "scripts/create_guest_key.js",
"line_start": 16,
"line_end": 16
},
{
"file": "scripts/create_guest_key.js",
"line_start": 19,
"line_end": 19
},
{
"file": "scripts/create_guest_key.js",
"line_start": 21,
"line_end": 21
},
{
"file": "scripts/create_guest_key.js",
"line_start": 29,
"line_end": 29
},
{
"file": "scripts/create_guest_key.js",
"line_start": 31,
"line_end": 31
},
{
"file": "scripts/download_share.js",
"line_start": 37,
"line_end": 37
},
{
"file": "scripts/shareone_api_request.js",
"line_start": 38,
"line_end": 38
},
{
"file": "scripts/shareone_client.js",
"line_start": 13,
"line_end": 13
},
{
"file": "scripts/shareone_client.js",
"line_start": 17,
"line_end": 17
},
{
"file": "scripts/shareone_client.js",
"line_start": 21,
"line_end": 21
},
{
"file": "scripts/shareone_client.js",
"line_start": 39,
"line_end": 39
},
{
"file": "scripts/shareone_client.js",
"line_start": 134,
"line_end": 134
},
{
"file": "scripts/shareone_client.js",
"line_start": 172,
"line_end": 172
},
{
"file": "scripts/shareone_client.js",
"line_start": 177,
"line_end": 177
},
{
"file": "scripts/shareone_upload.js",
"line_start": 180,
"line_end": 180
},
{
"file": "scripts/shareone_upload.js",
"line_start": 111,
"line_end": 111
},
{
"file": "scripts/shareone_upload.js",
"line_start": 197,
"line_end": 197
},
{
"file": "scripts/update_share_settings.js",
"line_start": 47,
"line_end": 47
},
{
"file": "scripts/update_share_settings.js",
"line_start": 102,
"line_end": 102
},
{
"file": "scripts/update_share_settings.js",
"line_start": 85,
"line_end": 85
},
{
"file": "scripts/upload_page.js",
"line_start": 51,
"line_end": 51
}
]
},
{
"factor": "network",
"evidence": [
{
"file": "scripts/shareone_client.js",
"line_start": 62,
"line_end": 62
},
{
"file": "scripts/shareone_client.js",
"line_start": 7,
"line_end": 7
},
{
"file": "workflows/environment-and-credentials.md",
"line_start": 63,
"line_end": 63
},
{
"file": "workflows/environment-and-credentials.md",
"line_start": 77,
"line_end": 77
},
{
"file": "workflows/environment-and-credentials.md",
"line_start": 116,
"line_end": 116
},
{
"file": "workflows/environment-and-credentials.md",
"line_start": 123,
"line_end": 123
},
{
"file": "workflows/publish-text-page.md",
"line_start": 39,
"line_end": 39
},
{
"file": "workflows/publish-text-page.md",
"line_start": 93,
"line_end": 93
},
{
"file": "workflows/result-and-errors.md",
"line_start": 9,
"line_end": 9
},
{
"file": "workflows/update-share-settings.md",
"line_start": 11,
"line_end": 11
},
{
"file": "workflows/update-share-settings.md",
"line_start": 28,
"line_end": 28
}
]
},
{
"factor": "filesystem",
"evidence": [
{
"file": "scripts/shareone_client.js",
"line_start": 31,
"line_end": 31
},
{
"file": "scripts/shareone_client.js",
"line_start": 43,
"line_end": 43
},
{
"file": "scripts/shareone_client.js",
"line_start": 48,
"line_end": 48
},
{
"file": "scripts/shareone_client.js",
"line_start": 29,
"line_end": 29
},
{
"file": "scripts/shareone_client.js",
"line_start": 47,
"line_end": 47
},
{
"file": "scripts/shareone_upload.js",
"line_start": 38,
"line_end": 38
},
{
"file": "scripts/shareone_upload.js",
"line_start": 59,
"line_end": 59
},
{
"file": "scripts/shareone_upload.js",
"line_start": 104,
"line_end": 104
},
{
"file": "scripts/upload_page.js",
"line_start": 61,
"line_end": 61
}
]
}
],
"critical_findings": [],
"high_findings": [],
"medium_findings": [],
"low_findings": [
{
"title": "Skill requires elevated capabilities by design",
"description": "The skill uses Node.js scripts with network, filesystem, and env access to call the ShareOne API. This is the documented purpose of the skill, not a vulnerability, but users should be aware that any compromise of these scripts could read or modify local files and exfiltrate the ShareOne API key. The static analyzer flagged the combination as a heuristic warning, which is appropriate. Confidence is high that the actual code is benign based on review of the scripts.",
"locations": [
{
"file": "scripts/shareone_client.js",
"line_start": 1,
"line_end": 290
}
]
}
],
"dangerous_patterns": [],
"files_scanned": 20,
"total_lines": 1935,
"audit_model": "claude",
"audited_at": "2026-06-09T02:29:43.839Z",
"risk_factors": [
"scripts",
"network",
"filesystem",
"env_access"
]
},
"content": {
"user_title": "Publish files to ShareOne and share public links",
"value_statement": "Sharing local files online usually means uploading, copying a link, and configuring permissions by hand. This skill automates the ShareOne workflow so Claude can publish, update, download, and moderate shared content in one conversation.",
"seo_keywords": [
"Claude",
"Codex",
"Claude Code",
"ShareOne",
"file sharing",
"publish HTML",
"public share link",
"AI agent",
"document publishing",
"comment collaboration"
],
"actual_capabilities": [
"Publish HTML, Markdown, TXT, PDF, Word, and PPTX files to the ShareOne platform",
"Generate public share links with optional password, watermark, and custom slug",
"Update settings on existing shares without re-uploading files",
"Download files from ShareOne links using owner credentials or public endpoints",
"Read and process comments left on shared pages",
"Manage ShareOne API keys via environment variables or local credential files"
],
"limitations": [
"Requires a valid ShareOne API key; only public downloads work without one",
"Binary file content cannot be edited in place; updates require re-uploading",
"Depends on the ShareOne service being reachable from the host machine",
"Watermarks and passwords apply at share level, not per viewer"
],
"use_cases": [
{
"title": "Developer publishing a generated report",
"description": "A developer finishes generating an HTML dashboard and asks Claude to publish it to ShareOne with a custom slug and password for the team.",
"target_user": "Software developers and technical writers"
},
{
"title": "Team lead distributing project documents",
"description": "A project manager needs to share a PDF contract with external reviewers and collect their feedback as comments on the page.",
"target_user": "Project managers and team leads"
},
{
"title": "Educator sharing lesson materials",
"description": "A teacher exports a Markdown lesson to ShareOne, then uses the comment workflow to gather student questions and update the document.",
"target_user": "Educators and content creators"
}
],
"prompt_templates": [
{
"title": "Publish a single file",
"prompt": "Publish the file ./report.html to ShareOne and give me the share link.",
"scenario": "Beginner: simple publish of a local file with no extra options"
},
{
"title": "Publish with protection options",
"prompt": "Upload the PDF at ./Q4-summary.pdf to ShareOne with password 'team-2026' and watermark 'Internal Only'. Return the share link.",
"scenario": "Intermediate: text or binary file with password, watermark, or custom slug"
},
{
"title": "Update settings on an existing share",
"prompt": "Take this ShareOne link https://shareone.app/s/abc123 and set a new password 'launch-day' and disable comments.",
"scenario": "Advanced: metadata-only update without re-uploading the source file"
},
{
"title": "Process feedback and re-publish",
"prompt": "Download the source of https://shareone.app/s/abc123, read its comments, apply the requested fixes, and publish the updated version back to the same share.",
"scenario": "Expert: combine download, comment processing, edit, and re-publish in one workflow"
}
],
"output_examples": [
{
"input": "Publish index.html to ShareOne and give me the link.",
"output": [
"Share link: https://shareone.app/s/quarterly-report",
"Backup link: https://api.shareone.app/s/abc123",
"Optional: add a password, watermark, or custom slug by replying with your preference."
]
},
{
"input": "Change the watermark on https://shareone.app/s/abc123 to 'Internal Use Only'.",
"output": [
"Watermark updated successfully.",
"Link: https://shareone.app/s/abc123",
"Settings: watermark='Internal Use Only', comments=disabled, password=unchanged."
]
},
{
"input": "Show me the comments on https://shareone.app/s/abc123.",
"output": [
"3 comments received.",
"Comment 1: 'Can you add a section about pricing?' from user@example.com",
"Comment 2: 'The chart on page 2 has wrong totals.' from reviewer@example.com",
"Comment 3: 'Looks great overall.' from user@example.com"
]
}
],
"best_practices": [
"Run check_api_key.js before any operation to confirm credentials are available and to detect the Sudowork environment.",
"Wait for the user to reply 'agree' before publishing, since share links expose the content on the public web.",
"Return the share_url exactly as the API provides it, including the backend_url when present, instead of constructing links manually."
],
"anti_patterns": [
"Do not call the system-wide shareone CLI found in PATH; always use the scripts in this skill directory.",
"Do not pass --api-key while running in Sudowork mode; the Auth Proxy injects the credential automatically.",
"Do not re-upload binary files through the page PUT endpoint; use shareone_upload.js for PDF, Word, PPTX, images, and zip files."
],
"faq": [
{
"question": "How does the skill obtain a ShareOne API key?",
"answer": "The skill checks process.env.SHAREONE_API_KEY and ~/.shareone_credentials. If neither is set, create_guest_key.js requests a temporary key from the ShareOne API and stores it locally."
},
{
"question": "Can the skill be used without an API key?",
"answer": "Public downloads work without authentication. All other operations, including upload, settings changes, and comment processing, require a valid ShareOne API key."
},
{
"question": "What is the difference between upload_page.js and shareone_upload.js?",
"answer": "upload_page.js publishes text pages such as HTML, Markdown, and TXT through the page API. shareone_upload.js publishes binary files such as PDF, Word, PPTX, images, and zip through the file upload API."
},
{
"question": "How do I change the watermark on an existing share?",
"answer": "Run update_share_settings.js with the share link and the --watermark flag. The source file is not downloaded or re-uploaded; only the metadata changes."
},
{
"question": "Does the skill support comments and collaboration?",
"answer": "Yes. The comments-view workflow reads comments on a shared page, and the comments-process workflow claims feedback, edits the source, and re-publishes the updated content."
},
{
"question": "Which file types can be published?",
"answer": "HTML, Markdown, TXT, PDF, Word (doc and docx), PowerPoint (ppt and pptx), PNG, JPG, GIF, and zip archives are all supported through the appropriate upload script."
}
]
},
"file_structure": [
{
"name": "scripts",
"type": "dir",
"path": "scripts",
"children": [
{
"name": "check_api_key.js",
"type": "file",
"path": "scripts/check_api_key.js",
"lines": 31
},
{
"name": "create_guest_key.js",
"type": "file",
"path": "scripts/create_guest_key.js",
"lines": 45
},
{
"name": "delete_api_key.js",
"type": "file",
"path": "scripts/delete_api_key.js",
"lines": 24
},
{
"name": "download_share.js",
"type": "file",
"path": "scripts/download_share.js",
"lines": 123
},
{
"name": "save_api_key.js",
"type": "file",
"path": "scripts/save_api_key.js",
"lines": 32
},
{
"name": "shareone_api_request.js",
"type": "file",
"path": "scripts/shareone_api_request.js",
"lines": 66
},
{
"name": "shareone_client.js",
"type": "file",
"path": "scripts/shareone_client.js",
"lines": 290
},
{
"name": "shareone_upload.js",
"type": "file",
"path": "scripts/shareone_upload.js",
"lines": 205
},
{
"name": "update_share_settings.js",
"type": "file",
"path": "scripts/update_share_settings.js",
"lines": 196
},
{
"name": "upload_page.js",
"type": "file",
"path": "scripts/upload_page.js",
"lines": 117
}
]
},
{
"name": "workflows",
"type": "dir",
"path": "workflows",
"children": [
{
"name": "comments-process.md",
"type": "file",
"path": "workflows/comments-process.md",
"lines": 112
},
{
"name": "comments-view.md",
"type": "file",
"path": "workflows/comments-view.md",
"lines": 44
},
{
"name": "delete-api-key.md",
"type": "file",
"path": "workflows/delete-api-key.md",
"lines": 22
},
{
"name": "download-file.md",
"type": "file",
"path": "workflows/download-file.md",
"lines": 58
},
{
"name": "environment-and-credentials.md",
"type": "file",
"path": "workflows/environment-and-credentials.md",
"lines": 130
},
{
"name": "publish-binary-file.md",
"type": "file",
"path": "workflows/publish-binary-file.md",
"lines": 63
},
{
"name": "publish-text-page.md",
"type": "file",
"path": "workflows/publish-text-page.md",
"lines": 133
},
{
"name": "result-and-errors.md",
"type": "file",
"path": "workflows/result-and-errors.md",
"lines": 52
},
{
"name": "update-share-settings.md",
"type": "file",
"path": "workflows/update-share-settings.md",
"lines": 54
}
]
},
{
"name": "SKILL.md",
"type": "file",
"path": "SKILL.md",
"lines": 138
}
]
}
处理 ShareOne 评论
当用户明确要求“处理这些评论”、“根据评论改一下页面”、“修改这个 ShareOne 链接的内容”时读取本文件。只查看评论时不要读取本文件。
ShareOne 页面评论包含状态机字段 status:open / in_progress / resolved / dismissed,以及作者字段 author_role:visitor / owner / agent。
1. 获取 share_id 与评论
从 ShareOne 链接 https://shareone.app/s/<share_id> 提取 <share_id>。
建议先获取未处理评论:
node scripts/shareone_api_request.js "/api/v1/shares/<SHARE_ID>/comments?status=unresolved"评论数据中可能包含 replies。必须将父评论及其所有回复作为一个 thread 整体阅读,综合理解最终共识。回复不需要单独走流程,只对父评论操作状态。
2. 标准闭环流程
对每条要处理的父评论,严格按以下顺序执行。
步骤 1:认领
必须在动手之前做:
node scripts/shareone_api_request.js "/api/v1/shares/<SHARE_ID>/comments/<COMMENT_ID>/status" \
--method PUT \
--data '{"status": "in_progress"}'访问者会立刻在页面侧栏看到“处理中”徽标和顶部“AI 正在处理 N 条评论...”横幅。跳过这一步会让用户感受不到 AI 在干活。
步骤 2:取源
node scripts/shareone_api_request.js "/api/v1/shares/<SHARE_ID>/download"返回:
{ "content": "...", "filename": "...", "content_type": "..." }步骤 3:精准应用修改
- 综合理解整个 thread(父评论 + 所有 replies)的最终意图,必要时先和用户确认。
- 绝对不要用全局
replace()或正则批量替换,会误伤其他同名文案。 - 基于 DOM 结构精确定位:利用
highlighter_data.startMeta.parentTagName、parentIndex、textOffset,结合quote(被选中原文)定位准确节点。 - 理解结构性意图:评论可能是“把这部分挪到底部 / 删掉这个区块 / 加个图标”,先定位再做结构变更。
- 如果在当前源文件里无论如何都找不到对应位置,不要瞎改,直接走 dismissed 流程,并用 note 告诉用户:“源文件结构已变更,无法定位你这条关于 XXX 的评论”。
步骤 4:重新发布
使用 publish-text-page.md 的 PUT 更新流程重新发布改后的文件,保留同一 share_id。
步骤 5:写回复并关闭评论
先在该父评论下发一条 AI 回复,明确告诉访问者改了什么:
node scripts/shareone_api_request.js "/api/v1/shares/<SHARE_ID>/comments" \
--method POST \
--data '{"parent_id":"<COMMENT_ID>","quote":"<父评论的 quote>","highlighter_data":"<父评论的 highlighter_data>","content":"已按你的建议把标题改成 ...,并调整了 ...","author_role":"agent"}'author_role: "agent" 只在 owner 鉴权下生效,访问者会在侧栏看到 AI 徽标和蓝色背景的回复。
然后关闭父评论:
node scripts/shareone_api_request.js "/api/v1/shares/<SHARE_ID>/comments/<COMMENT_ID>/status" \
--method PUT \
--data '{"status": "resolved", "note": "已采纳,见最新版本"}'note 会作为绿色“AI 已处理: ...”高亮区块显示在评论卡片底部,给访问者明确交代。
3. 无法处理或无关评论
对于无法处理或无关的评论,必须 dismiss,不要无视:
node scripts/shareone_api_request.js "/api/v1/shares/<SHARE_ID>/comments/<COMMENT_ID>/status" \
--method PUT \
--data '{"status": "dismissed", "note": "页面中没有此元素,可能指的是另一份分享"}'4. 兼容旧接口
旧接口仍然可用,但新代码不要使用:
PUT /api/v1/shares/<SHARE_ID>/comments/<COMMENT_ID>/resolve
{ "resolved": true/false }它等价于把 status 切到 resolved 或 open,但不会附带 note,访问者拿不到 AI 的解释。新代码一律使用 /status 接口。
5. 关键准则速查
| 准则 | 为什么 |
|---|---|
动手前先 in_progress | 让访问者看到“AI 在干活” |
改完一定要 POST 一条 author_role=agent 的回复 | 闭环的“答复”部分,没有它就只是状态变化、不是对话 |
resolution_note 要写人话 | “已把按钮改成主色” 比 “Applied.” 有用 |
不能处理就 dismissed + note | 不要让评论永远卡在 open |
| 只对父评论改状态,回复不单独操作 | 状态语义属于 thread 整体 |
unresolved = open + in_progress | 拉单子默认用 ?status=unresolved |
查看 ShareOne 评论
当用户只是要求查看、拉取、总结评论时读取本文件。不要修改源文件,不要认领评论,不要关闭评论。
1. 获取 share_id
从用户提供的 ShareOne 链接 https://shareone.app/s/<share_id> 中提取 <share_id>。
2. 查看未处理评论
调用:
node scripts/shareone_api_request.js "/api/v1/shares/<SHARE_ID>/comments?status=unresolved"status 可选值:
allopenin_progressresolveddismissedunresolved,等价于open + in_progress
3. 评论理解规则
- 只展示评论内容,绝对不要自作主张开始修改源文件。
- 等用户明确要求“处理这些评论”、“根据评论改一下页面”等,再进入
comments-process.md。 - 评论数据中可能包含
replies。必须将父评论及其所有回复作为一个 thread 整体阅读,综合理解最终共识。 - 不要把每条回复当成独立修改指令。
- 所有回复继承父评论的锚点,也就是
highlighter_data和quote。
4. 轻量摘要
如果只想看“现在还有没有未处理的事”,用摘要接口:
node scripts/shareone_api_request.js "/api/v1/shares/<SHARE_ID>/comments/summary"
# -> { total, open, in_progress, resolved, dismissed, last_activity_at }返回 open == 0 时无需拉全量评论。
删除 ShareOne API Key
当用户明确要求删除、清除、移除或重置 ShareOne API Key 时读取本文件。
1. 执行删除
执行:
node scripts/delete_api_key.js2. 根据输出回复
SUDOWORK_KEY_DELETED:告诉用户 Sudowork 中保存的 ShareOne API Key 已删除。KEY_DELETED:告诉用户本地保存的 ShareOne API Key 已删除。KEY_NOT_FOUND:告诉用户当前没有找到本地保存的 ShareOne API Key。
3. 删除后的规则
删除后,如果用户再次要求发布、查看评论、处理评论或执行任何 ShareOne API 操作,必须重新读取 environment-and-credentials.md 并完成凭据检查和配置。
下载 ShareOne 链接文件
当用户要求“下载这个 ShareOne 链接的文件”或“取回这个链接的源内容”时读取本文件。执行前必须已经完成 environment-and-credentials.md。
1. 获取链接或 share_id
用户可以提供完整链接、share_id、自定义短链 slug,或 /s/<share_id> 这类路径。优先把用户原始输入作为 ref 传给公开下载接口;不需要自行判断 slug 和 share_id。
2. 下载内容
优先使用下载脚本。脚本会在已配置 ShareOne API Key 时先尝试 owner 下载接口:
node scripts/download_share.js "<LINK_OR_ID>" > downloaded-fileowner 下载接口不受访问密码和 allow_download 限制;如果当前 API Key 不是 owner 或没有 API Key,脚本会自动退回公开下载。
如果用户提供了访问密码,必须通过 --password 传入,脚本会用 POST body 发送密码,不要把密码拼进 URL:
node scripts/download_share.js "<LINK_OR_ID>" --password "<PASSWORD>" > downloaded-file返回是原文件内容,不是 JSON。根据响应头里的文件名保存到本地文件,再按用户要求查看、总结或处理。
如果接口返回:
{
"detail": {
"code": "PASSWORD_REQUIRED",
"message": "This link requires a password before downloading."
}
}必须明确告诉用户:该 ShareOne 链接需要访问密码,请提供密码后再下载。
如果接口返回结构化错误:
{
"detail": {
"code": "DOWNLOAD_NOT_ALLOWED",
"message": "Downloads are not enabled for this link. Ask the owner to enable allow_download first."
}
}必须明确告诉用户:这个 ShareOne 链接没有开启允许下载,需要链接 owner 在文件管理中开启“允许下载”后才能让 agent 下载。
3. 后续处理
- 如果用户只是要求下载或查看,展示下载结果摘要,并按返回的文件名和
content_type说明内容类型。 - 如果用户要求修改下载到的内容,先保存源内容到本地文件,再根据文件类型读取
publish-text-page.md或publish-binary-file.md执行更新。 - 已配置 API Key 时,脚本会优先尝试
/api/v1/shares/<SHARE_ID>/downloadowner 下载;没有 API Key 或非 owner 时,必须要求链接开启allow_download,密码链接还必须要求用户提供访问密码。
环境判断与 API Key 凭据流程
只在需要 ShareOne API 的操作前读取本文件。不要把 Sudowork 和普通 AI Agent 的凭据流程混用。
1. 检查环境和 API Key
执行:
node scripts/check_api_key.js根据输出处理:
SUDOWORK_KEY_FOUND:当前在 Sudowork 中,且 Sudowork 已配置 ShareOne API Key。后续发布命令不要传--api-key。SUDOWORK_KEY_NOT_FOUND:当前在 Sudowork 中,但还没有设置 ShareOne API Key。KEY_FOUND:<api_key>:当前是普通 AI Agent 环境。将该 API Key 用于后续 direct 模式发布请求。KEY_NOT_FOUND:当前是普通 AI Agent 环境,且没有找到 API Key。
2. Sudowork 无 Key
如果输出 SUDOWORK_KEY_NOT_FOUND,暂停当前操作,询问用户是否已有 ShareOne API Key:
我检测到当前运行在 Sudowork 中,但还没有设置 ShareOne API Key。
请问您是否已经拥有 API Key?
>
- 如果有,请直接回复您的 API Key(例如 sk-xxx),我会通过 Sudowork 安全保存并继续。- 如果没有,请回复“没有”或“创建”,我会自动为您创建一个临时 API Key,并保存到 Sudowork。
用户已有 Key
执行:
node scripts/save_api_key.js <用户提供的KEY>如果输出 SUDOWORK_KEY_SAVED,重新执行:
node scripts/check_api_key.js确认输出 SUDOWORK_KEY_FOUND 后继续原操作。
如果输出 ERROR:SUDOWORK_AUTH_PROXY_SAVE_FAILED,暂停操作并提示:
我检测到当前运行在 Sudowork 中,但通过 Auth Proxy 设置 ShareOne API Key 失败。
请前往 Sudowork 的密钥管理手动添加 API Key,操作路径:【远程连接】-【密钥管理】。
添加完成后请回复“已添加”,我会重新检查并继续。
用户没有 Key 或要求创建
执行:
node scripts/create_guest_key.js如果输出 GUEST_KEY_CREATED:<api_key>,这是阻塞性用户通知。必须先回复用户以下内容,然后才能继续原操作:
已为您自动分配临时 API Key:<api_key>绑定账号链接:https://shareone.app/?key=<api_key>
请妥善保存此 API Key。为了方便您后续管理分享的链接,请尽快打开上面的链接绑定您的永久账号。
Sudowork 环境会把临时 API Key 保存到 Auth Proxy secrets。即使保存成功,也不能省略上面的用户通知。
如果输出 ERROR:SUDOWORK_AUTH_PROXY_SAVE_FAILED:<api_key>,暂停操作并提示:
已为您自动分配临时 API Key:<api_key>但我检测到当前运行在 Sudowork 中,通过 Auth Proxy 自动保存 API Key 失败。
请前往 Sudowork 的密钥管理手动添加该 API Key,操作路径:【远程连接】-【密钥管理】。
添加完成后请回复“已添加”,我会重新检查并继续。
如果输出 ERROR:RATE_LIMIT_EXCEEDED,暂停操作并提示:
获取临时凭证失败:您今天自动创建临时 API Key 的次数已达上限(每天最多 5 次)。请前往 https://shareone.app 手动注册并获取 API Key。
3. 普通 AI Agent 无 Key
如果输出 KEY_NOT_FOUND,暂停当前操作,询问用户是否已有 ShareOne API Key:
我没有找到您的 ShareOne API Key。
请问您是否已经拥有 API Key?
>
- 如果有,请直接回复您的 API Key(例如 sk-xxx),我将为您保存并继续。- 如果没有,请回复“没有”或“创建”,我可以为您创建一个临时 API Key。
用户已有 Key
执行:
node scripts/save_api_key.js <用户提供的KEY>如果输出 KEY_SAVED,重新执行:
node scripts/check_api_key.js确认输出 KEY_FOUND:<api_key> 后继续原操作。
用户没有 Key 或要求创建
执行:
node scripts/create_guest_key.js如果输出 GUEST_KEY_CREATED:<api_key>,这是阻塞性用户通知。必须先回复用户以下内容,然后才能继续原操作:
已为您自动分配临时 API Key:<api_key>绑定账号链接:https://shareone.app/?key=<api_key>
请妥善保存此 API Key。为了方便您后续管理分享的链接,请尽快打开上面的链接绑定您的永久账号。
普通 AI Agent 环境会把临时 API Key 保存到本地凭证文件。即使保存成功,也不能省略上面的用户通知。后续也可以使用环境变量 SHAREONE_API_KEY 或命令参数 --api-key。
如果输出 ERROR:RATE_LIMIT_EXCEEDED,暂停操作并提示:
获取临时凭证失败:您今天自动创建临时 API Key 的次数已达上限(每天最多 5 次)。请前往 https://shareone.app 手动注册并获取 API Key。
4. 后续命令规则
- Sudowork:不要传
--api-key。 - 普通 AI Agent:可以传
--api-key,也可以依赖SHAREONE_API_KEY或本地凭证。 - 如果凭据无效或服务返回 401,在结果处理 workflow 中提示 “API Key 无效或权限不足”。
发布二进制文件
当用户要发布 PDF、PPTX、PPT、Word、图片、zip 或其他二进制文件时读取本文件。发布前必须已经完成 environment-and-credentials.md。
适用文件类型包括但不限于:.pdf、.ppt、.pptx、.doc、.docx、.png、.jpg、.jpeg、.gif、.zip。
如果用户说“发布这个 pptx / PPT / 演示文稿 / presentation”,必须使用本 workflow。不要读取或执行 publish-text-page.md,不要使用 upload_page.js。
/s/<share_id> 是最终给用户访问的分享链接,不是上传地址。不要向 /s/<share_id> 上传 PPTX/PPT/PDF/Word 文件。
1. 文件检查
- 使用用户指定的文件。
- 如果用户没有指定文件,根据上下文寻找最近一次生成或编辑的文件。
- 如果文件不存在,停止并告知用户。
- 提取用户可能要求的密码 (
password)、水印 (watermark) 和自定义短链接后缀 (slug)。服务端会根据文件名自动生成可读的 slug,客户端无需主动设置。只有用户明确说”链接叫 xxx / 自定义短链接 xxx / URL 后缀 xxx”时才用--slug覆盖。
2. 发布前安全确认
发布前安全提示由入口 SKILL.md 统一描述。未展示入口安全提示并获得用户明确回复“同意”或 agree 前,不得执行本文件中的发布命令。
3. 首次发布二进制文件
为了最大兼容性,推荐使用本 skill 的 Node.js 脚本发起 HTTP 请求。
由于二进制文件可能较大,ShareOne 采用直传云存储方式,支持 S3 或 Azure。脚本会自动根据服务端返回的 upload_type 判断走 S3 表单上传还是 Azure PUT 直传。
首次发布 .pptx、.ppt、.pdf、Word、图片、zip 等二进制文件时,一律使用文件上传脚本。该脚本会创建新的 ShareOne 文件分享链接,并在结果中返回 share_url。
执行:
node scripts/shareone_upload.js "<FILE_PATH>" [--password "OPTIONAL_PASSWORD"] [--watermark "OPTIONAL_WATERMARK"] [--slug "OPTIONAL_SLUG"]规则:
- Sudowork 环境不要传
--api-key。 - 普通 AI Agent 环境可传
--api-key,也可以依赖SHAREONE_API_KEY或本地凭证。 - 如果服务端返回
custom_slug_warning,说明文件链接已生成但自定义短链接未生效。必须把提示展示给用户,并请用户提供新的 slug 或之后到管理页修改。
4. 更新已上传二进制链接的密码或水印
对于已经上传的二进制文件,如果用户只要求修改密码或水印,调用通用鉴权请求脚本:
node scripts/shareone_api_request.js "/api/v1/files/<YOUR_SHARE_ID>" \
--method PUT \
--data '{"password": "NEW_PASSWORD", "watermark": "NEW_WATERMARK"}'规则:
- Sudowork 环境不要传
--api-key。 - 普通 AI Agent 环境可传
--api-key,也可以依赖SHAREONE_API_KEY或本地凭证。 - 空字符串
""表示取消密码或水印。 /api/v1/files/<YOUR_SHARE_ID>这里只用于修改已上传二进制链接的密码或水印,不用于上传新的 PPTX/PPT/PDF/Word 文件内容。- 如果用户要求“发布某个 pptx”或“替换成另一个 pptx”,不要把文件内容 PUT 到
/s/<share_id>或/api/v1/files/<share_id>;应使用上面的shareone_upload.js "<FILE_PATH>"文件上传流程。
5. 下一步
执行完成后读取 result-and-errors.md,按返回 JSON 展示结果或错误。
发布或更新文本/HTML 页面
当用户要发布对话、大段文本、代码、HTML、Markdown 或纯文本时读取本文件。发布前必须已经完成 environment-and-credentials.md。
适用文件类型:.html、.md、.txt。
不适用文件类型:.ppt、.pptx、.pdf、.doc、.docx、图片、zip 或其他二进制文件。遇到这些文件时,立即停止本 workflow,改读 publish-binary-file.md。不要使用 upload_page.js 发布二进制文件。
1. 识别目标内容
- 如果用户要求分享对话、大段文本或代码:从对话历史中提取上一轮生成的完整文本或代码块,保存到当前目录下的临时文件,例如
share_note.md或share_note.html。 - 如果是 Markdown 内容:建议在保存为
.html前使用简单 HTML 模板包裹;如果无法确定后端是否渲染 Markdown,优先生成美观的.html文件。 - 对于提取的对话、大段文字或独立代码块,建议包装为带基础样式的美观 HTML,以保证展示效果。
- 如果用户指定了文件:使用用户指定的文件。
- 如果用户没有指定文件:根据上下文寻找最近一次生成或编辑的文本/HTML 文件,例如
.html、.md、.txt,或将对话/代码包装成.html。 - 如果锁定的文件不存在,停止并告知用户。
- 如果锁定的文件是
.ppt、.pptx、.pdf、.doc、.docx、图片、zip 或其他二进制文件,停止本 workflow,改读publish-binary-file.md。 - 提取用户可能要求的密码 (
password)、水印 (watermark) 和自定义短链接后缀 (slug)。服务端会根据文件名自动生成可读的 slug,客户端无需主动设置。只有用户明确说”链接叫 xxx / 自定义短链接 xxx / URL 后缀 xxx”时才用--slug覆盖。
2. 发布前安全确认
发布前安全提示由入口 SKILL.md 统一描述。未展示入口安全提示并获得用户明确回复“同意”或 agree 前,不得执行本文件中的发布命令。
3. 判断创建还是更新
检查对话上下文。如果当前会话中已经为同一个文件生成过 ShareOne 链接,提取之前的 share_id(16 位字符串)并执行 PUT 更新。
- 有
share_id:执行更新。 - 没有
share_id:执行首次创建。
4. 文本页面发布规则
为了最大兼容性,推荐使用本 skill 的 Node.js 脚本发起 HTTP 请求。
upload_page.js 只用于 .html、.md、.txt 或已经包装成 HTML 的文本内容。
不要通过 pages JSON 接口上传任何二进制文件,例如 .ppt、.pptx、.doc、.docx、.pdf、.zip、.png。如果看到 400 Bad Request 且提示检测到二进制内容,立即改用 publish-binary-file.md 中的 /api/v1/files 流程重新上传。
接口:https://shareone.app/api/v1/pages
格式:application/json
5. 首次创建 (POST)
执行:
node scripts/upload_page.js "<YOUR_FILE_PATH>" --filename "YOUR_FILE_NAME" [--password "OPTIONAL_PASSWORD"] [--watermark "OPTIONAL_WATERMARK"] [--slug "OPTIONAL_SLUG"] [--allow-comments true]规则:
- Sudowork 环境不要传
--api-key。 - 普通 AI Agent 环境可传
--api-key,也可以依赖SHAREONE_API_KEY或本地凭证。 - 只有当用户明确要求“开启评论”、“允许讨论”、“协同模式”等时,才加
--allow-comments true。 - 默认不开启评论。
- 服务端根据文件名自动生成 slug,无需手动设置。只有当用户明确要求自定义短链接时,才加
--slug覆盖。
6. 更新已有链接 (PUT)
如果用户只要求修改已有链接的水印、访问密码、自定义短链接或评论开关,不要执行本节,不要下载原文件;改读 update-share-settings.md,使用 update_share_settings.js 只更新元数据。
执行:
node scripts/upload_page.js "<YOUR_FILE_PATH>" --filename "YOUR_FILE_NAME" --share-id <YOUR_SHARE_ID> [--password "OPTIONAL_PASSWORD"] [--watermark "OPTIONAL_WATERMARK"] [--slug "OPTIONAL_SLUG"] [--allow-comments true/false]规则:
- Sudowork 环境不要传
--api-key。 - 如果用户要求关闭评论协同或开启评论协同,可以在 PUT 更新时传入
--allow-comments false或--allow-comments true。 - 如果用户要求修改或清除密码/水印,可以传入
--password或--watermark。 - 如果用户要求修改自定义短链接,可以传入
--slug。 - 空字符串
""表示清除对应设置。
7. 使用 Mermaid.js 绘制图表
当 HTML 页面需要包含图表、流程图、时序图、思维导图等可视化内容时,优先使用 Mermaid.js 而非 CSS/字符串拼接的伪图表。Mermaid 渲染的图表响应式更好、更生动。
引入方式
在 HTML 的 <style> 中添加防闪烁 CSS,在 <body> 末尾通过 ESM 模块加载:
/* 防止 Mermaid 加载前显示原始语法文本 */
pre.mermaid { background: none; border: none; text-align: center; padding: 20px 0; visibility: hidden; }
pre.mermaid[data-processed] { visibility: visible; }<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true, theme: 'default', look: 'handDrawn' });
</script>语法
在 HTML 中用 <pre class="mermaid"> 包裹 Mermaid 语法:
<pre class="mermaid">
flowchart LR
A[开始] --> B{条件判断}
B -->|是| C[执行]
B -->|否| D[跳过]
</pre>支持的图表类型
flowchart— 流程图sequenceDiagram— 时序图classDiagram— 类图stateDiagram-v2— 状态图erDiagram— ER 关系图gantt— 甘特图pie— 饼图mindmap— 思维导图timeline— 时间线
使用原则
- 页面中有图表需求时,默认使用 Mermaid 替代 CSS 手工绘制的伪图表。
- 一个页面可以包含多个
<pre class="mermaid">块。 - Mermaid 语法中不要包含 HTML 标签,保持纯文本描述。
- 如果图表极其复杂且 Mermaid 表达力不够,可以退回到 SVG 或 Canvas 方案。
8. 下一步
执行完成后读取 result-and-errors.md,按返回 JSON 展示结果或错误。
结果反馈与错误处理
发布、更新、上传或通用 API 请求完成后读取本文件。
1. 发布成功
如果 HTTP 200/201 或脚本返回 JSON 表示成功:
- 接口和脚本返回结果中已经包含完整的
share_url字段,例如https://shareone.app/s/<share_id>。 - 如果服务端配置了国内代理地址,返回结果还会包含
backend_url字段。 - 必须直接使用返回的
share_url展示给用户;如果返回中包含backend_url,同时作为备用链接展示给用户。 - 如果返回中包含
custom_slug_warning,必须同时展示该提示;这表示内容已发布成功,但用户请求的自定义短链接没有生效。 - 如果返回中包含
custom_slug_suggestions,必须把其中的推荐短链接名称一起展示给用户。 - 不要自己拼接分享链接或备用链接。
如果设置了密码,必须加粗显示密码:
发布成功!
链接:<返回的 share_url>
备用链接:<返回的 backend_url,仅当存在时展示>
提取码:<密码>
如果未设置密码但返回了 backend_url:
发布成功!
链接:<返回的 share_url>
备用链接:<返回的 backend_url>
2. 功能提示
在本次会话首次向用户展示生成的 share_url 时,必须主动但简短地提示所有未使用的高级功能。后续发布不再重复提示。
默认提示:
您也可以让我为这个分享链接设置自定义短链接名称、访问密码或水印。
按本次发布已经使用的能力删减提示内容:
- 如果用户已经指定自定义短链接,不再提示“自定义短链接名称”。
- 如果用户已经设置访问密码,不再提示“访问密码”。
- 如果用户已经设置水印,不再提示“水印”。
- 如果自定义短链接、访问密码、水印都已使用,不进行功能提示。
3. 常见错误
- 内容违规拦截,HTTP 400:提取 JSON 中的
detail字段展示给用户,例如“发布失败,内容未通过安全审核。原因:<detail>”。 - API Key 无效,HTTP 401:提示“API Key 无效或权限不足”。
- 找不到页面,HTTP 404:若 PUT 更新遇 404,说明原页面已被后台删除,请询问用户是否作为新页面重新 POST。
- 自定义短链接名称被占用,HTTP 400 且
detail.code为CUSTOM_SLUG_TAKEN:提示用户短链接名称已被占用;如果detail.suggested_slugs中包含推荐名称,必须把这些推荐名称一起展示给用户。 - 二进制文件被误发到 pages JSON 接口,HTTP 400 且提示检测到二进制内容:改读
publish-binary-file.md并使用/api/v1/files流程重新上传。 ERROR:UPDATE_VERIFY_FAILED:不要按发布成功处理。提示用户“ShareOne 接口接受了更新请求,但回读源内容与本地文件不一致,更新可能没有真正生效”,并保留本地文件,等待用户决定是否重试或作为新页面发布。
更新已有 ShareOne 链接设置
当用户提供已有 ShareOne 链接、share_id 或自定义短链 slug,并且只要求修改水印、访问密码、自定义短链接或评论开关时读取本文件。执行前必须已经完成 environment-and-credentials.md。
本 workflow 只更新元数据,不发布新内容,不替换源文件。
1. 判断是否适用
适用示例:
- “给 http://localhost:8000/s/test-page 加水印”
- “把这个 ShareOne 链接的水印改成内部资料”
- “清除 /s/demo 的访问密码”
- “给这个已有链接开启评论”
- “把这个链接的短链改成 product-demo”
如果用户没有提供要设置的具体值,例如只说“加水印”但没有水印文字,先询问水印内容。不要自动生成默认水印。
2. 不要做的事
- 不要下载源文件来判断类型。
- 不要使用
upload_page.js。 - 不要重新上传页面内容、二进制文件或
html_content。 - 不要为了
/s/<ref>判断是否是二进制文件;/s/<ref>按 HTML/Markdown 页面设置更新处理。
3. 更新命令
推荐使用专用脚本。脚本会解析完整 URL、/s/<ref> 路径、裸 share_id 或 slug;如果传入完整 URL,例如 http://localhost:8000/s/test-page,会自动使用该 URL 的 origin 作为 API base URL。
node scripts/update_share_settings.js "<SHARE_LINK_OR_ID>" [--watermark "OPTIONAL_WATERMARK"] [--password "OPTIONAL_PASSWORD"] [--slug "OPTIONAL_SLUG"] [--allow-comments true/false]规则:
- Sudowork 环境不要传
--api-key。 - 普通 AI Agent 环境可传
--api-key,也可以依赖SHAREONE_API_KEY或本地凭证。 - 空字符串
""表示清除对应设置,例如--watermark ""或--password ""。 - 只传用户明确要求修改的字段;不要把未知的现有设置补进请求体。
4. Endpoint 映射
脚本内部按以下规则选择接口:
/s/<ref>和/md/<ref>:PUT /api/v1/pages/<ref>。/pdf/<ref>、/ppt/<ref>和/word/<ref>:PUT /api/v1/files/<ref>。- 裸
share_id或 slug:先尝试PUT /api/v1/pages/<ref>;如果服务端明确提示该 endpoint 只适用于 HTML/Markdown 或要求使用/api/v1/files,再尝试PUT /api/v1/files/<ref>。
裸 share_id 或 slug 的回退过程仍然不下载源文件。
5. 下一步
执行完成后读取 result-and-errors.md,按返回 JSON 展示结果或错误。
Related skills
FAQ
How long is content hosted?
Uploaded content is hosted free for 90 days.
Does it use the local shareone CLI?
No; it uses the skill's own scripts unless the user explicitly asks to use the CLI.