@corvio/cli 0.1.0-beta.25 → 0.1.0-beta.27

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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +83 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -15,8 +15,8 @@ corvio files get <asset_id> --content --json
15
15
  corvio files organize <asset_id> --instruction 'Create a reusable decision brief' --yes --json
16
16
  corvio files operation <operation_id> --json
17
17
  corvio files resume <operation_id> --yes --json
18
- corvio docs table-read <document_id> --limit 25 --json
19
- corvio docs table-mutate <document_id> --input ./table-change.json --change-summary 'Updated owner and due date' --yes --json
18
+ corvio docs table-read <workspace_id/document_id> --limit 25 --json
19
+ corvio docs table-mutate <workspace_id/document_id> --input ./table-change.json --change-summary 'Updated owner and due date' --yes --json
20
20
  corvio sync init --dir ./knowledge --root-node-id <node_id> --json
21
21
  corvio update check --json
22
22
  ```
@@ -32,7 +32,7 @@ fresh host session. Until a reviewed WorkBuddy marketplace listing is live, a Wo
32
32
  auto-update from Corvio. Remote MCP changes are server-delivered and normally need only a refreshed host session unless new OAuth scopes
33
33
  require reauthorization.
34
34
 
35
- The CLI rejects unknown/duplicate options and malformed integer bounds before network access. Normal API calls have a bounded timeout (`CORVIO_REQUEST_TIMEOUT_MS`, maximum ten minutes); only reads, explicit idempotency contracts, revision guards, and other owner-declared safe operations retry automatically. Document updates preflight the current revision when `--expected-revision` is omitted. `docs table-read` returns a bounded stable-ID projection; `docs table-mutate` accepts a JSON payload for at most 50 cell updates or row appends and requires the fetched revision, while formulas, styles, structure, sorting, and semantic transformations remain `corvio ask --allow-actions` work. When the executing principal is a connected Agent, document and table mutations also require `--change-summary`, create a visible document-level comment after the guarded update, and return its receipt; a comment failure is reported as a partial effect rather than silent success. When a Corvio document or comment supplied the task, use `corvio agent closeout` to return the verified outcome, optionally resolve the thread, and read both document and thread back. Downloads, uploads, and Markdown pulls verify SHA-256 receipts before replacing local files; local writes are atomic, remote default filenames cannot escape the current directory, and Sync refuses symbolic-link traversal while checkpointing each successful push.
35
+ The CLI rejects unknown/duplicate options and malformed integer bounds before network access. Normal API calls have a bounded timeout (`CORVIO_REQUEST_TIMEOUT_MS`, maximum ten minutes); only reads, explicit idempotency contracts, revision guards, and other owner-declared safe operations retry automatically. Document commands accept both bare document IDs and canonical `workspace_id/document_id` handles; a canonical handle that disagrees with the selected Workspace fails before remote access, and successful reads return `canonical_id` for direct reuse. Document updates preflight the current revision when `--expected-revision` is omitted. `docs table-read` returns a bounded stable-ID projection; `docs table-mutate` accepts a JSON payload for at most 50 cell updates or row appends and requires the fetched revision, while formulas, styles, structure, sorting, and semantic transformations remain `corvio ask --allow-actions` work. When the executing principal is a connected Agent, document and table mutations also require `--change-summary`, create a visible document-level comment after the guarded update, and return its receipt; a comment failure is reported as a partial effect rather than silent success. When a Corvio document or comment supplied the task, use `corvio agent closeout` to return the verified outcome, optionally resolve the thread, and read both document and thread back. Downloads, uploads, and Markdown pulls verify SHA-256 receipts before replacing local files; local writes are atomic, remote default filenames cannot escape the current directory, and Sync refuses symbolic-link traversal while checkpointing each successful push.
36
36
 
37
37
  For `corvio ask`, pass the user's natural goal, complete decision-relevant context, and explicit constraints. Unless the user chose them,
38
38
  do not invent taxonomy, titles, artifact counts, or Corvio's internal plan. Read the terminal Question receipt and use `artifact.url` or
package/dist/cli.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  agentCredentialPath,
40
40
  } from "./core.js";
41
41
 
42
- const VERSION = "0.1.0-beta.25";
42
+ const VERSION = "0.1.0-beta.27";
43
43
  const DEFAULT_API_BASE = "https://api.corvio.ai/v1";
44
44
  const DEFAULT_APP_BASE = "https://corvio.ai";
45
45
  const USER_AGENT = `corvio-cli/${VERSION}`;
@@ -180,8 +180,8 @@ const COMMAND_HELP = Object.freeze({
180
180
  questions: `Usage:\n corvio questions list [--limit <n>] [--cursor <cursor>] [--conversation-id <id>]\n corvio questions get <question-id>`,
181
181
  conversations: `Usage:\n corvio conversations list [--limit <n>] [--cursor <cursor>]\n corvio conversations get <conversation-id>`,
182
182
  search: `Usage: corvio search <query> [--limit <n>] [--page-id <id>] [--node-id <id>]`,
183
- docs: `Usage:\n corvio docs list [--limit <n>] [--cursor <cursor>] [--lifecycle active|archived]\n corvio docs get <id> [--output <path>]\n corvio docs table-read <id> [--table-id <id>] [--after-row-id <id>] [--limit <1-100>]\n corvio docs table-mutate <id> --input <json> --yes [--operation-id <id>] [--change-summary <text>]\n corvio docs create --title <title> [--file <path>|--markdown <text>] [--parent-node-id <id>] [--idempotency-key <key>]\n corvio docs update <id> [--title <title>] [--file <path>|--markdown <text>] [--expected-revision <n>] [--change-summary <text>]\n corvio docs move <id> --parent-node-id <id>\n corvio docs archive <id> --yes\n corvio docs restore <id> --yes\n corvio docs share <id> --yes [--display-name <name>]\n corvio docs open <id>\n\nA selected Markdown file is uploaded once as a provenance-preserving Asset, then its source_asset_id becomes an editable Page without resending the body; inspect the returned document ID, node ID, revision, and URL. Other formats or semantic restructuring belong to \`corvio files organize\` or \`corvio ask --allow-actions\`. \`docs update\` replaces the Page body: use it only when whole-body replacement is the smallest faithful change; delegate large localized semantic edits, reorder, splice, or formatting to \`corvio ask --allow-actions\` with the canonical document handle. Use table-read plus table-mutate for bounded stable-ID cell updates or row appends without round-tripping a whole document. Agent-authenticated document changes require --change-summary and create a visible document-level comment after the guarded update. When a document/comment is the task owner, use \`corvio agent closeout\` to return verified work to that same collaboration surface and read it back.`,
184
- projects: `Usage:\n corvio projects list [--limit <n>]\n corvio projects create --title <title> [--idempotency-key <key>]\n\nProjects are durable grouping owners in the Corvio Docs tree. Reuse a matching Project instead of creating one ceremonial Project per file. 'corvio folders' is an alias.`,
183
+ docs: `Usage:\n corvio docs list [--limit <n>] [--cursor <cursor>] [--lifecycle active|archived]\n corvio docs get <id> [--output <path>]\n corvio docs table-read <id> [--table-id <id>] [--after-row-id <id>] [--limit <1-100>]\n corvio docs table-mutate <id> --input <json> --yes [--operation-id <id>] [--change-summary <text>]\n corvio docs create --title <title> [--file <path>|--markdown <text>] [--parent-node-id <id>] [--idempotency-key <key>]\n corvio docs update <id> [--title <title>] [--file <path>|--markdown <text>] [--expected-revision <n>] [--change-summary <text>]\n corvio docs move <id> --parent-node-id <id>\n corvio docs archive <id> --yes\n corvio docs restore <id> --yes\n corvio docs share <id> --yes [--display-name <name>]\n corvio docs open <id>\n\nA selected Markdown file is uploaded once as a provenance-preserving Asset, then its source_asset_id becomes an editable Page without resending the body; inspect the returned document ID, node ID, revision, and URL. Other formats or semantic restructuring belong to \`corvio files organize\` or \`corvio ask --allow-actions\`. \`docs update\` replaces the Page body: use it only when whole-body replacement is the smallest faithful change; delegate large localized semantic edits, reorder, splice, or formatting to \`corvio ask --allow-actions\` with the canonical document handle. Use table-read plus table-mutate for bounded stable-ID cell updates or row appends without round-tripping a whole document. \`docs move\` accepts a non-Project document and a Project destination; Projects stay at the Workspace root and cannot be nested. Use \`corvio files organize\` or \`corvio ask --allow-actions\` when deeper structure needs semantic branch Pages and leaves. Agent-authenticated document changes require --change-summary and create a visible document-level comment after the guarded update. When a document/comment is the task owner, use \`corvio agent closeout\` to return verified work to that same collaboration surface and read it back.`,
184
+ projects: `Usage:\n corvio projects list [--limit <n>]\n corvio projects create --title <title> [--idempotency-key <key>]\n\nProjects are durable root-level grouping owners in the Corvio Docs tree and cannot be nested under another Project. Reuse a matching Project instead of creating one ceremonial Project per file; use semantic branch Pages and leaves for deeper Work Models. 'corvio folders' is an alias.`,
185
185
  files: `Usage:\n corvio files list [--limit <n>]\n corvio files get <id> [--content]\n corvio files download <id> [--output <path>]\n corvio files upload --file <path> [--mime-type <type>] [--organize --instruction <text> --additional-asset-ids <id,id> --yes]\n corvio files organize <id> --instruction <text> --yes [--additional-asset-ids <id,id>] [--processing-profile auto|economy|standard|deep] [--scan-mode auto|always|off] [--skills-extraction-mode auto|always|off]\n corvio files operation <operation-id>\n corvio files resume <operation-id> --yes\n corvio files cancel <operation-id> --yes\n corvio files open <id>\n\nUse --content only when source facts are needed. It returns one bounded AI-safe projection with hash, completeness, and truncation receipts; it does not rewrite or materialize the original. Upload alone preserves the exact original, provenance, hash, policy, and stable Asset ID. Organization is a separate asynchronous value step: one coherent source set can produce reader-facing structure, source reconciliation, Memory candidates, and qualifying Project Skills. Pass --instruction a weak natural goal plus explicit user constraints and only authority facts needed to prevent wrong identity or scope. Keep source-derived facts in the source; do not precompute an outline, edit checklist, taxonomy, titles, artifact count, or sole leaf target from current visibility. For a newly finalized Asset that should enter a Work Model or Skill evaluation, use organize directly instead of duplicate ask requests just to await processing. Use 'files operation' to reach terminal and inspect output_document plus skills_evaluation; always requires evaluation but may correctly return evaluated_no_qualifying_skill. Resume reuses the same durable operation after resolving its blocker; cancel is idempotent.`,
