@corvio/cli 0.1.0-beta.13 → 0.1.0-beta.15
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 +4 -3
- package/dist/cli.js +296 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,15 +7,16 @@ npm install --global @corvio/cli@beta
|
|
|
7
7
|
export CORVIO_API_KEY='cvu_...'
|
|
8
8
|
corvio auth status
|
|
9
9
|
corvio agents connect --provider codex --project . --json
|
|
10
|
+
corvio collaboration status --json --no-input
|
|
10
11
|
corvio ask --prompt 'Summarize recurring launch risks' --json
|
|
11
12
|
corvio files upload --file ./research.pdf --json
|
|
12
13
|
corvio sync init --dir ./knowledge --root-node-id <node_id> --json
|
|
13
14
|
corvio update check --json
|
|
14
15
|
```
|
|
15
16
|
|
|
16
|
-
When no environment key is configured, run `corvio auth login`. Login creates one user-level `cvu_` control-plane credential; it does not freeze the browser's current Workspace. Run `corvio agents connect --project .` from the project you want to expose. The command verifies the selected Codex, Claude Code, or Copilot host before server mutation, then idempotently creates or restores the Agent for that provider/device/project binding, grants current/future Workspace coverage under live membership/role and ACL limits, saves the one-time `cvg_` in a `0600` multi-binding registry, and starts that project's listener. Another project gets another Agent. Ordinary data commands prefer the binding whose root contains the current directory. Use `corvio workspaces use`, `--workspace`, and `--agent` as explicit routing/reproducibility controls. Legacy Workspace-bound `cvk_`, `cvg_`, and version-1 credential files remain compatible and are never widened. Local sync stores only credential-free handles and revision/hash baselines in `.corvio/state.json`; Markdown lives below `corvio_docs/`, and conflicts require explicit resolution. Update checks compare npm's published `beta` tag with the API compatibility policy but never self-install. See [the Corvio CLI guide](https://corvio.ai/developers/cli) for the full command and receipt contract.
|
|
17
|
+
When no environment key is configured, run `corvio auth login`. Login creates one user-level `cvu_` control-plane credential; it does not freeze the browser's current Workspace. Run `corvio agents connect --project .` from the project you want to expose. The command verifies the selected Codex, Claude Code, or Copilot host before server mutation, then idempotently creates or restores the Agent for that provider/device/project binding, grants current/future Workspace coverage under live membership/role and ACL limits, saves the one-time `cvg_` in a `0600` multi-binding registry, and starts that project's listener. `corvio collaboration status` then checks a separate foreground requirement: the current host must also discover the official Skill at the live server contract version. A listener receives inbound `@Agent` work; it does not make an unrelated foreground coding session collaborate automatically. Another project gets another Agent. Ordinary data commands prefer the binding whose root contains the current directory. Use `corvio workspaces use`, `--workspace`, and `--agent` as explicit routing/reproducibility controls. Legacy Workspace-bound `cvk_`, `cvg_`, and version-1 credential files remain compatible and are never widened. Local sync stores only credential-free handles and revision/hash baselines in `.corvio/state.json`; Markdown lives below `corvio_docs/`, and conflicts require explicit resolution. Update checks compare npm's published `beta` tag with the API compatibility policy but never self-install. See [the Corvio CLI guide](https://corvio.ai/developers/cli) for the full command and receipt contract.
|
|
17
18
|
|
|
18
|
-
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. When the executing principal is a connected Agent, `docs update` also requires `--change-summary`, creates a visible document-level comment after the guarded write, and returns its receipt; a comment failure is reported as a partial effect rather than silent success. 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.
|
|
19
|
+
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. When the executing principal is a connected Agent, `docs update` also requires `--change-summary`, creates a visible document-level comment after the guarded write, and returns 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.
|
|
19
20
|
|
|
20
21
|
## Project Agent collaboration
|
|
21
22
|
|
|
@@ -25,7 +26,7 @@ Connect the current project with the user credential. User and Agent credentials
|
|
|
25
26
|
corvio agents connect --provider codex --project . --json
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
Without `--display-name`, connect derives a provider-aware project name from the authenticated user, such as `Ada Codex · billing-service`; Settings remains the rename owner. The receipt distinguishes `Configured`, `CLI Ready`, `Listening`, and `
|
|
29
|
+
Without `--display-name`, connect derives a provider-aware project name from the authenticated user, such as `Ada Codex · billing-service`; Settings remains the rename owner. The receipt distinguishes `Configured`, `CLI Ready`, `Listening`, `Offline`, and `Foreground collaboration ready`, and includes the stable Agent, opaque project binding, Workspace scope, host permission/trigger policy, Skill status, and Settings link. The default `workspace_write` profile uses the provider's native sandbox. `owner_only` is the safe trigger default. Allowing Workspace members to invoke the local project or granting unrestricted host access requires explicit `--yes`.
|
|
29
30
|
|
|
30
31
|
The listener first runs a read-only provider planning pass, then asks Corvio Query for exact origin-document and collaboration context. `local_project` work runs inside the bound project; online document writes remain Query/Writer-owned. The provider consumes the Query result before replying, and completion records exact event/lease/question/comment lineage plus provider, project fingerprint, claimed files/checks, and observed Git working-tree state. A custom handler remains available for advanced adapters:
|
|
31
32
|
|
package/dist/cli.js
CHANGED
|
@@ -39,13 +39,14 @@ import {
|
|
|
39
39
|
agentCredentialPath,
|
|
40
40
|
} from "./core.js";
|
|
41
41
|
|
|
42
|
-
const VERSION = "0.1.0-beta.
|
|
42
|
+
const VERSION = "0.1.0-beta.15";
|
|
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}`;
|
|
46
46
|
const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
47
47
|
const MAX_SYNC_FILES = 2_000;
|
|
48
48
|
const MAX_MARKDOWN_CHARACTERS = 120_000;
|
|
49
|
+
const CORVIO_SKILL_INSTALL_COMMAND = "npx skills add https://corvio.ai/developers/skills/corvio-operate-workspace/corvio-operate-workspace.zip";
|
|
49
50
|
const GLOBAL_OPTIONS = Object.freeze([
|
|
50
51
|
"api-base",
|
|
51
52
|
"workspace",
|
|
@@ -67,6 +68,7 @@ const COMMAND_OPTIONS = Object.freeze({
|
|
|
67
68
|
"workspaces:use": ["id"],
|
|
68
69
|
"workspaces:create": ["name", "kind", "description", "idempotency-key"],
|
|
69
70
|
"capabilities:": [],
|
|
71
|
+
"collaboration:status": ["project", "provider"],
|
|
70
72
|
"ask:": ["prompt", "conversation-id", "sources", "allow-actions", "context-file", "idempotency-key"],
|
|
71
73
|
"agents:list": ["include-revoked"],
|
|
72
74
|
"agents:connect": [
|
|
@@ -85,20 +87,23 @@ const COMMAND_OPTIONS = Object.freeze({
|
|
|
85
87
|
"agent:comment": ["document-id", "body", "selection-file", "start-new-chain", "idempotency-key"],
|
|
86
88
|
"agent:reply": ["document-id", "id", "body", "source-event-id", "source-event-lease-token", "start-new-chain", "idempotency-key"],
|
|
87
89
|
"agent:status": ["document-id", "id", "status"],
|
|
90
|
+
"agent:closeout": ["document-id", "thread-id", "body", "status", "idempotency-key"],
|
|
88
91
|
"agent:run": ["once", "handler", "provider", "worker-id", "lease-seconds", "wait-seconds", "sources", "project"],
|
|
89
92
|
"questions:list": ["limit", "cursor", "conversation-id"],
|
|
90
93
|
"questions:get": ["id"],
|
|
91
94
|
"conversations:list": ["limit", "cursor"],
|
|
92
95
|
"conversations:get": ["id"],
|
|
93
96
|
"search:": ["query", "limit", "page-id", "node-id"],
|
|
94
|
-
"docs:list": ["limit", "cursor"],
|
|
97
|
+
"docs:list": ["limit", "cursor", "lifecycle"],
|
|
95
98
|
"docs:get": ["id", "output"],
|
|
96
99
|
"docs:create": ["title", "file", "markdown", "parent-node-id", "idempotency-key"],
|
|
97
100
|
"docs:update": ["id", "title", "file", "markdown", "expected-revision", "change-summary"],
|
|
98
101
|
"docs:move": ["id", "parent-node-id", "before-node-id", "after-node-id", "idempotency-key"],
|
|
99
102
|
"docs:archive": ["id"],
|
|
103
|
+
"docs:restore": ["id"],
|
|
100
104
|
"docs:share": ["id", "display-name"],
|
|
101
105
|
"docs:open": ["id"],
|
|
106
|
+
"projects:list": ["limit"],
|
|
102
107
|
"projects:create": ["title", "idempotency-key"],
|
|
103
108
|
"files:list": ["limit"],
|
|
104
109
|
"files:get": ["id"],
|
|
@@ -121,14 +126,15 @@ Usage:
|
|
|
121
126
|
corvio auth login|status|logout
|
|
122
127
|
corvio workspaces list|use|current|create [id]
|
|
123
128
|
corvio capabilities
|
|
129
|
+
corvio collaboration status [--project <path>] [--provider codex|claude_code|copilot]
|
|
124
130
|
corvio ask --prompt <text> [--conversation-id <id>] [--allow-actions]
|
|
125
131
|
corvio agents list|connect|create|update|rotate [id]
|
|
126
|
-
corvio agent claim|renew|complete|fail|comments|comment|reply|status|run [id]
|
|
132
|
+
corvio agent claim|renew|complete|fail|comments|comment|reply|status|closeout|run [id]
|
|
127
133
|
corvio questions list|get [id]
|
|
128
134
|
corvio conversations list|get [id]
|
|
129
135
|
corvio search <query>
|
|
130
|
-
corvio docs list|get|create|update|move|archive|share|open [id]
|
|
131
|
-
corvio projects create
|
|
136
|
+
corvio docs list|get|create|update|move|archive|restore|share|open [id]
|
|
137
|
+
corvio projects list|create
|
|
132
138
|
corvio files list|get|download|upload|organize|open [id]
|
|
133
139
|
corvio sync init|status|plan|pull|push|resolve
|
|
134
140
|
corvio update check
|
|
@@ -149,14 +155,15 @@ const COMMAND_HELP = Object.freeze({
|
|
|
149
155
|
auth: `Usage: corvio auth login|status|logout\n\nAuthenticate with a user-level device link, inspect the current principal, or revoke the stored credential.`,
|
|
150
156
|
workspaces: `Usage:\n corvio workspaces list\n corvio workspaces use <workspace-id>\n corvio workspaces current\n corvio workspaces create --name <name> [--kind personal|team]\n\nWorkspace status and blocked capability reasons are returned with each item.`,
|
|
151
157
|
capabilities: `Usage: corvio capabilities [--json]\n\nShow live API resources, principal scopes, selected Workspace status, and CLI compatibility.`,
|
|
158
|
+
collaboration: `Usage:\n corvio collaboration status [--project <path>] [--provider codex|claude_code|copilot]\n\nVerify that the current project has a bound Corvio Agent, the official collaboration Skill is discoverable at the live contract version, and the inbound listener state is reported separately. This is the foreground work-loop readiness check; a listening process alone is not enough.`,
|
|
152
159
|
ask: `Usage: corvio ask --prompt <text> [--conversation-id <id>] [--sources workspace,web,memory] [--allow-actions] [--context-file <json>] [--idempotency-key <key>]`,
|
|
153
|
-
agents: `Usage:\n corvio agents list\n corvio agents connect [--provider codex|claude_code|copilot] [--project <path>] [--permission-profile read_only|workspace_write|danger_full_access] [--trigger-policy owner_only|workspace_members] [--display-name <name>] [--recover-credential] [--no-listen]\n corvio agents create --handle <handle> --display-name <name> [--provider codex|claude_code|copilot|custom] [--permissions read,comment,edit]\n corvio agents update <id> [--display-name <name>] [--status active|paused|revoked] [--permissions <list>]\n corvio agents rotate <id>\n\n'connect' binds the current Git project (or --project path) to one stable personal Agent. Repeating it is idempotent; another project creates another Agent. The default workspace_write profile is limited by the provider's native sandbox. owner_only is the safe trigger default. Secrets and absolute project paths stay local.`,
|
|
154
|
-
agent: `Usage:\n corvio agent claim [--worker-id <id>] [--wait-seconds <0-30>]\n corvio agent renew <event-id> --lease-token <token> [--lease-seconds <30-900>]\n corvio agent complete <event-id> --lease-token <token> [--result-file <json>]\n corvio agent fail <event-id> --lease-token <token> --error <message>\n corvio agent mentions --document-id <id> [--query <name>]\n corvio agent comments --document-id <id>\n corvio agent comment --document-id <id> --body <text> [--selection-file <json>] [--start-new-chain] [--idempotency-key <key>]\n corvio agent reply <thread-id> --document-id <id> --body <text> [--source-event-id <id> --source-event-lease-token <token>|--start-new-chain] [--idempotency-key <key>]\n corvio agent status <thread-id> --document-id <id> --status open|resolved\n corvio agent run [--provider codex|claude_code|copilot|custom] [--handler <executable>] [--agent <id>] [--once]\n\nUse mentions before commenting when the target is uncertain; aliases resolve to stable user or Agent IDs and ambiguity fails closed. Agent-to-Agent comments require either a claimed source event or explicit --start-new-chain. The runner iteratively exchanges origin-scoped evidence with Corvio Query/Writer, then invokes the bound provider inside its local project. Local code changes use the recorded provider permission profile; complex Corvio document changes belong to Query/Writer. Provider children never receive Corvio credentials.`,
|
|
160
|
+
agents: `Usage:\n corvio agents list\n corvio agents connect [--provider codex|claude_code|copilot] [--project <path>] [--permission-profile read_only|workspace_write|danger_full_access] [--trigger-policy owner_only|workspace_members] [--display-name <name>] [--recover-credential] [--no-listen]\n corvio agents create --handle <handle> --display-name <name> [--provider codex|claude_code|copilot|custom] [--permissions read,comment,edit]\n corvio agents update <id> [--display-name <name>] [--status active|paused|revoked] [--permissions <list>]\n corvio agents rotate <id>\n\n'connect' binds the current Git project (or --project path) to one stable personal Agent. Repeating it is idempotent; another project creates another Agent. It reports provider/CLI, listener, and foreground Skill readiness separately; run corvio collaboration status after installing or refreshing the Skill. The default workspace_write profile is limited by the provider's native sandbox. owner_only is the safe trigger default. Secrets and absolute project paths stay local.`,
|
|
161
|
+
agent: `Usage:\n corvio agent claim [--worker-id <id>] [--wait-seconds <0-30>]\n corvio agent renew <event-id> --lease-token <token> [--lease-seconds <30-900>]\n corvio agent complete <event-id> --lease-token <token> [--result-file <json>]\n corvio agent fail <event-id> --lease-token <token> --error <message>\n corvio agent mentions --document-id <id> [--query <name>]\n corvio agent comments --document-id <id>\n corvio agent comment --document-id <id> --body <text> [--selection-file <json>] [--start-new-chain] [--idempotency-key <key>]\n corvio agent reply <thread-id> --document-id <id> --body <text> [--source-event-id <id> --source-event-lease-token <token>|--start-new-chain] [--idempotency-key <key>]\n corvio agent status <thread-id> --document-id <id> --status open|resolved\n corvio agent closeout --document-id <id> [--thread-id <id>] --body <verified-result> [--status open|resolved] [--idempotency-key <key>]\n corvio agent run [--provider codex|claude_code|copilot|custom] [--handler <executable>] [--agent <id>] [--once]\n\nUse mentions before commenting when the target is uncertain; aliases resolve to stable user or Agent IDs and ambiguity fails closed. Use closeout when a Corvio document or comment owned the task: it returns the verified result to that surface and reads document/comments back. Agent-to-Agent comments require either a claimed source event or explicit --start-new-chain. The runner iteratively exchanges origin-scoped evidence with Corvio Query/Writer, then invokes the bound provider inside its local project. Local code changes use the recorded provider permission profile; complex Corvio document changes belong to Query/Writer. Provider children never receive Corvio credentials.`,
|
|
155
162
|
questions: `Usage:\n corvio questions list [--limit <n>] [--cursor <cursor>] [--conversation-id <id>]\n corvio questions get <question-id>`,
|
|
156
163
|
conversations: `Usage:\n corvio conversations list [--limit <n>] [--cursor <cursor>]\n corvio conversations get <conversation-id>`,
|
|
157
164
|
search: `Usage: corvio search <query> [--limit <n>] [--page-id <id>] [--node-id <id>]`,
|
|
158
|
-
docs: `Usage:\n corvio docs list [--limit <n>] [--cursor <cursor>]\n corvio docs get <id> [--output <path>]\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 share <id> --yes [--display-name <name>]\n corvio docs open <id>\n\
|
|
159
|
-
projects: `Usage
|
|
165
|
+
docs: `Usage:\n corvio docs list [--limit <n>] [--cursor <cursor>] [--lifecycle active|archived]\n corvio docs get <id> [--output <path>]\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\nDirect document commands operate on canonical Page/Markdown. Use \`corvio ask --allow-actions\` for Spreadsheet, Presentation, Code, HTML Artifact, or semantic organization. Agent-authenticated document updates 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.`,
|
|
166
|
+
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.`,
|
|
160
167
|
files: `Usage:\n corvio files list [--limit <n>]\n corvio files get <id>\n corvio files download <id> [--output <path>]\n corvio files upload --file <path> [--mime-type <type>]\n corvio files organize <id> --instruction <text> --yes [--target-root-node-id <id>]\n corvio files open <id>`,
|
|
161
168
|
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>]`,
|
|
162
169
|
update: `Usage: corvio update check [--json]\n\nCompare this executable with the reviewed API policy and the npm beta dist-tag.`,
|
|
@@ -180,9 +187,10 @@ function normalizeInvocation(command, action) {
|
|
|
180
187
|
questions: "list",
|
|
181
188
|
conversations: "list",
|
|
182
189
|
docs: "list",
|
|
183
|
-
projects: "
|
|
190
|
+
projects: "list",
|
|
184
191
|
files: "list",
|
|
185
192
|
agents: "list",
|
|
193
|
+
collaboration: "status",
|
|
186
194
|
agent: "claim",
|
|
187
195
|
sync: "status",
|
|
188
196
|
update: "check",
|
|
@@ -211,6 +219,7 @@ function validateInvocation(options, positionals) {
|
|
|
211
219
|
"docs:update",
|
|
212
220
|
"docs:move",
|
|
213
221
|
"docs:archive",
|
|
222
|
+
"docs:restore",
|
|
214
223
|
"docs:share",
|
|
215
224
|
"docs:open",
|
|
216
225
|
"files:get",
|
|
@@ -647,6 +656,141 @@ async function capabilities(options) {
|
|
|
647
656
|
}, { json: options.json });
|
|
648
657
|
}
|
|
649
658
|
|
|
659
|
+
async function codexPluginGuidanceCandidates(codexHome) {
|
|
660
|
+
const cacheRoot = join(codexHome, "plugins", "cache");
|
|
661
|
+
const listDirectories = async (directory) => {
|
|
662
|
+
try {
|
|
663
|
+
return (await readdir(directory, { withFileTypes: true }))
|
|
664
|
+
.filter((entry) => entry.isDirectory())
|
|
665
|
+
.slice(0, 256)
|
|
666
|
+
.map((entry) => entry.name);
|
|
667
|
+
} catch (error) {
|
|
668
|
+
if (["ENOENT", "ENOTDIR", "EACCES"].includes(error?.code)) return [];
|
|
669
|
+
throw error;
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
const candidates = [];
|
|
673
|
+
for (const marketplace of await listDirectories(cacheRoot)) {
|
|
674
|
+
const marketplaceRoot = join(cacheRoot, marketplace);
|
|
675
|
+
for (const plugin of await listDirectories(marketplaceRoot)) {
|
|
676
|
+
const pluginRoot = join(marketplaceRoot, plugin);
|
|
677
|
+
for (const version of await listDirectories(pluginRoot)) {
|
|
678
|
+
candidates.push({
|
|
679
|
+
scope: "codex_plugin_cache",
|
|
680
|
+
path: join(pluginRoot, version, "skills", "corvio-operate-workspace", "SKILL.md"),
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return candidates;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
async function inspectCollaborationGuidance(provider, projectRoot, expectedContractVersion) {
|
|
689
|
+
const candidates = [
|
|
690
|
+
{ scope: "project_agents", path: join(projectRoot, ".agents", "skills", "corvio-operate-workspace", "SKILL.md") },
|
|
691
|
+
{ scope: "user_agents", path: join(homedir(), ".agents", "skills", "corvio-operate-workspace", "SKILL.md") },
|
|
692
|
+
];
|
|
693
|
+
if (provider === "codex") {
|
|
694
|
+
const codexHome = String(process.env.CODEX_HOME || join(homedir(), ".codex"));
|
|
695
|
+
candidates.push({
|
|
696
|
+
scope: "user_codex",
|
|
697
|
+
path: join(codexHome, "skills", "corvio-operate-workspace", "SKILL.md"),
|
|
698
|
+
});
|
|
699
|
+
candidates.push(...await codexPluginGuidanceCandidates(codexHome));
|
|
700
|
+
}
|
|
701
|
+
if (provider === "claude_code") {
|
|
702
|
+
candidates.push(
|
|
703
|
+
{ scope: "project_claude", path: join(projectRoot, ".claude", "skills", "corvio-operate-workspace", "SKILL.md") },
|
|
704
|
+
{
|
|
705
|
+
scope: "user_claude",
|
|
706
|
+
path: join(String(process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude")), "skills", "corvio-operate-workspace", "SKILL.md"),
|
|
707
|
+
},
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
const found = [];
|
|
711
|
+
for (const candidate of candidates) {
|
|
712
|
+
try {
|
|
713
|
+
const source = await readFile(candidate.path, "utf8");
|
|
714
|
+
if (!/^name:\s*corvio-operate-workspace\s*$/m.test(source)) continue;
|
|
715
|
+
found.push({
|
|
716
|
+
scope: candidate.scope,
|
|
717
|
+
current: Boolean(expectedContractVersion)
|
|
718
|
+
&& source.includes(`contract version \`${expectedContractVersion}\``),
|
|
719
|
+
});
|
|
720
|
+
} catch (error) {
|
|
721
|
+
if (error?.code !== "ENOENT") throw error;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
const current = found.filter((item) => item.current);
|
|
725
|
+
const status = !expectedContractVersion
|
|
726
|
+
? "contract_unavailable"
|
|
727
|
+
: current.length
|
|
728
|
+
? "ready"
|
|
729
|
+
: found.length
|
|
730
|
+
? "stale"
|
|
731
|
+
: "missing";
|
|
732
|
+
return {
|
|
733
|
+
status,
|
|
734
|
+
expected_contract_version: expectedContractVersion || null,
|
|
735
|
+
discovered_scopes: found.map((item) => item.scope),
|
|
736
|
+
current_scopes: current.map((item) => item.scope),
|
|
737
|
+
install_command: status === "missing" || status === "stale" ? CORVIO_SKILL_INSTALL_COMMAND : null,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async function collaborationStatus(options) {
|
|
742
|
+
const projectRoot = await findProjectRoot(options.project);
|
|
743
|
+
const credential = await readAgentCredential(process.env, {
|
|
744
|
+
required: false,
|
|
745
|
+
agentId: String(options.agent || "").trim() || null,
|
|
746
|
+
projectRoot,
|
|
747
|
+
fallbackToActive: false,
|
|
748
|
+
});
|
|
749
|
+
const provider = String(options.provider || credential?.metadata?.provider || "codex");
|
|
750
|
+
const api = new ApiClient({ baseUrl: baseUrl(options), userAgent: USER_AGENT });
|
|
751
|
+
const index = await api.request("", { auth: false });
|
|
752
|
+
const liveContract = dictLike(index?.coding_agent_collaboration);
|
|
753
|
+
const serverVersion = String(liveContract.version || "").trim() || null;
|
|
754
|
+
const guidance = await inspectCollaborationGuidance(provider, projectRoot, serverVersion);
|
|
755
|
+
const bindingId = String(credential?.metadata?.binding_id || "").trim();
|
|
756
|
+
const runnerState = await readAgentRunnerState();
|
|
757
|
+
const listener = bindingId ? dictLike(runnerState?.listeners?.[bindingId]) : {};
|
|
758
|
+
const listenerStatus = processIsRunning(Number(listener.pid)) ? "listening" : "offline";
|
|
759
|
+
let status = "ready";
|
|
760
|
+
if (!credential) status = "needs_connection";
|
|
761
|
+
else if (!serverVersion) status = "contract_unavailable";
|
|
762
|
+
else if (guidance.status === "missing") status = "needs_skill";
|
|
763
|
+
else if (guidance.status === "stale") status = "stale_skill";
|
|
764
|
+
const nextActions = [];
|
|
765
|
+
if (!credential) nextActions.push(`corvio agents connect --provider ${provider} --project .`);
|
|
766
|
+
if (guidance.install_command) nextActions.push(guidance.install_command);
|
|
767
|
+
if (!serverVersion) nextActions.push("corvio update check --json");
|
|
768
|
+
writeResult({
|
|
769
|
+
status,
|
|
770
|
+
foreground_collaboration_ready: status === "ready",
|
|
771
|
+
contract_version: serverVersion,
|
|
772
|
+
server_contract_version: serverVersion,
|
|
773
|
+
project_agent: credential
|
|
774
|
+
? {
|
|
775
|
+
connected: true,
|
|
776
|
+
id: credential.metadata?.agent_id || null,
|
|
777
|
+
provider,
|
|
778
|
+
binding_id: bindingId || null,
|
|
779
|
+
}
|
|
780
|
+
: { connected: false, id: null, provider, binding_id: null },
|
|
781
|
+
host_guidance: guidance,
|
|
782
|
+
listener_status: listenerStatus,
|
|
783
|
+
listener_note: "The listener receives inbound @Agent work; it does not activate foreground Coding-Agent collaboration.",
|
|
784
|
+
synchronization: {
|
|
785
|
+
mode: "host_guided_foreground",
|
|
786
|
+
automatic_transcript_capture: false,
|
|
787
|
+
reason: "The host selects decision-relevant context and valuable results; Corvio never records arbitrary chats or local files in the background.",
|
|
788
|
+
work_loop: liveContract.work_loop || ["ground", "work", "checkpoint", "materialize", "verify", "return"],
|
|
789
|
+
},
|
|
790
|
+
next_actions: nextActions,
|
|
791
|
+
}, { json: options.json });
|
|
792
|
+
}
|
|
793
|
+
|
|
650
794
|
async function workspaces(options, action, id) {
|
|
651
795
|
const credential = await readWorkspaceCredential();
|
|
652
796
|
if (!credential) {
|
|
@@ -1221,6 +1365,22 @@ async function agents(options, action, id) {
|
|
|
1221
1365
|
const listener = options["no-listen"]
|
|
1222
1366
|
? { status: "offline", pid: null, reused: false, log: null }
|
|
1223
1367
|
: await ensureAgentListener({ options, provider, agentId: agent.id, binding });
|
|
1368
|
+
let collaborationVersion = null;
|
|
1369
|
+
try {
|
|
1370
|
+
const collaborationIndex = await api.request("", { auth: false });
|
|
1371
|
+
collaborationVersion = String(
|
|
1372
|
+
dictLike(collaborationIndex?.coding_agent_collaboration).version || "",
|
|
1373
|
+
).trim() || null;
|
|
1374
|
+
} catch {
|
|
1375
|
+
// The Agent binding and provider readiness are already durable. Keep this
|
|
1376
|
+
// later collaboration check explicit instead of turning a settled connect
|
|
1377
|
+
// into an ambiguous failure.
|
|
1378
|
+
}
|
|
1379
|
+
const hostGuidance = await inspectCollaborationGuidance(
|
|
1380
|
+
provider,
|
|
1381
|
+
binding.project_root,
|
|
1382
|
+
collaborationVersion,
|
|
1383
|
+
);
|
|
1224
1384
|
result = {
|
|
1225
1385
|
connected: true,
|
|
1226
1386
|
idempotent: Boolean(result.idempotent),
|
|
@@ -1255,8 +1415,20 @@ async function agents(options, action, id) {
|
|
|
1255
1415
|
reused: listener.reused,
|
|
1256
1416
|
log: listener.log,
|
|
1257
1417
|
},
|
|
1418
|
+
foreground_collaboration: {
|
|
1419
|
+
status: hostGuidance.status === "ready"
|
|
1420
|
+
? "ready"
|
|
1421
|
+
: hostGuidance.status === "contract_unavailable"
|
|
1422
|
+
? "contract_unavailable"
|
|
1423
|
+
: "needs_skill",
|
|
1424
|
+
ready: hostGuidance.status === "ready",
|
|
1425
|
+
host_guidance: hostGuidance,
|
|
1426
|
+
note: "The inbound listener and foreground collaboration are separate readiness facts.",
|
|
1427
|
+
},
|
|
1258
1428
|
settings_url: result.settings_url,
|
|
1259
|
-
next_command:
|
|
1429
|
+
next_command: hostGuidance.status === "ready"
|
|
1430
|
+
? "corvio collaboration status --json --no-input"
|
|
1431
|
+
: hostGuidance.install_command || "corvio collaboration status --json --no-input",
|
|
1260
1432
|
};
|
|
1261
1433
|
} else if (action === "create") {
|
|
1262
1434
|
result = await api.request("/agents", {
|
|
@@ -2193,6 +2365,97 @@ async function agentCommand(options, action, id) {
|
|
|
2193
2365
|
method: "POST",
|
|
2194
2366
|
body: { status: option(options, "status", { required: true }), entry_point: "external_agent_cli_status" },
|
|
2195
2367
|
});
|
|
2368
|
+
} else if (action === "closeout") {
|
|
2369
|
+
const documentId = option(options, "document-id", { required: true });
|
|
2370
|
+
const threadId = String(options["thread-id"] || "").trim() || null;
|
|
2371
|
+
const bodyText = option(options, "body", { required: true });
|
|
2372
|
+
const status = String(options.status || "open").trim().toLowerCase();
|
|
2373
|
+
if (!["open", "resolved"].includes(status)) {
|
|
2374
|
+
throw new CliError("--status must be open or resolved.", { code: "comment_status_invalid" });
|
|
2375
|
+
}
|
|
2376
|
+
const before = await api.request(`/documents/${resourcePath(documentId)}/comments`);
|
|
2377
|
+
if (threadId && !Array.isArray(before?.threads)) {
|
|
2378
|
+
throw new CliError("The current comment readback is invalid.", { code: "comment_readback_invalid" });
|
|
2379
|
+
}
|
|
2380
|
+
if (threadId && !before.threads.some((thread) => String(thread.id) === threadId)) {
|
|
2381
|
+
throw new CliError("The originating comment thread was not found on this document.", {
|
|
2382
|
+
code: "comment_thread_not_found",
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
const stableKey = idempotencyKey(options);
|
|
2386
|
+
const mentionTargets = await resolveAgentMentionTargets(api, documentId, bodyText);
|
|
2387
|
+
const comment = threadId
|
|
2388
|
+
? await api.request(`/documents/${resourcePath(documentId)}/comments/${resourcePath(threadId)}/replies`, {
|
|
2389
|
+
method: "POST",
|
|
2390
|
+
headers: { "idempotency-key": stableKey },
|
|
2391
|
+
body: {
|
|
2392
|
+
body: bodyText,
|
|
2393
|
+
mention_targets: mentionTargets,
|
|
2394
|
+
entry_point: "external_agent_cli_closeout",
|
|
2395
|
+
start_new_chain: false,
|
|
2396
|
+
},
|
|
2397
|
+
})
|
|
2398
|
+
: await api.request(`/documents/${resourcePath(documentId)}/comments`, {
|
|
2399
|
+
method: "POST",
|
|
2400
|
+
headers: { "idempotency-key": stableKey },
|
|
2401
|
+
body: {
|
|
2402
|
+
body: bodyText,
|
|
2403
|
+
mention_targets: mentionTargets,
|
|
2404
|
+
selection: { target_scope: "document" },
|
|
2405
|
+
entry_point: "external_agent_cli_closeout",
|
|
2406
|
+
start_new_chain: false,
|
|
2407
|
+
},
|
|
2408
|
+
});
|
|
2409
|
+
const closeoutThreadId = String(comment?.id || threadId || "");
|
|
2410
|
+
try {
|
|
2411
|
+
if (status === "resolved" && String(comment?.status || "") !== "resolved") {
|
|
2412
|
+
await api.request(`/documents/${resourcePath(documentId)}/comments/${resourcePath(closeoutThreadId)}/status`, {
|
|
2413
|
+
method: "POST",
|
|
2414
|
+
body: { status: "resolved", entry_point: "external_agent_cli_closeout" },
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
} catch {
|
|
2418
|
+
throw new CliError(
|
|
2419
|
+
"The result comment was committed, but the requested closeout status failed. Keep the thread open and retry the status update after readback.",
|
|
2420
|
+
{
|
|
2421
|
+
code: "comment_closeout_partial",
|
|
2422
|
+
exitCode: EXIT.REMOTE,
|
|
2423
|
+
details: { document_id: documentId, thread_id: closeoutThreadId, comment },
|
|
2424
|
+
},
|
|
2425
|
+
);
|
|
2426
|
+
}
|
|
2427
|
+
const [document, comments] = await Promise.all([
|
|
2428
|
+
api.request(`/documents/${resourcePath(documentId)}`),
|
|
2429
|
+
api.request(`/documents/${resourcePath(documentId)}/comments`),
|
|
2430
|
+
]);
|
|
2431
|
+
const thread = Array.isArray(comments?.threads)
|
|
2432
|
+
? comments.threads.find((item) => String(item.id) === closeoutThreadId) || null
|
|
2433
|
+
: null;
|
|
2434
|
+
if (!thread) {
|
|
2435
|
+
throw new CliError(
|
|
2436
|
+
"The result comment was committed, but its current thread could not be read back.",
|
|
2437
|
+
{
|
|
2438
|
+
code: "comment_closeout_readback_failed",
|
|
2439
|
+
exitCode: EXIT.REMOTE,
|
|
2440
|
+
details: { document_id: documentId, thread_id: closeoutThreadId, comment },
|
|
2441
|
+
},
|
|
2442
|
+
);
|
|
2443
|
+
}
|
|
2444
|
+
result = {
|
|
2445
|
+
document: {
|
|
2446
|
+
id: document?.id || documentId,
|
|
2447
|
+
node_id: document?.node_id || null,
|
|
2448
|
+
title: document?.title || null,
|
|
2449
|
+
doc_type: document?.doc_type || null,
|
|
2450
|
+
content_revision: document?.content_revision ?? null,
|
|
2451
|
+
url: document?.url || null,
|
|
2452
|
+
},
|
|
2453
|
+
thread,
|
|
2454
|
+
completed: status === "resolved" && thread?.status === "resolved",
|
|
2455
|
+
remaining_boundary: status === "resolved" && thread?.status === "resolved"
|
|
2456
|
+
? null
|
|
2457
|
+
: "The originating thread remains open for incomplete or unverified work.",
|
|
2458
|
+
};
|
|
2196
2459
|
} else {
|
|
2197
2460
|
throw new CliError("Unknown agent runtime action.");
|
|
2198
2461
|
}
|
|
@@ -2246,9 +2509,19 @@ async function docs(options, action, id) {
|
|
|
2246
2509
|
const documentId = id || options.id;
|
|
2247
2510
|
let result;
|
|
2248
2511
|
if (action === "list") {
|
|
2249
|
-
|
|
2512
|
+
const lifecycle = String(options.lifecycle || "active").trim().toLowerCase();
|
|
2513
|
+
if (!new Set(["active", "archived"]).has(lifecycle)) {
|
|
2514
|
+
throw new CliError("--lifecycle must be active or archived.", { code: "document_lifecycle_invalid" });
|
|
2515
|
+
}
|
|
2516
|
+
result = await api.request("/documents", { query: { ...pageQuery(options), lifecycle } });
|
|
2250
2517
|
} else if (action === "get") {
|
|
2251
2518
|
result = await api.request(`/documents/${resourcePath(documentId || option(options, "id", { required: true }))}`);
|
|
2519
|
+
if (executionIdentity === "agent") {
|
|
2520
|
+
result = {
|
|
2521
|
+
...result,
|
|
2522
|
+
collaboration_follow_up: "If this document or one of its comments is the task/feedback owner and materially drives your work, reread current state after the work, update an explicit status only when unambiguous, reply on the originating thread with verified evidence, and resolve only after completion. A read-only citation needs no ceremonial comment.",
|
|
2523
|
+
};
|
|
2524
|
+
}
|
|
2252
2525
|
if (options.output) {
|
|
2253
2526
|
await writeTextFile(options.output, String(result.markdown || ""));
|
|
2254
2527
|
result = { ...result, output: options.output };
|
|
@@ -2361,6 +2634,9 @@ async function docs(options, action, id) {
|
|
|
2361
2634
|
} else if (action === "archive") {
|
|
2362
2635
|
requireConfirmation(options, "Archiving a document");
|
|
2363
2636
|
result = await api.request(`/documents/${resourcePath(documentId || option(options, "id", { required: true }))}`, { method: "DELETE" });
|
|
2637
|
+
} else if (action === "restore") {
|
|
2638
|
+
requireConfirmation(options, "Restoring an archived document");
|
|
2639
|
+
result = await api.request(`/documents/${resourcePath(documentId || option(options, "id", { required: true }))}/restore`, { method: "POST", retrySafe: true });
|
|
2364
2640
|
} else if (action === "share") {
|
|
2365
2641
|
requireConfirmation(options, "Creating a document share link");
|
|
2366
2642
|
result = await api.request(`/documents/${resourcePath(documentId || option(options, "id", { required: true }))}/shares`, {
|
|
@@ -2374,16 +2650,20 @@ async function docs(options, action, id) {
|
|
|
2374
2650
|
const opened = options["no-input"] ? false : await openBrowser(target);
|
|
2375
2651
|
result = { document_id: targetId, url: target, opened };
|
|
2376
2652
|
} else {
|
|
2377
|
-
throw new CliError("docs action must be list, get, create, update, move, archive, share, or open.");
|
|
2653
|
+
throw new CliError("docs action must be list, get, create, update, move, archive, restore, share, or open.");
|
|
2378
2654
|
}
|
|
2379
2655
|
writeResult(result, { json: options.json });
|
|
2380
2656
|
}
|
|
2381
2657
|
|
|
2382
2658
|
async function projects(options, action) {
|
|
2659
|
+
const { api } = await client(options);
|
|
2660
|
+
if (action === "list") {
|
|
2661
|
+
writeResult(await api.request("/projects", { query: { limit: numberOption(options, "limit", 100, { maximum: 100 }) } }), { json: options.json });
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2383
2664
|
if (action !== "create") {
|
|
2384
|
-
throw new CliError("projects action must be create.");
|
|
2665
|
+
throw new CliError("projects action must be list or create.");
|
|
2385
2666
|
}
|
|
2386
|
-
const { api } = await client(options);
|
|
2387
2667
|
const result = await api.request("/projects", {
|
|
2388
2668
|
method: "POST",
|
|
2389
2669
|
headers: { "idempotency-key": idempotencyKey(options) },
|
|
@@ -3017,6 +3297,7 @@ async function main({ options, positionals }) {
|
|
|
3017
3297
|
return workspaces(options, action || "list", id);
|
|
3018
3298
|
}
|
|
3019
3299
|
if (command === "capabilities") return capabilities(options);
|
|
3300
|
+
if (command === "collaboration") return collaborationStatus(options);
|
|
3020
3301
|
if (command === "ask") return ask(options, [action, id, ...rest].filter(Boolean));
|
|
3021
3302
|
if (command === "agents") return agents(options, action || "list", id);
|
|
3022
3303
|
if (command === "agent") return agentCommand(options, action || "claim", id);
|