@arbol-ai/cli 1.20261017.200040 → 1.20261018.212151
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 +9 -2
- package/dist/index.js +145 -21
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
`arbol`, the Arbol API from your terminal. Its commands are generated by
|
|
4
4
|
[Cloudflare Forge](https://github.com/cloudflare/forge) from the platform's public OpenAPI,
|
|
5
5
|
[`https://api.getarbol.com/openapi.json`](https://api.getarbol.com/openapi.json), and every request
|
|
6
|
-
goes through [`@arbol-ai/sdk`](https://www.npmjs.com/package/@arbol-ai/sdk).
|
|
7
|
-
the
|
|
6
|
+
goes through [`@arbol-ai/sdk`](https://www.npmjs.com/package/@arbol-ai/sdk). A release's version is
|
|
7
|
+
the date and time it was built (`1.YYYYMMDD.HHMMSS`, UTC); `arbol --version` also names the API release
|
|
8
|
+
its commands were generated from.
|
|
8
9
|
|
|
9
10
|
```sh
|
|
10
11
|
npm install --global @arbol-ai/cli
|
|
@@ -31,6 +32,12 @@ ARBOL_PROFILE=ci arbol sura appointments list
|
|
|
31
32
|
|
|
32
33
|
An API key given to one command, through `--api-key-stdin` or `ARBOL_API_KEY`, wins over every
|
|
33
34
|
profile, as `GH_TOKEN` does for `gh`: scripts, servers and CI can run without saving anything.
|
|
35
|
+
When `arbol auth login` without `--profile` signs in as someone else, or to another organization,
|
|
36
|
+
than the default profile is, it asks where the login goes: a new profile (it suggests a name from the
|
|
37
|
+
organization), in place of the profile, or beside it in the same profile (`arbol auth switch` moves
|
|
38
|
+
between them). In a script it saves nothing and asks for `--profile` instead. `--profile <name>`
|
|
39
|
+
always saves the login in that profile.
|
|
40
|
+
|
|
34
41
|
`arbol auth status` prints which credential is in use and, for a login, its organization;
|
|
35
42
|
`arbol auth logout` signs a login out, or forgets a profile's API key (the key itself works until it
|
|
36
43
|
is revoked in Arbol Account).
|
package/dist/index.js
CHANGED
|
@@ -64673,6 +64673,27 @@ var schemaComponents = {
|
|
|
64673
64673
|
}
|
|
64674
64674
|
}
|
|
64675
64675
|
};
|
|
64676
|
+
// src/generated/generation.json
|
|
64677
|
+
var generation_default = {
|
|
64678
|
+
formatVersion: 4,
|
|
64679
|
+
release: "1.20261017.200040",
|
|
64680
|
+
openapiSha256: "279d68eeef778edef60506856eff29b16919284d546ce9d9f92f6043c927b5a8",
|
|
64681
|
+
forge: {
|
|
64682
|
+
repository: "https://github.com/cloudflare/forge",
|
|
64683
|
+
commit: "7f86447210b7bea3779bfdab3cd5655eade0eadb",
|
|
64684
|
+
license: "Apache-2.0",
|
|
64685
|
+
version: "0.1.0"
|
|
64686
|
+
},
|
|
64687
|
+
plugins: {
|
|
64688
|
+
cli: "39007ea178b892e91916a2afa22883ba71b8f0a9c12b8a0db7f27d9fa128890d"
|
|
64689
|
+
},
|
|
64690
|
+
outputs: [
|
|
64691
|
+
{
|
|
64692
|
+
path: "src/generated/commands.ts",
|
|
64693
|
+
sha256: "6815a99764c8bf660482ba688da147eeae2ea2c1c82775964a5683acb5812dde"
|
|
64694
|
+
}
|
|
64695
|
+
]
|
|
64696
|
+
};
|
|
64676
64697
|
|
|
64677
64698
|
// src/oauth.ts
|
|
64678
64699
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -64707,14 +64728,8 @@ function openBrowser(url) {
|
|
|
64707
64728
|
child.unref();
|
|
64708
64729
|
}
|
|
64709
64730
|
var ORGANIZATION = /^org_[A-Za-z0-9]+$/;
|
|
64710
|
-
async function
|
|
64711
|
-
|
|
64712
|
-
organizationId,
|
|
64713
|
-
nameOrganization = () => Promise.resolve(null)
|
|
64714
|
-
} = {}) {
|
|
64715
|
-
await credentials.initialize();
|
|
64716
|
-
await credentials.store.get(credentials.loginKey("keyring-preflight", "any"));
|
|
64717
|
-
const { config } = credentials;
|
|
64731
|
+
async function signIn(config, { noBrowser = false, organizationId, nameOrganization = () => Promise.resolve(null), preflight } = {}) {
|
|
64732
|
+
await preflight?.();
|
|
64718
64733
|
const redirect = new URL(config.redirectUri);
|
|
64719
64734
|
if (redirect.protocol !== "http:" || redirect.hostname !== "127.0.0.1" || !redirect.port) {
|
|
64720
64735
|
throw new Error("The CLI can only receive a login on a 127.0.0.1 loopback address");
|
|
@@ -64798,17 +64813,52 @@ ${authorization.href}
|
|
|
64798
64813
|
subject: claims.sub,
|
|
64799
64814
|
organizationId: organization
|
|
64800
64815
|
};
|
|
64801
|
-
|
|
64802
|
-
await credentials.locked(credentials.loginKey(claims.sub, organization), () => credentials.save(session, name));
|
|
64803
|
-
process.stderr.write(`Signed in to ${name ?? organization}${name ? ` (${organization})` : ""}.
|
|
64804
|
-
`);
|
|
64805
|
-
return { ...credentials.loginOf(session), organizationName: name };
|
|
64816
|
+
return { session, organizationName: await nameOrganization(tokens.access_token).catch(() => null) };
|
|
64806
64817
|
} finally {
|
|
64807
64818
|
clearTimeout(timeout);
|
|
64808
64819
|
server.closeAllConnections();
|
|
64809
64820
|
server.close();
|
|
64810
64821
|
}
|
|
64811
64822
|
}
|
|
64823
|
+
async function keep(credentials, { session, organizationName }) {
|
|
64824
|
+
await credentials.initialize();
|
|
64825
|
+
await credentials.locked(credentials.loginKey(session.subject, session.organizationId), () => credentials.save(session, organizationName));
|
|
64826
|
+
process.stderr.write(`Signed in to ${organizationName ?? session.organizationId}${organizationName ? ` (${session.organizationId})` : ""}.
|
|
64827
|
+
`);
|
|
64828
|
+
}
|
|
64829
|
+
async function login(credentials, options = {}) {
|
|
64830
|
+
await credentials.initialize();
|
|
64831
|
+
const signed = await signIn(credentials.config, {
|
|
64832
|
+
...options,
|
|
64833
|
+
preflight: () => credentials.store.get(credentials.loginKey("keyring-preflight", "any"))
|
|
64834
|
+
});
|
|
64835
|
+
await keep(credentials, signed);
|
|
64836
|
+
}
|
|
64837
|
+
async function revoke(config, refreshToken) {
|
|
64838
|
+
try {
|
|
64839
|
+
const response = await oauth.revocationRequest(await authority(config), client(config), oauth.None(), refreshToken, {
|
|
64840
|
+
signal: AbortSignal.timeout(15000)
|
|
64841
|
+
});
|
|
64842
|
+
await response.body?.cancel();
|
|
64843
|
+
return response.ok;
|
|
64844
|
+
} catch {
|
|
64845
|
+
return false;
|
|
64846
|
+
}
|
|
64847
|
+
}
|
|
64848
|
+
async function forgetAll(credentials) {
|
|
64849
|
+
let confirmed = true;
|
|
64850
|
+
for (const login of await credentials.logins()) {
|
|
64851
|
+
await credentials.locked(login.key, async () => {
|
|
64852
|
+
const session = await credentials.session(login).catch(() => null);
|
|
64853
|
+
if (session?.refreshToken && !await revoke(credentials.config, session.refreshToken)) {
|
|
64854
|
+
confirmed = false;
|
|
64855
|
+
}
|
|
64856
|
+
await credentials.store.delete(login.key);
|
|
64857
|
+
await credentials.forget(login);
|
|
64858
|
+
});
|
|
64859
|
+
}
|
|
64860
|
+
return confirmed;
|
|
64861
|
+
}
|
|
64812
64862
|
async function accessToken(credentials) {
|
|
64813
64863
|
const login = await credentials.active();
|
|
64814
64864
|
const again = `run arbol auth login --profile ${credentials.profile}`;
|
|
@@ -64995,9 +65045,10 @@ ${name}`);
|
|
|
64995
65045
|
}
|
|
64996
65046
|
|
|
64997
65047
|
// src/index.ts
|
|
64998
|
-
var VERSION = "1.
|
|
65048
|
+
var VERSION = "1.20261018.212151";
|
|
65049
|
+
var RELEASE = generation_default.release;
|
|
64999
65050
|
var SERVICES = [...new Set(Object.keys(commands).map((name) => name.split(" ")[0]))].toSorted();
|
|
65000
|
-
var HELP = `Arbol CLI ${VERSION}: the Arbol platform from your terminal.
|
|
65051
|
+
var HELP = `Arbol CLI ${VERSION} (Arbol API ${RELEASE}): the Arbol platform from your terminal.
|
|
65001
65052
|
|
|
65002
65053
|
Usage
|
|
65003
65054
|
arbol <command> [flags]
|
|
@@ -65194,7 +65245,7 @@ Run arbol --help.`);
|
|
|
65194
65245
|
}
|
|
65195
65246
|
const { positionals, values } = parsed;
|
|
65196
65247
|
if (values.version) {
|
|
65197
|
-
process.stdout.write(`arbol ${VERSION}
|
|
65248
|
+
process.stdout.write(`arbol ${VERSION} (Arbol API ${RELEASE})
|
|
65198
65249
|
`);
|
|
65199
65250
|
return;
|
|
65200
65251
|
}
|
|
@@ -65294,12 +65345,39 @@ Run arbol --help.`);
|
|
|
65294
65345
|
throw new UsageError(`Profile ${name} is for ${api}, not ${chosenApi()}; name another with --profile <name>`);
|
|
65295
65346
|
}
|
|
65296
65347
|
const auth = await discover(api);
|
|
65297
|
-
|
|
65348
|
+
const here = credentialsFor(name, auth);
|
|
65349
|
+
const signed = await signIn(auth, {
|
|
65298
65350
|
noBrowser: values["no-browser"],
|
|
65299
|
-
nameOrganization: (token) => nameOrganization(api, token)
|
|
65351
|
+
nameOrganization: (token) => nameOrganization(api, token),
|
|
65352
|
+
preflight: () => here.store.get(here.loginKey("keyring-preflight", "any"))
|
|
65300
65353
|
});
|
|
65301
|
-
|
|
65302
|
-
|
|
65354
|
+
let destination = name;
|
|
65355
|
+
if (named === undefined && existing?.kind === "login") {
|
|
65356
|
+
const before = credentialsFor(name, existing.auth);
|
|
65357
|
+
const current = await before.active().catch(() => null);
|
|
65358
|
+
if (current && (current.subject !== signed.session.subject || current.organizationId !== signed.session.organizationId)) {
|
|
65359
|
+
const known = (await before.logins()).find((entry) => entry.key === current.key);
|
|
65360
|
+
let choice;
|
|
65361
|
+
try {
|
|
65362
|
+
choice = await placement(name, known ?? { ...current, organizationName: null }, signed, profiles);
|
|
65363
|
+
} catch (error) {
|
|
65364
|
+
if (signed.session.refreshToken) {
|
|
65365
|
+
await revoke(auth, signed.session.refreshToken);
|
|
65366
|
+
}
|
|
65367
|
+
throw error;
|
|
65368
|
+
}
|
|
65369
|
+
if (choice.kind === "new") {
|
|
65370
|
+
destination = choice.name;
|
|
65371
|
+
} else if (choice.kind === "replace" && !await forgetAll(before)) {
|
|
65372
|
+
process.stderr.write(`Profile ${name}'s previous login was removed, but its revocation could not be confirmed.
|
|
65373
|
+
`);
|
|
65374
|
+
}
|
|
65375
|
+
}
|
|
65376
|
+
}
|
|
65377
|
+
await keep(credentialsFor(destination, auth), signed);
|
|
65378
|
+
await profiles.save(destination, { kind: "login", auth });
|
|
65379
|
+
process.stderr.write(destination === name ? `Saved${await saved(name)}.
|
|
65380
|
+
` : `Saved${await saved(destination)}; use it with --profile ${destination}, or by default after arbol auth use ${destination}.
|
|
65303
65381
|
`);
|
|
65304
65382
|
return;
|
|
65305
65383
|
}
|
|
@@ -65603,6 +65681,51 @@ Type yes to continue: `);
|
|
|
65603
65681
|
prompt.close();
|
|
65604
65682
|
}
|
|
65605
65683
|
}
|
|
65684
|
+
function suggestedName(organizationName, taken) {
|
|
65685
|
+
const base = folded(organizationName ?? "").replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "").slice(0, 28).replace(/-+$/, "") || "profile";
|
|
65686
|
+
let name = base;
|
|
65687
|
+
for (let next = 2;taken.includes(name); next++) {
|
|
65688
|
+
name = `${base}-${next}`;
|
|
65689
|
+
}
|
|
65690
|
+
return name;
|
|
65691
|
+
}
|
|
65692
|
+
async function placement(profile, current, signed, profiles) {
|
|
65693
|
+
const describe = (subject, organizationId, name) => `${name ? `${name} (${organizationId})` : organizationId} as ${subject}`;
|
|
65694
|
+
const now = describe(signed.session.subject, signed.session.organizationId, signed.organizationName);
|
|
65695
|
+
const before = describe(current.subject, current.organizationId, current.organizationName);
|
|
65696
|
+
if (!(process.stdin.isTTY && process.stderr.isTTY)) {
|
|
65697
|
+
throw new UsageError(`You signed in to ${now}, but profile ${profile} is ${before}. Nothing was saved: run arbol auth login --profile <name> to keep both, or arbol auth logout first to replace it.`);
|
|
65698
|
+
}
|
|
65699
|
+
const taken = (await profiles.list()).map(({ name }) => name);
|
|
65700
|
+
const suggestion = suggestedName(signed.organizationName, taken);
|
|
65701
|
+
process.stderr.write(`You signed in to ${now}.
|
|
65702
|
+
Profile ${profile} is ${before}.
|
|
65703
|
+
1. Save it as a new profile (${suggestion})
|
|
65704
|
+
2. Replace profile ${profile} with it
|
|
65705
|
+
3. Add it to profile ${profile}; arbol auth switch moves between them
|
|
65706
|
+
`);
|
|
65707
|
+
const prompt = createInterface({ input: process.stdin, output: process.stderr });
|
|
65708
|
+
try {
|
|
65709
|
+
const answer = (await prompt.question("Choose 1, 2 or 3 [1]: ")).trim() || "1";
|
|
65710
|
+
if (answer === "2") {
|
|
65711
|
+
return { kind: "replace" };
|
|
65712
|
+
}
|
|
65713
|
+
if (answer === "3") {
|
|
65714
|
+
return { kind: "add" };
|
|
65715
|
+
}
|
|
65716
|
+
if (answer !== "1") {
|
|
65717
|
+
throw new UsageError("Choose 1, 2 or 3; nothing was saved.");
|
|
65718
|
+
}
|
|
65719
|
+
const name = (await prompt.question(`Profile name [${suggestion}]: `)).trim() || suggestion;
|
|
65720
|
+
validProfileName(name);
|
|
65721
|
+
if (taken.includes(name)) {
|
|
65722
|
+
throw new UsageError(`Profile ${name} exists; nothing was saved. Run arbol auth login --profile ${name} to sign it in.`);
|
|
65723
|
+
}
|
|
65724
|
+
return { kind: "new", name };
|
|
65725
|
+
} finally {
|
|
65726
|
+
prompt.close();
|
|
65727
|
+
}
|
|
65728
|
+
}
|
|
65606
65729
|
async function nameOrganization(api, token) {
|
|
65607
65730
|
const { data } = await createClient({ baseUrl: api, token }).account.organizations.current({});
|
|
65608
65731
|
return typeof data.name === "string" ? data.name : null;
|
|
@@ -65742,5 +65865,6 @@ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.ar
|
|
|
65742
65865
|
}
|
|
65743
65866
|
}
|
|
65744
65867
|
export {
|
|
65745
|
-
main
|
|
65868
|
+
main,
|
|
65869
|
+
suggestedName
|
|
65746
65870
|
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arbol-ai/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20261018.212151",
|
|
4
4
|
"description": "The Arbol CLI: the Arbol API from your terminal, with commands generated by Cloudflare Forge from the public OpenAPI.",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"arbol",
|
|
7
|
+
"cli",
|
|
8
|
+
"api",
|
|
9
|
+
"openapi",
|
|
10
|
+
"forge"
|
|
11
|
+
],
|
|
6
12
|
"homepage": "https://github.com/getarbol/project-arbol-cli#readme",
|
|
7
13
|
"bugs": {
|
|
8
14
|
"url": "https://github.com/getarbol/project-arbol-cli/issues"
|
|
@@ -15,7 +21,11 @@
|
|
|
15
21
|
"bin": {
|
|
16
22
|
"arbol": "./dist/index.js"
|
|
17
23
|
},
|
|
18
|
-
"files": [
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
19
29
|
"type": "module",
|
|
20
30
|
"engines": {
|
|
21
31
|
"node": ">=26"
|
|
@@ -27,7 +37,7 @@
|
|
|
27
37
|
"scripts": {
|
|
28
38
|
"forge": "bun scripts/forge.ts",
|
|
29
39
|
"openapi": "curl -fsS --proto =https --max-redirs 0 --max-filesize 33554432 -o openapi.json https://api.getarbol.com/openapi.json",
|
|
30
|
-
"generate": "bun scripts/generate.ts openapi.json
|
|
40
|
+
"generate": "bun scripts/generate.ts openapi.json",
|
|
31
41
|
"typecheck": "tsc --noEmit",
|
|
32
42
|
"test": "bun test tests",
|
|
33
43
|
"build": "bun scripts/build.ts",
|