@claudeink/mcp-server 0.0.2 → 0.0.4

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/cli.js CHANGED
@@ -1,11 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { writeFile, readFile, mkdir } from "fs/promises";
5
- import { join } from "path";
6
- import { homedir } from "os";
4
+ import { writeFile, readFile, mkdir, cp, access } from "fs/promises";
5
+ import { join, dirname } from "path";
6
+ import { fileURLToPath } from "url";
7
7
  var args = process.argv.slice(2);
8
8
  var command = args[0];
9
+ var __filename = fileURLToPath(import.meta.url);
10
+ var __dirname = dirname(__filename);
11
+ var WORKFLOW_SRC = join(__dirname, "..", "workflow");
9
12
  async function init() {
10
13
  const keyIdx = args.indexOf("--key");
11
14
  const key = keyIdx >= 0 ? args[keyIdx + 1] : void 0;
@@ -13,86 +16,111 @@ async function init() {
13
16
  console.error("Usage: npx @claudeink/mcp-server init --key WF-XXXX-XXXX-XXXX");
14
17
  process.exit(1);
15
18
  }
16
- console.log("\u{1F680} ClaudeInk MCP Server \u521D\u59CB\u5316\u4E2D...\n");
17
- const claudeinkDir = join(homedir(), ".claudeink");
18
- await mkdir(claudeinkDir, { recursive: true });
19
- await mkdir(join(claudeinkDir, "logs"), { recursive: true });
20
- console.log("\u2705 \u914D\u7F6E\u76EE\u5F55\u5DF2\u521B\u5EFA: ~/.claudeink/");
21
- const credsPath = join(claudeinkDir, "credentials.json");
22
- await writeFile(
23
- credsPath,
24
- JSON.stringify({ licenseKey: key, token: "", userId: "", plan: "starter", expiresAt: "" }, null, 2),
25
- { mode: 384 }
26
- );
27
- console.log("\u2705 License key \u5DF2\u4FDD\u5B58");
28
- const configPath = join(claudeinkDir, "config.json");
29
- await writeFile(
30
- configPath,
31
- JSON.stringify(
32
- {
33
- apiBaseUrl: "https://claudeink-web.pages.dev",
34
- syncIntervalMs: 3e5,
35
- heartbeatIntervalMs: 3e5,
36
- maxTagQueueBatch: 20,
37
- workflowDir: process.cwd()
38
- },
39
- null,
40
- 2
41
- )
42
- );
43
- console.log(`\u2705 \u914D\u7F6E\u5DF2\u521D\u59CB\u5316 (\u5DE5\u4F5C\u76EE\u5F55: ${process.cwd()})`);
44
- await registerWithClaude();
45
- console.log("\n\u{1F389} \u521D\u59CB\u5316\u5B8C\u6210\uFF01");
46
- console.log(" \u8BF7\u5728 Claude \u4E2D\u4F7F\u7528 auth.activate \u5B8C\u6210\u6FC0\u6D3B\u3002");
47
- console.log(" \u6216\u91CD\u65B0\u542F\u52A8 Claude\uFF0C\u7CFB\u7EDF\u5C06\u81EA\u52A8\u8BC6\u522B ClaudeInk MCP Server\u3002\n");
48
- }
49
- async function registerWithClaude() {
50
- const mcpConfig = {
51
- claudeink: {
52
- command: "npx",
53
- args: ["-y", "@claudeink/mcp-server"]
19
+ const cwd = process.cwd();
20
+ console.log("\u{1F680} ClaudeInk \u5DE5\u4F5C\u6D41\u521D\u59CB\u5316\u4E2D...\n");
21
+ console.log(` \u5DE5\u4F5C\u76EE\u5F55: ${cwd}`);
22
+ console.log(` \u6A21\u677F\u6765\u6E90: ${WORKFLOW_SRC}
23
+ `);
24
+ console.log("\u{1F4E6} \u91CA\u653E\u5DE5\u4F5C\u6D41\u6A21\u677F...");
25
+ const items = ["CLAUDE.md", "base-rules.md", "platforms", "accounts", "tools"];
26
+ for (const item of items) {
27
+ const src = join(WORKFLOW_SRC, item);
28
+ const dest = join(cwd, item);
29
+ try {
30
+ await access(dest);
31
+ console.log(` \u23ED\uFE0F ${item} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7`);
32
+ } catch {
33
+ await cp(src, dest, { recursive: true });
34
+ console.log(` \u2705 ${item}`);
54
35
  }
55
- };
56
- const platform = process.platform;
57
- let desktopConfigPath;
58
- if (platform === "win32") {
59
- desktopConfigPath = join(
60
- process.env.APPDATA || join(homedir(), "AppData", "Roaming"),
61
- "Claude",
62
- "claude_desktop_config.json"
63
- );
64
- } else {
65
- desktopConfigPath = join(
66
- homedir(),
67
- "Library",
68
- "Application Support",
69
- "Claude",
70
- "claude_desktop_config.json"
71
- );
72
36
  }
73
- await registerToConfig(desktopConfigPath, mcpConfig, "Claude Desktop");
74
- const codeConfigPath = join(homedir(), ".claude.json");
75
- await registerToConfig(codeConfigPath, mcpConfig, "Claude Code");
76
- }
77
- async function registerToConfig(configPath, mcpConfig, name) {
78
- try {
79
- let config = {};
37
+ console.log("\n\u{1F4C1} \u521B\u5EFA\u8FD0\u884C\u65F6\u76EE\u5F55...");
38
+ const dirs = [
39
+ "sources/articles",
40
+ "sources/video-transcripts",
41
+ "sources/notes",
42
+ "sources/data",
43
+ "sources/daily",
44
+ "templates",
45
+ ".claudeink"
46
+ ];
47
+ for (const dir of dirs) {
48
+ await mkdir(join(cwd, dir), { recursive: true });
49
+ }
50
+ console.log(" \u2705 sources/, templates/, .claudeink/");
51
+ const { glob } = await import("glob");
52
+ const yamlFiles = await glob("accounts/*.yaml", { cwd, ignore: "accounts/_template.yaml" });
53
+ for (const yamlFile of yamlFiles) {
80
54
  try {
81
- const existing = await readFile(configPath, "utf-8");
82
- config = JSON.parse(existing);
55
+ const content = await readFile(join(cwd, yamlFile), "utf-8");
56
+ const idMatch = content.match(/^id:\s*"?([^"\n]+)"?/m);
57
+ if (idMatch) {
58
+ const id = idMatch[1].trim();
59
+ const draftsMatch = content.match(/drafts:\s*"?([^"\n]+)"?/m);
60
+ const publishedMatch = content.match(/published:\s*"?([^"\n]+)"?/m);
61
+ const assetsMatch = content.match(/assets:\s*"?([^"\n]+)"?/m);
62
+ const drafts = (draftsMatch?.[1] || `accounts/${id}/drafts/`).replace("{id}", id).trim();
63
+ const published = (publishedMatch?.[1] || `accounts/${id}/published/`).replace("{id}", id).trim();
64
+ const assets = (assetsMatch?.[1] || `accounts/${id}/assets/`).replace("{id}", id).trim();
65
+ await mkdir(join(cwd, drafts), { recursive: true });
66
+ await mkdir(join(cwd, published), { recursive: true });
67
+ await mkdir(join(cwd, assets), { recursive: true });
68
+ console.log(` \u2705 \u8D26\u53F7 ${id}: ${drafts}, ${published}, ${assets}`);
69
+ }
83
70
  } catch {
84
71
  }
85
- if (!config.mcpServers) {
86
- config.mcpServers = {};
72
+ }
73
+ console.log("\n\u{1F511} \u6FC0\u6D3B License...");
74
+ try {
75
+ const res = await fetch("https://app.claudeink.com/api/auth/activate", {
76
+ method: "POST",
77
+ headers: { "Content-Type": "application/json" },
78
+ body: JSON.stringify({ key })
79
+ });
80
+ const data = await res.json();
81
+ if (data.userId) {
82
+ await writeFile(
83
+ join(cwd, ".claudeink", "credentials.json"),
84
+ JSON.stringify(data, null, 2),
85
+ { mode: 384 }
86
+ );
87
+ console.log(` \u2705 \u6FC0\u6D3B\u6210\u529F (\u5957\u9910: ${data.plan}, \u5230\u671F: ${data.expiresAt})`);
88
+ } else {
89
+ console.log(` \u26A0\uFE0F \u6FC0\u6D3B\u5931\u8D25: ${JSON.stringify(data)}`);
90
+ console.log(" \u53EF\u7A0D\u540E\u624B\u52A8\u6FC0\u6D3B\u3002");
87
91
  }
88
- config.mcpServers["claudeink"] = mcpConfig.claudeink;
89
- const dir = configPath.substring(0, configPath.lastIndexOf("/"));
90
- await mkdir(dir, { recursive: true });
91
- await writeFile(configPath, JSON.stringify(config, null, 2));
92
- console.log(`\u2705 \u5DF2\u6CE8\u518C\u5230 ${name}: ${configPath}`);
93
92
  } catch (err) {
94
- console.log(`\u26A0\uFE0F \u6CE8\u518C\u5230 ${name} \u5931\u8D25 (\u975E\u81F4\u547D): ${err}`);
93
+ console.log(` \u26A0\uFE0F \u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}`);
94
+ console.log(" \u53EF\u7A0D\u540E\u624B\u52A8\u6FC0\u6D3B\u3002");
95
+ }
96
+ try {
97
+ await access(join(cwd, "tools", "crawler", "package.json"));
98
+ console.log("\n\u{1F4E6} \u5B89\u88C5\u722C\u866B\u4F9D\u8D56...");
99
+ const { execSync } = await import("child_process");
100
+ execSync("npm install --silent", {
101
+ cwd: join(cwd, "tools", "crawler"),
102
+ stdio: "pipe"
103
+ });
104
+ console.log(" \u2705 \u722C\u866B\u4F9D\u8D56\u5DF2\u5B89\u88C5");
105
+ } catch {
95
106
  }
107
+ console.log("\n" + "=".repeat(50));
108
+ console.log("\u2705 ClaudeInk \u5DE5\u4F5C\u6D41\u521D\u59CB\u5316\u5B8C\u6210\uFF01");
109
+ console.log("=".repeat(50));
110
+ console.log(`
111
+ \u{1F4C2} ${cwd}/
112
+ \u251C\u2500\u2500 CLAUDE.md (\u7CFB\u7EDF\u7D22\u5F15 \u2190 \u8BF7\u5148\u8BFB\u53D6)
113
+ \u251C\u2500\u2500 base-rules.md (\u901A\u7528\u5199\u4F5C\u5E95\u5EA7)
114
+ \u251C\u2500\u2500 platforms/ (5 \u4E2A\u5E73\u53F0\u89C4\u5219)
115
+ \u251C\u2500\u2500 accounts/ (\u8D26\u53F7\u914D\u7F6E\u6A21\u677F)
116
+ \u251C\u2500\u2500 sources/ (\u5171\u4EAB\u7D20\u6750\u5E93)
117
+ \u2514\u2500\u2500 tools/ (\u722C\u866B\u7B49\u5DE5\u5177)
118
+
119
+ \u{1F3AF} \u4E0B\u4E00\u6B65\uFF1A
120
+ 1. \u8BFB\u53D6 CLAUDE.md \u786E\u8BA4\u7CFB\u7EDF\u5C31\u7EEA
121
+ 2. \u4F7F\u7528 /\u65B0\u5EFA\u8D26\u53F7 \u521B\u5EFA\u7B2C\u4E00\u4E2A\u81EA\u5A92\u4F53\u8D26\u53F7
122
+ 3. \u4F7F\u7528 /\u7F16\u5199 \u5F00\u59CB\u5185\u5BB9\u521B\u4F5C
123
+ `);
96
124
  }