186
186
  sync: `Usage:\n corvio sync init --root-node-id <id> [--dir <path>] [--name <name>]\n corvio sync status|plan|pull [--dir <path>]\n corvio sync push --yes [--dir <path>]\n corvio sync resolve --conflict-id <id> --strategy use-remote|keep-local --yes [--dir <path>]`,
187
187
  update: `Usage: corvio update check [--json]\n\nCompare this executable with the reviewed API policy and the npm beta dist-tag.`,
@@ -189,8 +189,11 @@ const COMMAND_HELP = Object.freeze({
189
189
 
190
190
  function contextualHelp(command, action) {
191
191
  const normalizedCommand = command === "workspace" ? "workspaces" : command === "folders" ? "projects" : command;
192
- const commandHelp = COMMAND_HELP[normalizedCommand];
193
- if (!commandHelp) return HELP.trimEnd();
192
+ const baseCommandHelp = COMMAND_HELP[normalizedCommand];
193
+ if (!baseCommandHelp) return HELP.trimEnd();
194
+ const commandHelp = normalizedCommand === "docs"
195
+ ? `${baseCommandHelp}\n\nDocument commands accept either a bare document ID or the canonical workspace_id/document_id handle returned by Corvio. A canonical handle that disagrees with the selected Workspace fails before any remote request. Document reads return canonical_id for direct reuse.`
196
+ : baseCommandHelp;
194
197
  if (!action) return commandHelp;
195
198
  const matchingLine = commandHelp
196
199
  .split("\n")
@@ -277,6 +280,53 @@ function resourcePath(value) {
277
280
  return encodeURIComponent(String(value));
278
281
  }
279
282
 
283
+ function resolveDocumentId(value, selection) {
284
+ const raw = String(value || "").trim();
285
+ if (!raw) {
286
+ throw new CliError("Provide a document ID or canonical workspace/document handle.", {
287
+ code: "document_id_required",
288
+ });
289
+ }
290
+ const handle = raw.startsWith("document:") ? raw.slice("document:".length) : raw;
291
+ if (!handle.includes("/")) return handle;
292
+ const parts = handle.split("/");
293
+ if (parts.length !== 2 || parts.some((part) => !part.trim())) {
294
+ throw new CliError(
295
+ "Document handles must be a document ID or canonical workspace_id/document_id pair.",
296
+ { code: "document_handle_invalid" },
297
+ );
298
+ }
299
+ const [workspaceId, documentId] = parts.map((part) => part.trim());
300
+ if (!selection?.workspaceId) {
301
+ throw new CliError(
302
+ "A canonical document handle requires an explicit or resolved Workspace route.",
303
+ { code: "document_workspace_route_unavailable", exitCode: EXIT.AUTH },
304
+ );
305
+ }
306
+ if (selection?.workspaceId && workspaceId !== selection.workspaceId) {
307
+ throw new CliError(
308
+ `The document handle belongs to Workspace ${workspaceId}, but the current route is ${selection.workspaceId}.`,
309
+ {
310
+ code: "document_workspace_mismatch",
311
+ exitCode: EXIT.AUTH,
312
+ details: {
313
+ handle_workspace_id: workspaceId,
314
+ selected_workspace_id: selection.workspaceId,
315
+ },
316
+ },
317
+ );
318
+ }
319
+ return documentId;
320
+ }
321
+
322
+ function withCanonicalDocumentId(value, selection, fallbackDocumentId = null) {
323
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
324
+ const workspaceId = String(selection?.workspaceId || "").trim();
325
+ const documentId = String(fallbackDocumentId || value.id || "").trim();
326
+ if (!workspaceId || !documentId) return value;
327
+ return { ...value, canonical_id: `${workspaceId}/${documentId}` };
328
+ }
329
+
280
330
  function safeRemoteFileName(value, fallback) {
281
331
  const normalized = String(value || "").replaceAll("\\", "/");
282
332
  const name = basename(normalized).trim();
@@ -2821,7 +2871,7 @@ async function search(options, positionals) {
2821
2871
  }
2822
2872
 
2823
2873
  async function docs(options, action, id) {
2824
- const { api, executionIdentity } = await executionClient(options);
2874
+ const { api, executionIdentity, selection } = await executionClient(options);
2825
2875
  const documentId = id || options.id;
2826
2876
  let result;
2827
2877
  if (action === "list") {
@@ -2830,8 +2880,19 @@ async function docs(options, action, id) {
2830
2880
  throw new CliError("--lifecycle must be active or archived.", { code: "document_lifecycle_invalid" });
2831
2881
  }
2832
2882
  result = await api.request("/documents", { query: { ...pageQuery(options), lifecycle } });
2883
+ if (Array.isArray(result?.data)) {
2884
+ result = {
2885
+ ...result,
2886
+ data: result.data.map((item) => withCanonicalDocumentId(item, selection)),
2887
+ };
2888
+ }
2833
2889
  } else if (action === "get") {
2834
- result = await api.request(`/documents/${resourcePath(documentId || option(options, "id", { required: true }))}`);
2890
+ const targetId = resolveDocumentId(documentId, selection);
2891
+ result = withCanonicalDocumentId(
2892
+ await api.request(`/documents/${resourcePath(targetId)}`),
2893
+ selection,
2894
+ targetId,
2895
+ );
2835
2896
  if (executionIdentity === "agent") {
2836
2897
  result = {
2837
2898
  ...result,
@@ -2843,7 +2904,7 @@ async function docs(options, action, id) {
2843
2904
  result = { ...result, output: options.output };
2844
2905
  }
2845
2906
  } else if (action === "table-read") {
2846
- const targetId = documentId || option(options, "id", { required: true });
2907
+ const targetId = resolveDocumentId(documentId, selection);
2847
2908
  result = await api.request(`/documents/${resourcePath(targetId)}/table`, {
2848
2909
  query: {
2849
2910
  table_id: options["table-id"] || undefined,
@@ -2853,7 +2914,7 @@ async function docs(options, action, id) {
2853
2914
  });
2854
2915
  } else if (action === "table-mutate") {
2855
2916
  requireConfirmation(options, "Mutating a document table");
2856
- const targetId = documentId || option(options, "id", { required: true });
2917
+ const targetId = resolveDocumentId(documentId, selection);
2857
2918
  const payload = await readJsonObject(option(options, "input", { required: true }), "--input");
2858
2919
  const changeSummary = String(options["change-summary"] || "").trim();
2859
2920
  if (executionIdentity === "agent" && !changeSummary) {
@@ -2931,7 +2992,7 @@ async function docs(options, action, id) {
2931
2992
  code: "document_update_empty",
2932
2993
  });
2933
2994
  }
2934
- const targetId = documentId || option(options, "id", { required: true });
2995
+ const targetId = resolveDocumentId(documentId, selection);
2935
2996
  const changeSummary = String(options["change-summary"] || "").trim();
2936
2997
  if (executionIdentity === "agent" && !changeSummary) {
2937
2998
  throw new CliError(
@@ -3005,7 +3066,8 @@ async function docs(options, action, id) {
3005
3066
  }
3006
3067
  } else if (action === "move") {
3007
3068
  rejectTogether(options, "before-node-id", "after-node-id");
3008
- const current = await api.request(`/documents/${resourcePath(documentId || option(options, "id", { required: true }))}`);
3069
+ const targetId = resolveDocumentId(documentId, selection);
3070
+ const current = await api.request(`/documents/${resourcePath(targetId)}`);
3009
3071
  result = await api.request(`/nodes/${resourcePath(current.node_id)}:move`, {
3010
3072
  method: "POST",
3011
3073
  headers: { "idempotency-key": idempotencyKey(options) },
@@ -3017,25 +3079,31 @@ async function docs(options, action, id) {
3017
3079
  });
3018
3080
  } else if (action === "archive") {
3019
3081
  requireConfirmation(options, "Archiving a document");
3020
- result = await api.request(`/documents/${resourcePath(documentId || option(options, "id", { required: true }))}`, { method: "DELETE" });
3082
+ const targetId = resolveDocumentId(documentId, selection);
3083
+ result = await api.request(`/documents/${resourcePath(targetId)}`, { method: "DELETE" });
3021
3084
  } else if (action === "restore") {
3022
3085
  requireConfirmation(options, "Restoring an archived document");
3023
- result = await api.request(`/documents/${resourcePath(documentId || option(options, "id", { required: true }))}/restore`, { method: "POST", retrySafe: true });
3086
+ const targetId = resolveDocumentId(documentId, selection);
3087
+ result = await api.request(`/documents/${resourcePath(targetId)}/restore`, { method: "POST", retrySafe: true });
3024
3088
  } else if (action === "share") {
3025
3089
  requireConfirmation(options, "Creating a document share link");
3026
- result = await api.request(`/documents/${resourcePath(documentId || option(options, "id", { required: true }))}/shares`, {
3090
+ const targetId = resolveDocumentId(documentId, selection);
3091
+ result = await api.request(`/documents/${resourcePath(targetId)}/shares`, {
3027
3092
  method: "POST",
3028
3093
  retrySafe: true,
3029
3094
  body: { public_display_name: options["display-name"] || null },
3030
3095
  });
3031
3096
  } else if (action === "open") {
3032
- const targetId = documentId || option(options, "id", { required: true });
3097
+ const targetId = resolveDocumentId(documentId, selection);
3033
3098
  const target = `${String(process.env.CORVIO_APP_BASE || DEFAULT_APP_BASE).replace(/\/+$/, "")}/app/d/${resourcePath(targetId)}`;
3034
3099
  const opened = options["no-input"] ? false : await openBrowser(target);
3035
3100
  result = { document_id: targetId, url: target, opened };
3036
3101
  } else {
3037
3102
  throw new CliError("docs action must be list, get, table-read, table-mutate, create, update, move, archive, restore, share, or open.");
3038
3103
  }
3104
+ if (!new Set(["list", "create"]).has(action)) {
3105
+ result = withCanonicalDocumentId(result, selection, resolveDocumentId(documentId, selection));
3106
+ }
3039
3107
  writeResult(result, { json: options.json });
3040
3108
  }
3041
3109
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corvio/cli",
3
- "version": "0.1.0-beta.25",
3
+ "version": "0.1.0-beta.27",
4
4
  "description": "Official Corvio Workspace CLI for questions, knowledge search, documents, and Agent workflows.",
5
5
  "type": "module",
6
6
  "bin": {