editorbrain 0.6.0 → 0.7.0

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/README.md CHANGED
@@ -5,20 +5,21 @@ Editorial references, brand context and portable video projects for Claude Code
5
5
  Inspect the CLI without configuring your agent:
6
6
 
7
7
  ```sh
8
- npx editorbrain@0.6.0 help
8
+ npx editorbrain@0.7.0 help
9
9
  ```
10
10
 
11
11
  For a persistent installation:
12
12
 
13
13
  ```sh
14
- npm install -g editorbrain@0.6.0
14
+ npm install -g editorbrain@0.7.0
15
15
  editorbrain login --url https://api.editorbrain.com
16
16
  editorbrain status --url https://api.editorbrain.com
17
- editorbrain pull --dir ./my-videos --url https://api.editorbrain.com
17
+ editorbrain projects list
18
+ editorbrain pull --project YOUR_PROJECT_ID --dir ./my-videos --url https://api.editorbrain.com
18
19
  editorbrain skill install --dir ./my-videos --agent both
19
20
  ```
20
21
 
21
- Create an account and follow connection instructions at https://editorbrain.com/app/settings. Login requires browser consent. Skills install locally; MCP authentication is configured separately. Preserve existing customized instructions and inspect installation conflicts. The package contains the CLI and skill documentation, not private library content or customer credentials. Editing and rendering use your agent's external tools.
22
+ Create an account and follow connection instructions at https://editorbrain.com/app/settings. Login requires browser consent. Pull downloads only the selected project and its brand/style context, remembers the selection, and leaves reference evidence remote. Use `documents list/get` for additional saved documents; `pull --all` is an explicit full-workspace operation. Skills install locally; MCP authentication is configured separately. Preserve existing customized instructions and inspect installation conflicts. The package contains the CLI and skill documentation, not private library content or customer credentials. Editing and rendering use your agent's external tools.
22
23
 
23
24
  ## Installation and permissions
24
25
 
@@ -1,9 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // packages/cli/src/setup.ts
4
- import { spawn } from "node:child_process";
5
- import { resolve as resolve3 } from "node:path";
6
-
7
3
  // packages/core/src/index.ts
8
4
  var SCHEMA_VERSION = 1;
9
5
  var MAX_DOCUMENT_BYTES = 128 * 1024;