97
125
  switch (command) {
98
126
  case "init":
@@ -102,7 +130,7 @@ switch (command) {
102
130
  });
103
131
  break;
104
132
  case "version":
105
- console.log("@claudeink/mcp-server v0.0.1");
133
+ console.log("@claudeink/mcp-server v0.0.3");
106
134
  break;
107
135
  default:
108
136
  import("./index.js");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudeink/mcp-server",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "ClaudeInk MCP Server - 自媒体多平台写作系统的本地 MCP 服务,连接 Claude 与云端后台",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,7 @@
15
15
  },
16
16
  "files": [
17
17
  "dist",
18
+ "workflow",
18
19
  "README.md",
19
20
  "LICENSE"
20
21
  ],
@@ -0,0 +1,167 @@
1
+ # ClaudeInk 自媒体多平台多账号写作系统
2
+
3
+ > Powered by ClaudeInk — 在 Cowork 中管理素材、撰写内容、跨平台发布
4
+
5
+ ## 系统架构
6
+
7
+ 采用"通用底座 + 平台规则 + 账号配置"三层架构:
8
+
9
+ | 层级 | 文件 | 作用 |
10
+ |---|---|---|
11
+ | 通用底座 | `base-rules.md` | 跨平台通用:写作能力、质量标准、内容红线、工作流、标题通用规则、指令集、模板 |
12
+ | 平台规则 | `platforms/<平台>.md` | 平台专属:字数限制、标题规范、排版要求、SEO/推荐优化、封禁红线 |
13
+ | 账号配置 | `accounts/<账号名>.yaml` | 账号专属:定位、领域、风格、人设、专属模板、文件路径 |
14
+
15
+ 配置优先级:**账号 > 平台 > 通用**(后者覆盖前者的同名字段)
16
+
17
+ ## 工作规则
18
+
19
+ 1. **每次写作前**,按顺序读取三层配置:`base-rules.md` → `platforms/<平台>.md` → `accounts/<账号名>.yaml`
20
+ 2. **账号配置最优先**:账号 yaml 中定义的字段覆盖平台和通用默认值
21
+ 3. **展示思考过程**:每次生成内容前,按 base-rules.md 中定义的格式展示思考框(含 [账号] 和 [平台] 字段)
22
+ 4. **切换账号**:使用 `/切换 <账号名>` 指令
23
+ 5. **跨平台适配**:使用 `/适配 <平台>` 指令将内容转换为另一平台格式
24
+
25
+ ## 当前平台
26
+
27
+ | 平台 | 规则文件 | 状态 |
28
+ |---|---|---|
29
+ | 微信公众号 | `platforms/wechat.md` | ✅ 已完善(含搜一搜优化+封禁红线) |
30
+ | 小红书 | `platforms/xiaohongshu.md` | 🚧 框架已建,待实战完善 |
31
+ | X (Twitter) | `platforms/x-twitter.md` | 🚧 框架已建,待实战完善 |
32
+ | 头条号 | `platforms/toutiao.md` | 🚧 框架已建,待实战完善 |
33
+ | 个人博客 | `platforms/blog.md` | 🚧 框架已建,待实战完善 |
34
+
35
+ ## 当前账号列表
36
+
37
+ | 账号 | 平台 | 定位 | 配置文件 |
38
+ |---|---|---|---|
39
+ | (暂无,使用 `/新建账号` 创建) | | | |
40
+
41
+ ## 素材管理
42
+
43
+ 素材(sources/)是**全局共享**的,不属于任何单一账号:
44
+
45
+ - 所有素材统一存放在根目录 `sources/` 下
46
+ - 素材入库后由大模型自动打标签(tags),标签存储在每篇素材的 YAML front matter 中
47
+ - 各账号写作时,按自己的 `domains` 字段自动匹配相关素材
48
+ - 同一篇素材可以被不同账号从不同角度使用
49
+
50
+ ```
51
+ sources/
52
+ ├── articles/ ← 爬虫抓取的文章(按源分目录)
53
+ ├── video-transcripts/ ← 视频转录文本
54
+ ├── notes/ ← 灵感笔记
55
+ ├── data/ ← 数据素材
56
+ └── daily/ ← 每日素材卡片
57
+ ```
58
+
59
+ ### 素材标签规范
60
+
61
+ 每篇素材的 YAML front matter 中包含 `tags` 字段:
62
+
63
+ ```yaml
64
+ ---
65
+ title: "文章标题"
66
+ source: techcrunch
67
+ published: 2026-03-10
68
+ tags:
69
+ - AI
70
+ - 融资
71
+ - SaaS
72
+ ---
73
+ ```
74
+
75
+ 标签用于:
76
+ 1. `/编写` 时自动筛选与当前账号 domains 匹配的素材
77
+ 2. 批量生成初稿时按标签聚类
78
+ 3. 月度复盘时统计内容分布
79
+
80
+ ## 新建账号流程
81
+
82
+ 1. 复制 `accounts/_template.yaml` → `accounts/<新账号名>.yaml`
83
+ 2. 填入账号信息:名称、**platform**、领域、风格、人设等
84
+ 3. 创建文件目录:`accounts/<新账号名>/drafts/`、`published/`、`assets/`(不需要单独的 sources)
85
+ 4. 在上方"当前账号列表"中登记
86
+ 5. 使用 `/切换 <新账号名>` 开始为该账号写作
87
+
88
+ 或直接使用 `/新建账号` 指令,交互式完成以上步骤。
89
+
90
+ ## 新建平台流程
91
+
92
+ 1. 在 `platforms/` 下创建 `<平台名>.md`
93
+ 2. 按现有平台文件的结构填写:平台概况、内容规范、标题规则、SEO/推荐优化、封禁红线、输出格式
94
+ 3. 在上方"当前平台"表中登记
95
+ 4. 在相关账号的 yaml 中设置 `platform: <平台名>`
96
+
97
+ ## 文件夹结构
98
+
99
+ ```
100
+ ├── CLAUDE.md ← 你正在读的文件(系统索引)
101
+ ├── base-rules.md ← 通用写作底座(跨平台)
102
+
103
+ ├── platforms/ ← 平台规则层
104
+ │ ├── wechat.md ← 微信公众号
105
+ │ ├── xiaohongshu.md ← 小红书
106
+ │ ├── x-twitter.md ← X (Twitter)
107
+ │ ├── toutiao.md ← 头条号
108
+ │ └── blog.md ← 个人博客
109
+
110
+ ├── accounts/ ← 账号配置层
111
+ │ ├── _template.yaml ← 账号配置模板
112
+ │ └── <账号名>.yaml ← 账号配置
113
+
114
+ ├── sources/ ← 素材(全局共享,大模型打 tags)
115
+ │ ├── articles/ ← 爬虫抓取的文章
116
+ │ ├── video-transcripts/ ← 视频转录
117
+ │ ├── notes/ ← 灵感笔记
118
+ │ ├── data/ ← 数据素材
119
+ │ └── daily/ ← 每日素材卡片
120
+
121
+ ├── accounts/<账号名>/ ← 账号独立目录
122
+ │ ├── drafts/
123
+ │ ├── published/
124
+ │ └── assets/
125
+
126
+ ├── tools/
127
+ │ └── crawler/ ← Blog 爬虫
128
+ └── .claudeink/ ← 凭证和配置(自动生成)
129
+ └── credentials.json
130
+ ```
131
+
132
+ ## 云端同步(ClaudeInk API)
133
+
134
+ 工作流支持将元数据同步到 ClaudeInk 云端控制台(https://app.claudeink.com),在 Dashboard 中查看数据概览。
135
+
136
+ ### 同步指令
137
+
138
+ - `/同步`:将本地素材和草稿的元数据推送到云端
139
+ - `/报告`:从云端拉取数据分析报告
140
+
141
+ ### 同步机制
142
+
143
+ 同步只上传**元数据**(标题、标签、状态、字数),**全文内容不上传**,保留在本地。
144
+
145
+ ```bash
146
+ # 同步实现(由 Claude 在 /同步 时执行)
147
+ curl -X POST https://app.claudeink.com/api/sync/batch \
148
+ -H "Authorization: Bearer <TOKEN>" \
149
+ -H "Content-Type: application/json" \
150
+ -d '{"sources": [...], "drafts": [...], "analytics": [...]}'
151
+ ```
152
+
153
+ 凭证存储在 `.claudeink/credentials.json`,激活时自动生成。
154
+
155
+ ## 指令速查
156
+
157
+ 所有指令见 `base-rules.md` 的"指令集"章节。核心指令:
158
+
159
+ - `/切换 <账号名>`:切换当前操作账号,自动加载平台+账号配置
160
+ - `/新建账号`:交互式创建新账号配置
161
+ - `/适配 <平台>`:将当前内容适配到另一平台
162
+ - `/编写`:基于素材或主题编写文章
163
+ - `/标题`:为当前内容生成 10 条备选标题
164
+ - `/分析`:对素材执行内容结构分析
165
+ - `/内容分析`:对已写内容进行质量评估
166
+ - `/同步`:将本地元数据同步到 ClaudeInk 云端
167
+ - `/报告`:查看数据分析报告
@@ -0,0 +1,70 @@
1
+ # ============================================================
2
+ # 自媒体账号配置模板
3
+ # 复制此文件为 accounts/<账号名>.yaml,填入具体信息即可
4
+ #
5
+ # 配置优先级:账号 > 平台 > 通用底座
6
+ # 账号中定义的字段覆盖平台默认值,平台覆盖通用默认值
7
+ # ============================================================
8
+
9
+ # ── 基本信息 ──
10
+ name: "" # 账号名称
11
+ id: "" # 英文标识符(用于文件夹命名,如 auston、techpulse)
12
+ description: "" # 一句话定位
13
+ platform: "" # 所属平台:wechat / xiaohongshu / x-twitter / toutiao / blog
14
+ # 系统会自动加载 platforms/<platform>.md 中的平台规则
15
+
16
+ # ── 领域与受众 ──
17
+ domains: # 内容领域(用于素材聚类和模板匹配)
18
+ - "" # 示例:数码产品、AI、美股、健康、教育
19
+ audience:
20
+ target: "" # 目标读者画像
21
+ age_range: "" # 年龄段,如"25-40"
22
+ pain_points: # 读者核心痛点/需求
23
+ - ""
24
+
25
+ # ── 写作风格 ──
26
+ style:
27
+ tone: "" # 基调:幽默 / 正式 / 轻松 / 严肃 / 犀利
28
+ voice: "" # 语气:口语化 / 书面 / 学术 / 段子手
29
+ formality: "" # 正式度:high / medium / low
30
+ emotion: "" # 情绪浓度:克制理性 / 适度情绪 / 强情绪驱动
31
+ humor: "" # 幽默程度:无 / 偶尔 / 频繁
32
+ language: "" # 语言:zh-CN / en / bilingual
33
+ example_sentence: "" # 一句典型的该账号风格句子,用于找感觉
34
+
35
+ # ── 人设 ──
36
+ persona:
37
+ role: "" # 角色定位,如"硬核科技博主""投资老司机""生活方式编辑"
38
+ background: "" # 人设背景,如"前券商分析师""10年果粉"
39
+ first_person: "" # 第一人称称呼,如"我""笔者""老王"
40
+ catchphrase: "" # 口头禅或标志性表达(可选)
41
+
42
+ # ── 内容规则(覆盖平台和通用默认值) ──
43
+ content_rules:
44
+ word_count: null # 正文目标字数(null = 使用平台默认值)
45
+ summary_word_count: null # 精写版字数
46
+ title_max_length: null # 标题最大字数(null = 使用平台默认值)
47
+ title_style: "" # 标题风格偏好:悬念型 / 数据型 / 反问型 / 故事型 / 混合
48
+ paragraph_max_lines: null # 每段最大行数
49
+ special_terms: [] # 该账号特有术语的处理规则
50
+ banned_words: [] # 该账号额外禁用的词汇
51
+ required_elements: [] # 每篇文章必须包含的元素
52
+ hashtags: [] # 常用话题标签(小红书、X、头条适用)
53
+
54
+ # ── 内容模板 ──
55
+ templates:
56
+ primary: # 最常用的模板(列表,引用通用模板名)
57
+ - ""
58
+ custom: [] # 账号专属模板
59
+ # custom 示例:
60
+ # - name: "周报"
61
+ # structure: "本周要闻 → 深度解读 x2 → 值得关注 → 下周预告"
62
+ # word_count: 2000
63
+
64
+ # ── 文件路径 ──
65
+ # 素材(sources/)是全局共享的,不在账号目录下
66
+ # 以下路径相对于项目根目录,新建账号时会自动创建
67
+ paths:
68
+ drafts: "accounts/{id}/drafts/"
69
+ published: "accounts/{id}/published/"
70
+ assets: "accounts/{id}/assets/"