@caveman-ai/cloud 0.1.0-rc.25 → 0.1.0-rc.26
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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Every Caveman Cloud operation from the terminal, plus the Cloud MCP server.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npm i -g
|
|
6
|
+
npm i -g @caveman-ai/cloud@rc # the newest release; `latest` is still the 0.0.1 bootstrap
|
|
7
7
|
cvm setup --project PROJECT_ID # signs in, picks the project, installs the Caveman Cloud skill
|
|
8
8
|
```
|
|
9
9
|
|
package/bin/cvm.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// cvm, the Caveman Cloud CLI. It signs in with the browser device flow and
|
|
3
|
-
// stores the credential exactly where `caveman login` does
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// stores the credential exactly where `caveman login` does (the Cloud config
|
|
4
|
+
// points at the macOS Keychain item or the 0600 credentials file), so either
|
|
5
|
+
// CLI's sign-in serves both, and a refreshed pair goes back to the same place,
|
|
6
|
+
// so neither CLI ever signs the other out.
|
|
7
7
|
import {readFileSync,writeFileSync,mkdirSync,chmodSync,renameSync,existsSync,realpathSync,cpSync,rmSync,readdirSync,statSync} from 'node:fs';
|
|
8
8
|
import {execFileSync,spawn} from 'node:child_process';
|
|
9
9
|
import {randomUUID} from 'node:crypto';
|
|
@@ -15,12 +15,17 @@ import {setTimeout as pause} from 'node:timers/promises';
|
|
|
15
15
|
|
|
16
16
|
const version=JSON.parse(readFileSync(new URL('../package.json',import.meta.url),'utf8')).version;
|
|
17
17
|
const client=()=>import('../dist/cloud-client.mjs');
|
|
18
|
-
const
|
|
19
|
-
const credentialsPath=join(
|
|
18
|
+
const cavemanHome=process.env.CAVEMAN_HOME??join(homedir(),'.caveman');
|
|
19
|
+
const credentialsPath=join(cavemanHome,'credentials');
|
|
20
|
+
// The Cloud config is $CAVEMAN_HOME/cloud.json, as `caveman` (v4) keeps it; that
|
|
21
|
+
// CLI copies the old ~/.caveman-cloud/config.json there once, and until then the
|
|
22
|
+
// old file is the one both read and write.
|
|
23
|
+
const legacyConfigPath=join(homedir(),'.caveman-cloud','config.json');
|
|
24
|
+
function configPath(){const path=join(cavemanHome,'cloud.json');return existsSync(path)||!existsSync(legacyConfigPath)?path:legacyConfigPath;}
|
|
20
25
|
const keychain=['-s','caveman','-a','token'];
|
|
21
26
|
const origin=url=>(url??'').replace(/\/+$/,'');
|
|
22
27
|
|
|
23
|
-
function readConfig(){try{return JSON.parse(readFileSync(configPath,'utf8'));}catch{return {};}}
|
|
28
|
+
function readConfig(){try{return JSON.parse(readFileSync(configPath(),'utf8'));}catch{return {};}}
|
|
24
29
|
function writePrivate(path,text){
|
|
25
30
|
if(existsSync(path))path=realpathSync(path); // a symlinked config stays a symlink
|
|
26
31
|
mkdirSync(dirname(path),{recursive:true,mode:0o700});
|
|
@@ -37,9 +42,15 @@ function storedSecret(meta){
|
|
|
37
42
|
function decode(raw){try{const parsed=JSON.parse(raw);if(typeof parsed.access_token==='string')return parsed;}catch{}return {access_token:raw};}
|
|
38
43
|
const claims=token=>{try{return JSON.parse(Buffer.from(token.split('.')[0],'base64url').toString('utf8'))??{};}catch{return {};}};
|
|
39
44
|
const expiresSoon=token=>claims(token).exp<=Date.now()/1000+60;
|
|
45
|
+
// Without a default keychain (a sudo or remote shell, a fresh account) the
|
|
46
|
+
// keychain write opens a macOS "A keychain cannot be found" dialog instead of
|
|
47
|
+
// failing, so the file is used without asking.
|
|
48
|
+
function defaultKeychain(){
|
|
49
|
+
try{const path=execFileSync('security',['default-keychain'],{encoding:'utf8',stdio:['ignore','pipe','ignore']}).trim().replace(/^"|"$/g,'');return path!==''&&existsSync(path);}catch{return false;}
|
|
50
|
+
}
|
|
40
51
|
function store(credentials){
|
|
41
52
|
const secret=JSON.stringify(credentials);
|
|
42
|
-
if(process.platform==='darwin'&&!process.env.CAVE_NO_KEYCHAIN){try{execFileSync('security',['add-generic-password','-U',...keychain,'-w',secret],{stdio:'ignore'});return 'keychain';}catch{}}
|
|
53
|
+
if(process.platform==='darwin'&&!process.env.CAVE_NO_KEYCHAIN&&defaultKeychain()){try{execFileSync('security',['add-generic-password','-U',...keychain,'-w',secret],{stdio:'ignore'});return 'keychain';}catch{}}
|
|
43
54
|
writePrivate(credentialsPath,secret);return 'file';
|
|
44
55
|
}
|
|
45
56
|
// Refresh tokens rotate: the new pair goes back where caveman reads it, or the
|
|
@@ -58,7 +69,7 @@ async function refresh(meta,credentials,baseURL){
|
|
|
58
69
|
const now=readConfig();
|
|
59
70
|
if(now.tokenStore===meta.tokenStore&&!now.logoutPendingLocalCleanup&&decode(storedSecret(now)).refresh_token===credentials.refresh_token){
|
|
60
71
|
const {token,...rest}=now;
|
|
61
|
-
writePrivate(configPath,JSON.stringify({...rest,tokenStore:store(next)},null,2)+'\n');
|
|
72
|
+
writePrivate(configPath(),JSON.stringify({...rest,tokenStore:store(next)},null,2)+'\n');
|
|
62
73
|
}
|
|
63
74
|
return next;
|
|
64
75
|
}catch{return credentials;}
|
|
@@ -113,7 +124,7 @@ async function login(args=[]){
|
|
|
113
124
|
// No gatewayUrl: with one, `caveman enable claude` switches to managed mode
|
|
114
125
|
// and writes a gateway base URL with no key into Claude Code's settings.
|
|
115
126
|
const {token,logoutPendingLocalCleanup,gatewayUrl,organizationId,projectId,...rest}=readConfig();
|
|
116
|
-
writePrivate(configPath,JSON.stringify({...rest,baseURL,tokenStore,...(typeof oid==='string'?{organizationId:oid}:{}),...(credentials.project_id?{projectId:credentials.project_id}:{})},null,2)+'\n');
|
|
127
|
+
writePrivate(configPath(),JSON.stringify({...rest,baseURL,tokenStore,...(typeof oid==='string'?{organizationId:oid}:{}),...(credentials.project_id?{projectId:credentials.project_id}:{})},null,2)+'\n');
|
|
117
128
|
// The receipt: the server replays a durable grant until told it was stored,
|
|
118
129
|
// and may revoke one that never is.
|
|
119
130
|
if(Object.keys(credentials).length>1){
|
|
@@ -156,13 +167,13 @@ async function logout(){
|
|
|
156
167
|
}
|
|
157
168
|
// Remote revocation is recorded before the only local credential goes, so a
|
|
158
169
|
// failed cleanup is retried without presenting a revoked session.
|
|
159
|
-
writePrivate(configPath,JSON.stringify({...meta,logoutPendingLocalCleanup:true},null,2)+'\n');
|
|
170
|
+
writePrivate(configPath(),JSON.stringify({...meta,logoutPendingLocalCleanup:true},null,2)+'\n');
|
|
160
171
|
if(meta.tokenStore==='keychain'||!meta.tokenStore&&process.platform==='darwin'&&!process.env.CAVE_NO_KEYCHAIN){
|
|
161
172
|
try{execFileSync('security',['delete-generic-password',...keychain],{stdio:'ignore'});}catch(error){if(error.status!==44)throw new Error('Could not remove the credential from the macOS Keychain.');} // 44: no such item
|
|
162
173
|
}
|
|
163
174
|
if(meta.tokenStore!=='keychain')rmSync(credentialsPath,{force:true});
|
|
164
175
|
const {token,tokenStore,projectId,organizationId,gatewayUrl,logoutPendingLocalCleanup,...rest}=meta;
|
|
165
|
-
writePrivate(configPath,JSON.stringify({...rest,baseURL:''},null,2)+'\n');
|
|
176
|
+
writePrivate(configPath(),JSON.stringify({...rest,baseURL:''},null,2)+'\n');
|
|
166
177
|
process.stdout.write(process.stdout.isTTY?'Signed out.\n':JSON.stringify({logged_out:true})+'\n');
|
|
167
178
|
return 0;
|
|
168
179
|
}
|
|
@@ -45,7 +45,7 @@ cvm auth status # who you are signed in as; exit 3 means signed out
|
|
|
45
45
|
means this `cvm` and the server disagree: tell the user to install the `cvm`
|
|
46
46
|
that matches their Caveman release (`npm i -g "@caveman-ai/cloud@VERSION"`).
|
|
47
47
|
- `cvm` missing altogether: the user runs
|
|
48
|
-
`npm i -g
|
|
48
|
+
`npm i -g @caveman-ai/cloud@rc`, then `cvm setup`.
|
|
49
49
|
|
|
50
50
|
## 2. Find the operation, read its schema, call it
|
|
51
51
|
|
|
@@ -16,7 +16,7 @@ experiments; it does not route the agent's own LLM traffic.
|
|
|
16
16
|
1. Check first: `cvm context show` and `cvm auth status`. A signed-in `cvm` on
|
|
17
17
|
the task's project is a working connection; reuse it. Never select the first
|
|
18
18
|
available project or silently substitute another identity.
|
|
19
|
-
2. If `cvm` is missing, the user runs `npm i -g
|
|
19
|
+
2. If `cvm` is missing, the user runs `npm i -g @caveman-ai/cloud@rc`, then
|
|
20
20
|
`cvm setup --project PROJECT_ID` with the project from the dashboard's
|
|
21
21
|
**Connect your coding agent** step. On a private install `CAVE_API_URL` names
|
|
22
22
|
its API first. `cvm setup` signs in through the browser, saves the project
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caveman-ai/cloud",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.26",
|
|
4
4
|
"description": "cvm, the Caveman Cloud CLI: every Cloud operation from the terminal, plus the Cloud MCP server. Sign in with `cvm login`; `cvm setup` signs in, picks the project and installs the Caveman Cloud skill.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|