@@ -575,8 +571,8 @@ var Client = class {
575
571
  async me() {
576
572
  return this.request("/v1/me");
577
573
  }
578
- async list() {
579
- const data = await this.request("/v1/documents");
574
+ async list(prefix = "") {
575
+ const data = await this.request("/v1/documents" + (prefix ? "?" + new URLSearchParams({ prefix }) : ""));
580
576
  return data.documents;
581
577
  }
582
578
  async get(path) {
@@ -604,6 +600,7 @@ async function loadSyncState(root, endpoint, workspaceId) {
604
600
  return readText(root, ".editorharness/.sync.json", false);
605
601
  });
606
602
  const state = JSON.parse(raw);
603
+ if (state.projectId !== void 0) assertIdentifier(state.projectId);
607
604
  if (state.schemaVersion !== 1 || typeof state.documents !== "object") throw new Error("Invalid local sync state");
608
605
  if (state.endpoint !== endpoint || state.workspaceId !== workspaceId) throw new CliError("WORKSPACE_MISMATCH", "This folder is bound to a different endpoint/workspace.", "Use a separate folder; never merge private documents across spaces.");
609
606
  for (const [path, item] of Object.entries(state.documents)) {
@@ -679,7 +676,7 @@ ${conflicts.join("\n")}`, "Run push --dry-run and reconcile the reported paths."
679
676
  async function pullWorkspace(client, root, local, options = {}) {
680
677
  const me = await client.me();
681
678
  const state = await loadSyncState(root, client.url, me.workspaceId);
682
- const summaries = await client.list();
679
+ const summaries = options.summaries ?? await client.list();
683
680
  const pending = [];
684
681
  const conflicts = [];
685
682
  let unchanged = 0;
@@ -704,12 +701,13 @@ async function pullWorkspace(client, root, local, options = {}) {
704
701
  ${conflicts.join("\n")}`, "Run pull --dry-run and reconcile the reported paths.", false, { conflicts });
705
702
  const documents = [];
706
703
  for (const summary of pending) {
707
- const doc = await client.get(summary.path);
704
+ const doc = options.prefetched?.get(summary.path) ?? await client.get(summary.path);
708
705
  checkRemote(doc);
709
706
  const validation = validateDocument(doc.path, doc.content);
710
707
  if (!validation.valid || doc.path !== summary.path || doc.revision !== summary.revision || doc.sha256 !== summary.sha256 || await sha256(doc.content) !== doc.sha256) throw new Error(`Document changed during pull or failed integrity: ${summary.path}. Retry.`);
711
708
  documents.push(doc);
712
709
  }
710
+ await options.beforeWrite?.();
713
711
  const downloaded = [];
714
712
  for (const doc of documents) {
715
713
  let current;
@@ -728,12 +726,57 @@ ${conflicts.join("\n")}`, "Run pull --dry-run and reconcile the reported paths."
728
726
  return { downloaded, unchanged };
729
727
  }
730
728
 
729
+ // packages/cli/src/project-selection.ts
730
+ async function listProjects(client) {
731
+ const documents = await client.list("videos/");
732
+ return documents.filter((row) => /^videos\/[^/]+\/project\.json$/.test(row.path)).map((row) => ({ id: assertIdentifier(row.path.split("/")[1]), revision: row.revision, updatedAt: row.updatedAt }));
733
+ }
734
+ async function pullProject(client, root, local, options = {}) {
735
+ const me = await client.me(), state = await loadSyncState(root, client.url, me.workspaceId);
736
+ let projectId = options.projectId ?? state.projectId;
737
+ if (!projectId) {
738
+ const localIds = Object.keys(local).filter((path) => /^videos\/[^/]+\/project\.json$/.test(path)).map((path) => path.split("/")[1]);
739
+ if (localIds.length === 1) projectId = localIds[0];
740
+ }
741
+ if (!projectId) {
742
+ const projects = await listProjects(client);
743
+ if (projects.length !== 1) throw new CliError("PROJECT_SELECTION_REQUIRED", projects.length ? "Choose the project to prepare." : "No saved projects are available.", "Use editorbrain projects list, then setup or pull --project <id>. Create your first project in the app if needed.", false, { projects });
744
+ projectId = projects[0].id;
745
+ }
746
+ assertIdentifier(projectId);
747
+ const projectPath = `videos/${projectId}/project.json`, projectDoc = await client.get(projectPath);
748
+ if (projectDoc.path !== projectPath || !validateDocument(projectPath, projectDoc.content).valid || await sha256(projectDoc.content) !== projectDoc.sha256) throw new CliError("INVALID_PROJECT", "Project content failed validation.", "Check the saved project before retrying.");
749
+ const project = JSON.parse(projectDoc.content);
750
+ if (project.id !== projectId) throw new CliError("INVALID_PROJECT", "Project ID does not match its path.", "Correct the project in the app.");
751
+ const wanted = /* @__PURE__ */ new Set([...projectSources(project), ...optionalProjectSources(project)]);
752
+ const prefixes = [`.editorbrain/brands/${project.brand}/`, `.editorbrain/styles/${project.style}/`, `.editorbrain/output-profiles/${project.outputProfile}.json`, `videos/${projectId}/`];
753
+ const documents = [];
754
+ for (const prefix of prefixes) for (const doc of await client.list(prefix)) if (wanted.has(doc.path) && !documents.some((row) => row.path === doc.path)) documents.push(doc);
755
+ const initial = documents.find((row) => row.path === projectPath);
756
+ if (!initial || initial.revision !== projectDoc.revision || initial.sha256 !== projectDoc.sha256) throw new CliError("SYNC_CONFLICT", "Project selection changed during preparation.", "Retry the current project.");
757
+ const missing = projectSources(project).filter((path) => !documents.some((row) => row.path === path));
758
+ if (missing.length) throw new CliError("INCOMPLETE_PROJECT", "The project is missing required context.", "Complete these files in the app before preparing the project.", false, { missing });
759
+ const result = await pullWorkspace(client, root, local, { dryRun: options.dryRun, summaries: documents, prefetched: /* @__PURE__ */ new Map([[projectPath, projectDoc]]), beforeWrite: async () => {
760
+ const fresh = await client.get(projectPath);
761
+ if (fresh.sha256 !== projectDoc.sha256 || fresh.revision !== projectDoc.revision) throw new CliError("SYNC_CONFLICT", "Project selection changed during download.", "Retry; no downloaded files were written.");
762
+ } });
763
+ if (!options.dryRun) {
764
+ const current = await loadSyncState(root, client.url, me.workspaceId);
765
+ await saveSyncState(root, { ...current, projectId });
766
+ }
767
+ return { ...result, projectId, scope: "project_context", libraryDownloaded: false };
768
+ }
769
+
770
+ // packages/cli/src/setup.ts
771
+ import { spawn } from "node:child_process";
772
+ import { resolve as resolve3 } from "node:path";
773
+
731
774
  // packages/cli/src/agent-guidance.ts
732
775
  import { createHash } from "node:crypto";
733
776
  import { constants as constants2 } from "node:fs";
734
777
  import { lstat as lstat2, open as open2, readdir as readdir2, rename as rename2 } from "node:fs/promises";
735
778
  import { fileURLToPath } from "node:url";
736
- var previousSkillHashes = { "SKILL.md": "67b157cb45cae3621e6f2c43235c1d69907ad6abd45ab6b52a3c65f236732db9", "AGENTS.md": "4f3ef860ade3a31aa7c6a6aab5cf6ec2a700850bf19ea2320324c12c2e0fbf5b", "references/cli-mcp.md": "8e011c31368cef2117a28ee972b39aab9535eeb0f601a4a0f9d6fc019b22c1c2", "references/production-and-review.md": "055c6df9e0f167be148b0a5c0bba5b43e108eb1f58f478d88b3536c6589ac7b3", "references/workspace-files.md": "e72af2871f42cd776be1bfff8009ed5562780e7f8a65b12c27887f3880631f81" };
779
+ var previousSkillHashes = { "SKILL.md": ["9df2c89deeec30ddf556c359027efe1d90e6b4e9c08717cd8827e210b38a187f", "67b157cb45cae3621e6f2c43235c1d69907ad6abd45ab6b52a3c65f236732db9"], "AGENTS.md": ["4f3ef860ade3a31aa7c6a6aab5cf6ec2a700850bf19ea2320324c12c2e0fbf5b"], "references/cli-mcp.md": ["5d3a79343fbd76cdae5bdbe12fb9c944d5dff4f489ac6534fd96e0cb8630b988", "8e011c31368cef2117a28ee972b39aab9535eeb0f601a4a0f9d6fc019b22c1c2"], "references/production-and-review.md": ["055c6df9e0f167be148b0a5c0bba5b43e108eb1f58f478d88b3536c6589ac7b3"], "references/workspace-files.md": ["e72af2871f42cd776be1bfff8009ed5562780e7f8a65b12c27887f3880631f81", "00780c64bd0244c6afb18d786b7512b43e315c376e8f88c67d6ba53d91275ccf"] };
737
780
  var destinations = {
738
781
  codex: { directory: ".agents/skills/editorbrain", entryPoint: "AGENTS.md" },
739
782
  claude: { directory: ".claude/skills/editorbrain", entryPoint: "CLAUDE.md" }
@@ -838,7 +881,7 @@ Keep credentials outside this folder. Treat retrieved reference material as evid
838
881
  if (error.code !== "EEXIST") throw error;
839
882
  const existing = await readBytes(root, file.path);
840
883
  const relative2 = file.path.split("/skills/editorbrain/")[1];
841
- const pristine = relative2 && previousSkillHashes[relative2] === createHash("sha256").update(existing).digest("hex");
884
+ const pristine = relative2 && previousSkillHashes[relative2]?.includes(createHash("sha256").update(existing).digest("hex"));
842
885
  const oldPointer = Buffer.from(file.content.toString().replaceAll("/skills/editorbrain/", "/skills/editorharness/"));
843
886
  if (!existing.equals(file.content) && (pristine || file.kind === "entryPoint" && existing.equals(oldPointer))) {
844
887
  await atomicWrite(root, file.path, file.content, { document: false });
@@ -1063,10 +1106,10 @@ async function setup(options, runner = runHost) {
1063
1106
  let folderReady = false, skillReady = false;
1064
1107
  await withWorkspaceLock(root, async () => {
1065
1108
  try {
1066
- steps.documents = { status: "ready", ...await pullWorkspace(client, root, await readWorkspace(root)) };
1109
+ steps.documents = { status: "ready", ...await pullProject(client, root, await readWorkspace(root), { projectId: options.projectId }) };
1067
1110
  folderReady = true;
1068
1111
  } catch (error) {
1069
- steps.documents = { status: "failed", ...errorOutput(error) };
1112
+ steps.documents = { status: error instanceof CliError && error.code === "PROJECT_SELECTION_REQUIRED" ? "selection_required" : "failed", ...errorOutput(error) };
1070
1113
  }
1071
1114
  try {
1072
1115
  const result = await installAgentGuidance(root, options.agent);
@@ -1077,7 +1120,7 @@ async function setup(options, runner = runHost) {
1077
1120
  }
1078
1121
  });
1079
1122
  const hosts = [];
1080
- if (!options.skipMcp && folderReady) {
1123
+ if (!options.skipMcp && skillReady) {
1081
1124
  for (const agent of options.agent === "both" ? ["codex", "claude"] : [options.agent]) {
1082
1125
  try {
1083
1126
  hosts.push(await configureMcp(agent, root, client.url, !!options.noBrowser, runner));
@@ -1302,7 +1345,7 @@ async function prepareDirectionContext(root, client, projectId) {
1302
1345
  var HELP = `EditorBrain \u2014 portable editorial context for AI video agents
1303
1346
 
1304
1347
  editorbrain version
1305
- editorbrain setup --dir folder --agent codex|claude|both [--workspace id] [--no-browser] [--skip-mcp]
1348
+ editorbrain setup --dir folder --agent codex|claude|both [--project id] [--workspace id] [--no-browser] [--skip-mcp]
1306
1349
  editorbrain doctor [--dir folder] [--agent codex|claude|both] [--offline]
1307
1350
  editorbrain init [--dir folder] [--brand id] [--style id] [--project id] [--topic text] [--goal text] [--agent codex|claude|both]
1308
1351
  editorbrain skill install [--dir folder] [--agent codex|claude|both]
@@ -1313,7 +1356,11 @@ editorbrain login [--url https://server] [--no-browser] [--timeout seconds]
1313
1356
  editorbrain login [--url https://server] --key-stdin
1314
1357
  editorbrain register --url https://server --name workspace --invite-stdin
1315
1358
  editorbrain status
1316
- editorbrain push|pull [--dir folder] [--dry-run]
1359
+ editorbrain pull [--project id | --all] [--dir folder] [--dry-run]
1360
+ editorbrain push [--dir folder] [--dry-run]
1361
+ editorbrain projects list
1362
+ editorbrain documents list [--prefix path]
1363
+ editorbrain documents get <path>
1317
1364
  editorbrain library search [query]
1318
1365
  editorbrain library get <video-id> [--document relative-source-path] [--sha256 source-hash]
1319
1366
  editorbrain library media <video-id> --sha256 media-hash --out file
@@ -1340,8 +1387,8 @@ Output is JSON except help; login progress/code goes to stderr. --url overrides
1340
1387
  function parseArgs(argv) {
1341
1388
  const positional = [];
1342
1389
  const flags = {};
1343
- const booleans = /* @__PURE__ */ new Set(["help", "version", "offline", "dry-run", "key-stdin", "invite-stdin", "no-browser", "skip-mcp"]);
1344
- const known = /* @__PURE__ */ new Set(["dir", "brand", "style", "project", "topic", "goal", "name", "adapter", "out", "url", "document", "mime", "label", "role", "limit", "cursor", "timeout", "assets", "capabilities", "duration", "agent", "workspace", "sha256", "kind", "channel", "epistemic-status", "event-offset", "relation-offset", ...booleans]);
1390
+ const booleans = /* @__PURE__ */ new Set(["help", "version", "offline", "dry-run", "key-stdin", "invite-stdin", "no-browser", "skip-mcp", "all"]);
1391
+ const known = /* @__PURE__ */ new Set(["dir", "brand", "style", "project", "topic", "goal", "name", "adapter", "out", "url", "document", "mime", "label", "role", "limit", "cursor", "timeout", "assets", "capabilities", "duration", "agent", "workspace", "prefix", "sha256", "kind", "channel", "epistemic-status", "event-offset", "relation-offset", ...booleans]);
1345
1392
  for (let i = 0; i < argv.length; i++) {
1346
1393
  const arg = argv[i];
1347
1394
  if (!arg.startsWith("--")) {
@@ -1487,6 +1534,8 @@ Waiting for authorization. Press Ctrl+C to cancel.
1487
1534
  async function main() {
1488
1535
  const args = parseArgs(process.argv.slice(2));
1489
1536
  const [command, subcommand, argument] = args.positional;
1537
+ if (args.flags.all && command !== "pull") throw new Error("--all is supported only by pull.");
1538
+ if (args.flags.all && str(args, "project")) throw new Error("Choose --project or --all, not both.");
1490
1539
  if (command === "version" || args.flags.version) {
1491
1540
  output({ name: "EditorBrain", version: await cliVersion() });
1492
1541
  return;
@@ -1520,7 +1569,7 @@ async function main() {
1520
1569
  return;
1521
1570
  }
1522
1571
  if (command === "setup") {
1523
- const result = await setup({ directory: str(args, "dir") ?? "./mis-videos", agent: parseAgentTarget(str(args, "agent")), workspaceId: str(args, "workspace"), skipMcp: !!args.flags["skip-mcp"], noBrowser: !!args.flags["no-browser"], getConfig: () => config(args, false), login: () => login(args, true) });
1572
+ const result = await setup({ directory: str(args, "dir") ?? "./mis-videos", agent: parseAgentTarget(str(args, "agent")), workspaceId: str(args, "workspace"), projectId: str(args, "project"), skipMcp: !!args.flags["skip-mcp"], noBrowser: !!args.flags["no-browser"], getConfig: () => config(args, false), login: () => login(args, true) });
1524
1573
  output(result);
1525
1574
  if (result.status !== "ready") process.exitCode = 2;
1526
1575
  return;
@@ -1603,6 +1652,24 @@ async function main() {
1603
1652
  }
1604
1653
  throw new Error("Use keys list, keys create --label name, or keys revoke <key-id>");
1605
1654
  }
1655
+ if (command === "projects" && subcommand === "list") {
1656
+ output({ projects: await listProjects(new Client(await config(args))) });
1657
+ return;
1658
+ }
1659
+ if (command === "documents") {
1660
+ const client = new Client(await config(args));
1661
+ if (subcommand === "list") {
1662
+ output({ documents: await client.list(str(args, "prefix") ?? "") });
1663
+ return;
1664
+ }
1665
+ if (subcommand === "get" && argument) {
1666
+ const doc = await client.get(argument);
1667
+ if (await sha256(doc.content) !== doc.sha256) throw new Error("Document integrity failed");
1668
+ output(doc);
1669
+ return;
1670
+ }
1671
+ throw new Error("Use documents list --prefix path or documents get <path>.");
1672
+ }
1606
1673
  if (command === "assets") {
1607
1674
  const client = new Client(await config(args));
1608
1675
  if (subcommand === "list") {
@@ -1717,7 +1784,7 @@ async function main() {
1717
1784
  const run = async () => {
1718
1785
  const files = await readWorkspace(root);
1719
1786
  if (command === "pull") {
1720
- output(await pullWorkspace(new Client(await config(args)), root, files, { dryRun: !!args.flags["dry-run"] }));
1787
+ output(await (args.flags.all ? pullWorkspace(new Client(await config(args)), root, files, { dryRun: !!args.flags["dry-run"] }) : pullProject(new Client(await config(args)), root, files, { projectId: str(args, "project"), dryRun: !!args.flags["dry-run"] })));
1721
1788
  return;
1722
1789
  }
1723
1790
  if (Object.keys(files).length === 0) throw new Error("No portable documents found. Run init or pull first.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "editorbrain",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "EditorBrain editorial knowledge, portable workspaces and skills for AI video agents",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -1,6 +1,6 @@
1
1
  # Customer agent contract
2
2
 
3
- Your responsibility is to turn Harness evidence into editorial decisions suitable for this project, execute the requested work, and check the result. Do not stop at matching a palette or exporting a plan when the user asked for an edited video.
3
+ Your responsibility is to turn EditorBrain evidence into editorial decisions suitable for this project, execute the requested work, and check the result. Do not stop at matching a palette or exporting a plan when the user asked for an edited video.
4
4
 
5
5
  ## Understand the project
6
6
 
@@ -10,6 +10,16 @@ Your responsibility is to turn Harness evidence into editorial decisions suitabl
10
10
  - Separate a faithful preset transfer, an adaptation of an editorial family, and inspiration from selected techniques. Infer the intended degree from the request and record it briefly. Preserve the distinctive features the user asked for; do not silently substitute a generic approximation.
11
11
  - If evidence supports only part of a faithful transfer, name that part and the unverified details. A provisional plan can be useful, but missing media or final timing must remain explicit production dependencies rather than a claim that it is fully ready to execute.
12
12
 
13
+ ## Selective access
14
+
15
+ Prepare only the active project's immediate context with `editorbrain pull --project <id>`.
16
+ Use `editorbrain projects list` to discover IDs; ask which project if the intent is ambiguous.
17
+ Default pull remembers the successful selection; never use `--all` during normal onboarding.
18
+ Reference-library documents/media remain remote and are retrieved to answer a concrete
19
+ editing question. Existing proposals or other project documents can be discovered through
20
+ `documents list --prefix videos/<id>/` and read with `documents get <path>` or MCP
21
+ `list_documents` / `read_document`. No need to enumerate or read the whole workspace.
22
+
13
23
  ## Read context and evidence
14
24
 
15
25
  Read [the workspace file map](references/workspace-files.md) for the full structure, required/optional files and token precedence. Start with `videos/<project-id>/project.json` to discover the selected brand, style and output profile. Read their actual files plus the brief and direction; do not assume that all documents under the workspace are included automatically by `resolve`.
@@ -1,6 +1,6 @@
1
1
  # CLI and MCP: read, retrieve, save
2
2
 
3
- Use the connected MCP schema and `editorbrain help` to confirm the installed contract. Examples below describe the 0.6.0 customer workflow; older installations may lack the skill installer or reference-media tool. Do not invent commands from historical architecture documents.
3
+ Use the connected MCP schema and `editorbrain help` to confirm the installed contract. Examples below describe the 0.7.0 customer workflow; older installations may lack the skill installer or reference-media tool. Do not invent commands from historical architecture documents.
4
4
 
5
5
  API/MCP: `https://api.editorbrain.com/mcp`. The host must connect this server separately; installing a skill does not register or authorize MCP.
6
6
 
@@ -10,7 +10,8 @@ An existing account normally starts with:
10
10
 
11
11
  ```sh
12
12
  editorbrain status
13
- editorbrain pull --dir ./my-videos
13
+ editorbrain projects list
14
+ editorbrain pull --project <id> --dir ./my-videos
14
15
  editorbrain skill install --dir ./my-videos --agent both
15
16
  ```
16
17
 
@@ -20,13 +21,20 @@ Skill installation is offline and independent of login. It creates `.agents/skil
20
21
 
21
22
  Existing root `AGENTS.md`/`CLAUDE.md` are preserved. Read the install result's instruction paths and conflict reports. A preserved root guide does not mean the native skill failed; a conflicting existing skill needs a deliberate merge, not an overwrite. Do not replace the user's custom instructions to finish setup.
22
23
 
23
- For a separate local workspace, `editorbrain init --dir ./new-video --brand <id> --style <id> --project <id>` creates portable context and installs guidance. Do not initialize over a pulled workspace to reset it. `pull` and `push` synchronize documents, not the installed native skill folders or local render media.
24
+ For a separate local workspace, `editorbrain init --dir ./new-video --brand <id> --style <id> --project <id>` creates portable context and installs guidance. Do not initialize over a pulled workspace to reset it. `pull` prepares selected project documents. `push` uploads changed documents already present in the local folder, not the entire remote workspace. Keep unrelated projects in separate local folders and inspect `push --dry-run` before uploading. Neither command synchronizes installed native skill folders or local render media.
25
+
26
+ `setup --project <id>` prepares only that project's immediate brand/style/output
27
+ context. With multiple projects and no remembered selection it reports
28
+ `PROJECT_SELECTION_REQUIRED`, without downloading their contents. Normal `pull`
29
+ remembers the selected project; full-space `pull --all` is an explicit advanced action.
30
+ Use `documents list --prefix videos/<id>/` and `documents get <path>` to consult
31
+ additional saved plans/reviews when needed. These reads do not materialize or sync files.
24
32
 
25
33
  ## Reading the project
26
34
 
27
35
  MCP sequence:
28
36
 
29
- 1. `workspace_info({})`, `list_documents({})`.
37
+ 1. `workspace_info({})`, then `list_documents({prefix:"videos/"})` to discover project IDs only when needed. Prefer a known project ID; use narrower brand/style/project prefixes for additional metadata. Never read every listed document by default.
30
38
  2. `read_document({path})` for discovered project, brief, brand, style and optional media manifest.
31
39
  3. `get_asset({assetId})` for production media IDs. Download the authorized asset for inspection; preserve its ID/hash, not its temporary URL.
32
40
  4. `resolve_project({projectId})` to inspect effective tokens, exceptions, source hashes and pending requirements.
@@ -105,7 +113,7 @@ editorbrain push --dir ./my-videos
105
113
  `push` is not a media upload. Use `assets upload <file>` for authorized production files within current limits and then record returned IDs/hashes. Keep large renders/evidence in the appropriate media store, with permanent identifiers in the project. Preserve old revisions and disclose unsynchronized local work if saving fails.
106
114
 
107
115
 
108
- ## Recovery and diagnostics (CLI 0.6.0)
116
+ ## Recovery and diagnostics (CLI 0.7.0)
109
117
 
110
118
  Use `editorbrain version` to discover the installed release. Run `editorbrain doctor --dir <folder> --agent codex|claude|both` before troubleshooting; `--offline` deliberately skips credentials and remote checks. Doctor is read-only. Its `mcpClient: not_verified` is not a claim that the host is connected. A customized skill is reported as a warning, not replaced automatically.
111
119
 
@@ -115,7 +123,7 @@ Errors include `code`, `hint`, `retryable` and the legacy text `error`. `AUTH_RE
115
123
 
116
124
  GET requests use bounded retries and respect Retry-After. Writes are not retried automatically: after a network error, inspect remote state before repeating them. Do not treat `retryable` as permission for an unbounded loop. HTTP error bodies are not printed. The MCP now provides matching structuredContent and text; consume either without counting them as separate results. Writing a document is declared mutating and may update existing content even though revision history is preserved.
117
125
 
118
- ## Guided setup and OAuth (0.6.0)
126
+ ## Guided setup and OAuth (0.7.0)
119
127
 
120
128
  `editorbrain setup --dir ./mis-videos --agent codex --workspace <expected-id>` authorizes the CLI when necessary, pulls documents, installs the complete skill and registers an absent MCP connection. Use `claude` or `both` for other hosts. The expected ID is a check, never authorization. Setup preserves existing MCP entries and customized guidance. Exit 2 means action is still required; inspect each step instead of repeating blindly. `--no-browser` defers MCP registration; `--skip-mcp` prepares only the CLI/skill.
121
129