@caveman-ai/cloud 0.1.0-rc.32 → 0.1.0-rc.34
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 +28 -3
- package/bin/ci.mjs +2 -2
- package/bin/cvm.mjs +60 -1
- package/dist/cloud-client.mjs +59 -36
- package/dist/cloud-mcp.mjs +32 -32
- package/dist/criterion-registry.json +80 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,12 +6,14 @@ Every Caveman Cloud operation from the terminal, plus the Cloud MCP server.
|
|
|
6
6
|
npx -y @caveman-ai/cloud@rc setup --project PROJECT_ID # installs cvm, signs in, connects your coding agents
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
`@rc` is the newest release; `latest`
|
|
9
|
+
`@rc` is the newest release; npm's `latest` tag lags behind it, so install `@rc`. `npm i -g @caveman-ai/cloud@rc` installs cvm by itself. When cvm and the server speak different API versions, the error (and `cvm doctor`) prints the exact install command: the server's release when it reports one, else `@rc`.
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
12
|
cvm login # browser sign-in; one sign-in for cvm and caveman
|
|
13
13
|
cvm projects list
|
|
14
|
-
cvm traces
|
|
14
|
+
cvm traces # the verbs in one family
|
|
15
|
+
cvm traces search --help # the flags one verb takes
|
|
16
|
+
cvm traces tail # new traces as they arrive (Ctrl-C stops)
|
|
15
17
|
cvm sql "SELECT model, count() FROM requests GROUP BY model"
|
|
16
18
|
cvm tools list # every operation this Cloud offers you
|
|
17
19
|
cvm mcp # Cloud MCP server over stdio
|
|
@@ -20,9 +22,32 @@ cvm mcp # Cloud MCP server over stdio
|
|
|
20
22
|
- **Sign-in.** `cvm login` signs in with the browser device flow: it prints a code and a link, opens the browser (`--no-browser` or `CAVE_NO_BROWSER=1` to skip) and waits for you to confirm. It stores the credential where `caveman login` does (the macOS Keychain, else `~/.caveman/credentials`), so either CLI's sign-in works for both; `cvm logout` signs both out. `--instance URL` (or `CAVE_API_URL`) signs in to a private install. In CI, set `CAVE_TOKEN` (and `CAVE_API_URL` for a private install).
|
|
21
23
|
- **Environment.** A flag wins over the environment, and the environment over the saved login: `CAVE_API_URL`, `CAVE_PROJECT_ID`, `CAVE_TOKEN`. The saved credential is only ever sent to the address it was saved with; another `CAVE_API_URL` needs its own `CAVE_TOKEN`.
|
|
22
24
|
- **Projects.** Use `--project UUID`, `CAVE_PROJECT_ID`, `cvm context bind` (writes `.caveman-cloud.json` in the repository) or the saved default.
|
|
23
|
-
- **Output.** JSON when piped, readable when on a terminal; `--format json|jsonl|table`.
|
|
25
|
+
- **Output.** JSON when piped, readable when on a terminal; `--format json|jsonl|table`. Help follows the same rule: `cvm`, `cvm <family>` and `cvm <family> <verb> --help` are screens on a terminal and JSON when piped, and a mistyped command names the nearest one.
|
|
24
26
|
- **Exit codes.** 0 ok · 1 request failed · 2 usage or invalid input (HTTP 400/422) · 3 signed out or forbidden (401/403) · 4 conflict (409) · 5 a comparison or gate failed · 6 timeout, still running or not enough data · 7 rate limited (429).
|
|
25
27
|
|
|
28
|
+
## Human Code lifecycle
|
|
29
|
+
|
|
30
|
+
These fixed commands use the same saved human sign-in as `cvm login`. They do
|
|
31
|
+
not expose credentials, allocate computers, or grant Code access to agent
|
|
32
|
+
connections. Server authorization remains authoritative; an agent connection is
|
|
33
|
+
still denied. No arbitrary HTTP paths, request bodies, or headers are accepted.
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
cvm human code computers
|
|
37
|
+
cvm human code runtimes
|
|
38
|
+
cvm human code workspace WORKSPACE_UUID
|
|
39
|
+
cvm human code archive WORKSPACE_UUID --yes
|
|
40
|
+
cvm human code --help
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
IDs must be canonical hyphenated UUIDs. Archive requires `--yes` (`-y`) even on
|
|
44
|
+
a terminal: it terminates sessions and may delete the workspace's computer when
|
|
45
|
+
no active workspace uses it. Without confirmation, no request (including login
|
|
46
|
+
refresh) is made. These commands accept `--format json|jsonl|table`; `table`
|
|
47
|
+
pretty-prints the lifecycle JSON. They use the built-in saved context and its
|
|
48
|
+
normal environment overrides, not named operation profiles. Errors omit
|
|
49
|
+
upstream response details to avoid disclosing credentials.
|
|
50
|
+
|
|
26
51
|
## Setup
|
|
27
52
|
|
|
28
53
|
`cvm setup` connects your coding agents to Caveman Cloud. Each step is safe to run again, and running it again is how you repair a setup.
|
package/bin/ci.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import {randomUUID} from 'node:crypto';
|
|
|
9
9
|
import {join} from 'node:path';
|
|
10
10
|
import {parseArgs} from 'node:util';
|
|
11
11
|
import {setTimeout as delay} from 'node:timers/promises';
|
|
12
|
-
import {CloudClient,CloudError,inertJSON,runCloudCLI} from '../dist/cloud-client.mjs';
|
|
12
|
+
import {CloudClient,CloudError,inertJSON,runCloudCLI,versionConflictMessage} from '../dist/cloud-client.mjs';
|
|
13
13
|
|
|
14
14
|
const MAX_REPORTS=20,MAX_REPORT_BYTES=10*1024*1024; // the finish route's own limits
|
|
15
15
|
const say=line=>process.stderr.write(line+'\n');
|
|
@@ -29,7 +29,7 @@ export async function connect(host,project){
|
|
|
29
29
|
client.request=async(...args)=>{try{return await request(...args);}catch(error){if(error?.code==='cave_operation_version_conflict')error.message=VERSION_CONFLICT;throw error;}};
|
|
30
30
|
return client;
|
|
31
31
|
}
|
|
32
|
-
export const VERSION_CONFLICT=
|
|
32
|
+
export const VERSION_CONFLICT=versionConflictMessage();
|
|
33
33
|
function projectPath(client){
|
|
34
34
|
if(!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(client.projectId??''))throw new CloudError('cave_project_required','Select a project with --project, CAVE_PROJECT_ID or cvm context bind.',2);
|
|
35
35
|
return `/api/v1/projects/${client.projectId}`;
|
package/bin/cvm.mjs
CHANGED
|
@@ -14,6 +14,9 @@ import {parseArgs} from 'node:util';
|
|
|
14
14
|
import {setTimeout as pause} from 'node:timers/promises';
|
|
15
15
|
import {version,skillDirs,marker,installSkill} from './agents.mjs';
|
|
16
16
|
|
|
17
|
+
// A reader that stops early (cvm tools list | head) closes the pipe: end
|
|
18
|
+
// quietly, as Unix tools do, instead of an unhandled EPIPE stack trace.
|
|
19
|
+
for(const stream of [process.stdout,process.stderr])stream.on('error',error=>{if(error.code!=='EPIPE')throw error;process.exit(process.exitCode??0);});
|
|
17
20
|
const client=()=>import('../dist/cloud-client.mjs');
|
|
18
21
|
const cavemanHome=process.env.CAVEMAN_HOME??join(homedir(),'.caveman');
|
|
19
22
|
const credentialsPath=join(cavemanHome,'credentials');
|
|
@@ -192,6 +195,60 @@ async function setup(args){
|
|
|
192
195
|
return run(args,{client,context,login,call,cvm:realpathSync(fileURLToPath(import.meta.url))});
|
|
193
196
|
}
|
|
194
197
|
|
|
198
|
+
// Fixed human lifecycle routes, deliberately outside the operation/MCP catalog.
|
|
199
|
+
// The normal built-in context owns credentials; this command never exports them.
|
|
200
|
+
async function humanCode(args){
|
|
201
|
+
const {CloudClient,CloudError,inertJSON}=await client();
|
|
202
|
+
const usage='cvm human code computers|runtimes|workspace ID|archive ID --yes [--format json|jsonl|table]';
|
|
203
|
+
const fail=(code,message,exit)=>new CloudError(code,message,exit);
|
|
204
|
+
let format=process.stdout.isTTY?'table':'json';
|
|
205
|
+
try{
|
|
206
|
+
let values,positionals;
|
|
207
|
+
try{({values,positionals}=parseArgs({args,strict:true,allowPositionals:true,options:{help:{type:'boolean',short:'h'},format:{type:'string'},yes:{type:'boolean',short:'y'}}}));}
|
|
208
|
+
catch{throw fail('cave_usage',`Use ${usage}.`,2);}
|
|
209
|
+
format=values.format??format;
|
|
210
|
+
if(!['json','jsonl','table'].includes(format))throw fail('cave_usage','Format must be json, jsonl, or table.',2);
|
|
211
|
+
const [family,verb,id]=positionals;
|
|
212
|
+
const write=value=>process.stdout.write(inertJSON(value,format==='table'?2:undefined)+'\n');
|
|
213
|
+
if(values.help||!positionals.length||positionals.length===1&&family==='code'){
|
|
214
|
+
write({usage,summary:'Human Code lifecycle. Uses your normal saved sign-in; agent connections remain forbidden. Archive terminates sessions and may delete an unused workspace computer. No computer allocation.'});return 0;
|
|
215
|
+
}
|
|
216
|
+
if(family!=='code'||!['computers','runtimes','workspace','archive'].includes(verb))throw fail('cave_usage',`Use ${usage}.`,2);
|
|
217
|
+
const needsID=verb==='workspace'||verb==='archive';
|
|
218
|
+
if(positionals.length!==(needsID?3:2)||needsID&&(id.length!==36||!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(id)))throw fail('cave_usage','Supply exactly one workspace UUID for workspace or archive; no ID for computers or runtimes.',2);
|
|
219
|
+
if(values.yes&&verb!=='archive')throw fail('cave_usage','--yes is only for archive.',2);
|
|
220
|
+
// Validate and confirm before even resolving context (which may refresh login).
|
|
221
|
+
if(verb==='archive'&&!values.yes)throw fail('cave_confirmation_required','Archiving terminates sessions and may delete the unused workspace computer. Repeat with --yes to confirm.',2);
|
|
222
|
+
const path=needsID?`/api/v1/code/workspaces/${encodeURIComponent(id)}${verb==='archive'?'/archive':''}`:`/api/v1/code/${verb}`;
|
|
223
|
+
try{
|
|
224
|
+
const api=new CloudClient(await context());
|
|
225
|
+
write(await api.request(path,verb==='archive'?{body:{}}:{}));return 0;
|
|
226
|
+
}catch(error){
|
|
227
|
+
// Do not print upstream bodies, messages, details, URLs or unknown errors:
|
|
228
|
+
// they can contain credentials or terminal controls. Preserve denial codes.
|
|
229
|
+
if(error.httpStatus){
|
|
230
|
+
const code=error.httpStatus===403&&error.code==='cave_agent_operation_forbidden'?'cave_agent_operation_forbidden':'cave_request_failed';
|
|
231
|
+
throw fail(code,`Code lifecycle request failed (HTTP ${error.httpStatus}).`,error.exitCode);
|
|
232
|
+
}
|
|
233
|
+
const safe={cave_auth_required:'Run cvm login.',cave_endpoint_invalid:'Cloud endpoint must be HTTPS or loopback HTTP without credentials, query or fragment.',cave_network_error:'Cloud request failed. Inspect workspace state before retrying archive.',cave_timeout:'Cloud request timed out. Inspect workspace state before retrying archive.',cave_invalid_response:'Cloud returned an invalid JSON response.',cave_result_too_large:'Cloud response exceeded the size limit.'};
|
|
234
|
+
throw fail(Object.hasOwn(safe,error.code)?error.code:'cave_failed',safe[error.code]??'Code lifecycle command failed.',error instanceof CloudError?error.exitCode:1);
|
|
235
|
+
}
|
|
236
|
+
}catch(error){
|
|
237
|
+
process.stdout.write(inertJSON({schema_version:1,ok:false,status:'failed',error:{code:error.code,message:error.message}},format==='table'?2:undefined)+'\n');return error.exitCode;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// What cvm answers before the client, for `cvm --help` and `cvm <family>`.
|
|
242
|
+
const commands=[
|
|
243
|
+
{id:'setup',usage:'cvm setup [--project UUID] [--agent claude,codex,...] [--yes] [--no-launch] [--remove]',summary:'Connect your coding agents: sign-in, project, skill and MCP server.'},
|
|
244
|
+
{id:'login',usage:'cvm login [--instance URL] [--no-browser]',summary:'Sign in with the browser.'},
|
|
245
|
+
{id:'logout',usage:'cvm logout',summary:'Sign out and revoke this device.'},
|
|
246
|
+
{id:'mcp',usage:'cvm mcp',summary:'Serve the Cloud MCP server over stdio.'},
|
|
247
|
+
{id:'human.code',usage:'cvm human code computers|runtimes|workspace ID|archive ID --yes',summary:'Human Code lifecycle using your normal saved sign-in; no agent access.'},
|
|
248
|
+
{id:'evals.init',usage:'cvm evals init [PATH]',summary:'Write a starter eval file (example.eval.json).'},
|
|
249
|
+
{id:'evals.ci',usage:'cvm evals ci FILE... | cvm evals ci [--suite NAME] -- TEST_COMMAND',summary:'Gate a pull request on eval files or a CI test run.'},
|
|
250
|
+
{id:'evals.watch',usage:'cvm evals watch RUN_ID',summary:'Follow one run until it settles.'},
|
|
251
|
+
];
|
|
195
252
|
const argv=process.argv.slice(2);
|
|
196
253
|
// What only the operation form of `evals validate` and `evals run` takes.
|
|
197
254
|
// A bare id is the experiment to run, unless it is the value of --project.
|
|
@@ -214,6 +271,8 @@ if(argv[0]==='--version'||argv[0]==='-v'){
|
|
|
214
271
|
process.stdout.write(JSON.stringify({schema_version:1,ok:false,status:'failed',error:{code:error.exitCode?error.code:'cave_failed',message:error.message}})+'\n');
|
|
215
272
|
process.exitCode=exit;
|
|
216
273
|
}
|
|
274
|
+
}else if(argv[0]==='human'&&argv[1]==='code'){
|
|
275
|
+
process.exitCode=await humanCode(argv.slice(1));
|
|
217
276
|
}else if(argv[0]==='mcp'&&argv.length===1){
|
|
218
277
|
const {serveCloudMCP}=await import('../dist/cloud-mcp.mjs');
|
|
219
278
|
// A launcher may narrow the server to the operations one task needs.
|
|
@@ -229,5 +288,5 @@ if(argv[0]==='--version'||argv[0]==='-v'){
|
|
|
229
288
|
process.exitCode=await (argv[1]==='ci'?evalsCI:evalsWatch)(argv.slice(2),{context,login:loginCommand});
|
|
230
289
|
}else{
|
|
231
290
|
const {runCloudCLI}=await client();
|
|
232
|
-
process.exitCode=await runCloudCLI(argv,{context,login:loginCommand});
|
|
291
|
+
process.exitCode=await runCloudCLI(argv,{context,login:loginCommand,commands});
|
|
233
292
|
}
|