@shardflux/cli 0.8.1 → 0.10.0
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/CHANGELOG.md +94 -0
- package/README.md +132 -27
- package/dist/account-args.js +32 -4
- package/dist/account-commands.js +234 -111
- package/dist/args.d.ts +1 -2
- package/dist/args.js +288 -6
- package/dist/commands.d.ts +15 -1
- package/dist/commands.js +384 -5
- package/dist/format.d.ts +21 -1
- package/dist/format.js +61 -0
- package/dist/main.js +8 -0
- package/package.json +2 -2
package/dist/account-commands.js
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* setup), else the only one there is; with several the usage error lists them.
|
|
14
14
|
*/
|
|
15
15
|
import { chmod, writeFile } from 'node:fs/promises';
|
|
16
|
-
import { API_KEY_TOOL_PERMISSIONS, CheckoutTimeoutError, Shardflux, ShardfluxAccount, ShardfluxApiError, fetchBillingCatalog, parseEmailToken } from '@shardflux/sdk';
|
|
17
|
-
import { UUID, parseDuration, parsePositiveInt } from "./args.js";
|
|
16
|
+
import { API_KEY_TOOL_PERMISSIONS, CheckoutTimeoutError, Shardflux, ShardfluxAccount, ShardfluxApiError, codexIdentityProof, fetchBillingCatalog, parseEmailToken } from '@shardflux/sdk';
|
|
17
|
+
import { CLI_VERSION, UUID, parseDuration, parsePositiveInt } from "./args.js";
|
|
18
18
|
import { openUrl } from "./browser.js";
|
|
19
19
|
import { fileExists, oneOf, out, parseSlugVersion, principal, readInput, resolveWorkspace, str, strs, waitTimeout } from "./commands.js";
|
|
20
20
|
import { AuthConfigError, CheckoutEndedError, InterruptedError, MfaPendingError, NotFoundError, UsageError } from "./errors.js";
|
|
@@ -83,16 +83,24 @@ async function mfaEnabled(account) {
|
|
|
83
83
|
* Step-up: the password (a prompt, or the next piped line) plus, with two-factor authentication, the factor from the
|
|
84
84
|
* flags, else a prompt (or the next piped line).
|
|
85
85
|
*/
|
|
86
|
-
async function stepUp(ctx, account, factor, command) {
|
|
86
|
+
async function stepUp(ctx, account, factor, command, codex = false) {
|
|
87
87
|
const needsFactor = factor === null && (await mfaEnabled(account));
|
|
88
|
-
const
|
|
88
|
+
const first = codex ? { codexIdToken: await codexProofOrThrow(ctx) } : { password: await ctx.input.secret('Password: ', 'The password', pipe(command)) };
|
|
89
89
|
let f = factor;
|
|
90
90
|
if (needsFactor) {
|
|
91
91
|
f = factorOf(await ctx.input.answer('Authenticator code (or a recovery code): '));
|
|
92
92
|
if (f === null)
|
|
93
93
|
throw new UsageError('two-factor authentication is on: pass --code <totp> (or --recovery-code <code>)');
|
|
94
94
|
}
|
|
95
|
-
return account.auth.stepUp({
|
|
95
|
+
return account.auth.stepUp({ ...first, ...factorParams(f) });
|
|
96
|
+
}
|
|
97
|
+
/** The ID token of this machine's Codex login (0.9.0+), refreshed by Codex; a usage error says why there is none. */
|
|
98
|
+
async function codexProofOrThrow(ctx) {
|
|
99
|
+
const proof = await codexIdentityProof({ env: ctx.io.env, clientName: 'shard', clientVersion: CLI_VERSION });
|
|
100
|
+
if (!proof.ok)
|
|
101
|
+
throw new UsageError(proof.message, proof.reason === 'api_key_login' || proof.reason === 'not_signed_in' ? 'codex login (then run this again)' : undefined);
|
|
102
|
+
ctx.addSecret(proof.idToken);
|
|
103
|
+
return proof.idToken;
|
|
96
104
|
}
|
|
97
105
|
/** Runs an account call; on 403 step_up_required on a terminal, steps up and retries once (else the error stands). */
|
|
98
106
|
async function withStepUp(ctx, account, call) {
|
|
@@ -248,7 +256,7 @@ function userLine(u) {
|
|
|
248
256
|
/** What to do after signing in: verify, set up, or open a workspace. */
|
|
249
257
|
function nextAfterSignIn(ctx, user, me) {
|
|
250
258
|
if (!user.email_verified)
|
|
251
|
-
return `shard
|
|
259
|
+
return `shard verify (emails a 6-digit code; or open the link we emailed you: shard auth verify-email '<link>')`;
|
|
252
260
|
const profile = ctx.store.profile(ctx.apiUrl);
|
|
253
261
|
const hasKey = profile.api_key !== undefined || Boolean(ctx.io.env.SHARDFLUX_API_KEY?.trim());
|
|
254
262
|
if (!hasKey || !me || me.memberships.length === 0)
|
|
@@ -535,6 +543,127 @@ async function waitedVolume(ctx, call) {
|
|
|
535
543
|
throw err;
|
|
536
544
|
}
|
|
537
545
|
}
|
|
546
|
+
/**
|
|
547
|
+
* `shard setup`, also the last step of `shard signup`: uses (or creates) an organization and project, creates an API
|
|
548
|
+
* key and saves it in the credentials file. Returns the report instead of printing it.
|
|
549
|
+
*/
|
|
550
|
+
async function runSetup(ctx, values, account) {
|
|
551
|
+
const me = await account.me();
|
|
552
|
+
if (me.type !== 'user' || !me.user)
|
|
553
|
+
throw new UsageError('setup needs a user session: shard auth login');
|
|
554
|
+
const tools = toolsFrom(values);
|
|
555
|
+
const profile = ctx.store.profile(ctx.apiUrl);
|
|
556
|
+
const created = { organization: false, project: false, api_key: false };
|
|
557
|
+
const flagged = str(values.org) !== undefined || str(values.project) !== undefined;
|
|
558
|
+
let keyCheck = null;
|
|
559
|
+
if (profile.api_key && values['new-key'] !== true) {
|
|
560
|
+
keyCheck = await checkKey(ctx, profile.api_key.token);
|
|
561
|
+
if (!keyCheck.ok && !ctx.json)
|
|
562
|
+
ctx.io.stderr.write(`note: the saved API key ${profile.api_key.key_id} no longer works (${keyCheck.reason}); creating a new one.\n`);
|
|
563
|
+
}
|
|
564
|
+
let org;
|
|
565
|
+
let project;
|
|
566
|
+
// The working saved key's own organization and project, when this user can see them.
|
|
567
|
+
let home = null;
|
|
568
|
+
if (keyCheck?.ok && !flagged) {
|
|
569
|
+
try {
|
|
570
|
+
home = { org: await account.organizations.get(keyCheck.organizationId), project: await account.projects.get(keyCheck.projectId) };
|
|
571
|
+
}
|
|
572
|
+
catch (err) {
|
|
573
|
+
if (!gone(err))
|
|
574
|
+
throw err;
|
|
575
|
+
if (!ctx.json)
|
|
576
|
+
ctx.io.stderr.write(`note: the saved API key ${profile.api_key.key_id} belongs to a project you cannot see; creating a key for your own.\n`);
|
|
577
|
+
keyCheck = { ok: false, reason: 'another account' };
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (home) {
|
|
581
|
+
org = home.org;
|
|
582
|
+
project = home.project;
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
const orgs = await allOrgs(account);
|
|
586
|
+
const orgRef = str(values.org);
|
|
587
|
+
const savedOrg = profile.organization_id ? orgs.find((o) => o.id === profile.organization_id) : undefined;
|
|
588
|
+
if (orgRef !== undefined)
|
|
589
|
+
org = await findOrg(account, orgRef);
|
|
590
|
+
else if (savedOrg)
|
|
591
|
+
org = savedOrg;
|
|
592
|
+
else if (orgs.length === 1)
|
|
593
|
+
org = orgs[0];
|
|
594
|
+
else if (orgs.length === 0) {
|
|
595
|
+
org = await account.organizations.create({ name: str(values['org-name'])?.trim() || defaultOrgName(me.user) });
|
|
596
|
+
created.organization = true;
|
|
597
|
+
}
|
|
598
|
+
else
|
|
599
|
+
throw new UsageError(`You belong to ${orgs.length} organizations; pick one with --org <id|slug>:\n${choices(orgs)}\n`, 'or save a default first: shard orgs use <id|slug>');
|
|
600
|
+
const projects = created.organization ? [] : await allProjects(account, org.id);
|
|
601
|
+
const projectRef = str(values.project);
|
|
602
|
+
const savedProject = profile.project_id ? projects.find((p) => p.id === profile.project_id) : undefined;
|
|
603
|
+
if (projectRef !== undefined) {
|
|
604
|
+
const hit = byRef(projects, projectRef);
|
|
605
|
+
if (!hit)
|
|
606
|
+
throw new NotFoundError(`No project "${projectRef}" in ${org.name} (${org.slug}).`, 'list them: shard projects list');
|
|
607
|
+
project = hit;
|
|
608
|
+
}
|
|
609
|
+
else if (savedProject)
|
|
610
|
+
project = savedProject;
|
|
611
|
+
else if (projects.length === 1)
|
|
612
|
+
project = projects[0];
|
|
613
|
+
else if (projects.length === 0) {
|
|
614
|
+
project = await account.projects.create(org.id, { name: str(values['project-name'])?.trim() || 'Default' });
|
|
615
|
+
created.project = true;
|
|
616
|
+
}
|
|
617
|
+
else
|
|
618
|
+
throw new UsageError(`${org.name} has ${projects.length} projects; pick one with --project <id|slug>:\n${choices(projects)}\n`, 'or save a default first: shard projects use <id|slug>');
|
|
619
|
+
}
|
|
620
|
+
const keep = keyCheck?.ok === true && profile.api_key?.project_id === project.id;
|
|
621
|
+
let key;
|
|
622
|
+
let permissions = tools;
|
|
623
|
+
if (keep)
|
|
624
|
+
key = profile.api_key;
|
|
625
|
+
else {
|
|
626
|
+
const name = (str(values['key-name'])?.trim() || `shard-cli ${ctx.hostname()}`).slice(0, 200);
|
|
627
|
+
const res = await account.apiKeys.create(project.id, { name, toolPermissions: tools });
|
|
628
|
+
ctx.addSecret(res.secret);
|
|
629
|
+
key = storedKey(res.api_key, res.secret);
|
|
630
|
+
permissions = res.api_key.tool_permissions;
|
|
631
|
+
created.api_key = true;
|
|
632
|
+
}
|
|
633
|
+
ctx.store.update(ctx.apiUrl, (p) => {
|
|
634
|
+
p.organization_id = org.id;
|
|
635
|
+
p.project_id = project.id;
|
|
636
|
+
p.api_key = key;
|
|
637
|
+
});
|
|
638
|
+
const check = keep ? keyCheck : await checkKey(ctx, key.token);
|
|
639
|
+
if (!check?.ok || check.projectId !== project.id)
|
|
640
|
+
throw new UsageError(`the API key ${key.key_id} was saved but does not authenticate for project ${project.id} (${check && !check.ok ? check.reason : 'another project'})`, 'try again: shard setup --new-key');
|
|
641
|
+
if (keep) {
|
|
642
|
+
const k = (await account.apiKeys.list(project.id, { limit: 200 })).data.find((x) => x.id === key.id);
|
|
643
|
+
if (k)
|
|
644
|
+
permissions = k.tool_permissions;
|
|
645
|
+
}
|
|
646
|
+
const tag = (made) => (made ? 'created' : 'existing');
|
|
647
|
+
const envNote = ctx.io.env.SHARDFLUX_API_KEY?.trim() ? 'note: SHARDFLUX_API_KEY is set in this environment and wins over the saved key; unset it to use the saved key.\n' : '';
|
|
648
|
+
return {
|
|
649
|
+
text: (keep ? 'Already set up; the saved API key works.\n' : 'Set up.\n') +
|
|
650
|
+
kv([
|
|
651
|
+
['organization', `${org.name} (${org.slug}, ${org.id}) ${tag(created.organization)}`],
|
|
652
|
+
['project', `${project.name} (${project.slug}, ${project.id}) ${tag(created.project)}`],
|
|
653
|
+
['api key', `"${key.name}" ${key.key_id} (tools: ${toolsText(permissions)}) ${keep ? 'saved earlier' : 'created'}, saved in ${ctx.store.file}`],
|
|
654
|
+
]) +
|
|
655
|
+
envNote +
|
|
656
|
+
`next: ${NEXT_WORKSPACE}\n`,
|
|
657
|
+
data: {
|
|
658
|
+
organization: org,
|
|
659
|
+
project,
|
|
660
|
+
api_key: { id: key.id, key_id: key.key_id, name: key.name, project_id: key.project_id, tool_permissions: permissions },
|
|
661
|
+
created,
|
|
662
|
+
credentials_file: ctx.store.file,
|
|
663
|
+
next: NEXT_WORKSPACE,
|
|
664
|
+
},
|
|
665
|
+
};
|
|
666
|
+
}
|
|
538
667
|
// ---- handlers --------------------------------------------------------------------------------------------------
|
|
539
668
|
export const ACCOUNT_HANDLERS = {
|
|
540
669
|
async 'auth register'(ctx, values) {
|
|
@@ -582,6 +711,8 @@ export const ACCOUNT_HANDLERS = {
|
|
|
582
711
|
return 0;
|
|
583
712
|
},
|
|
584
713
|
async 'auth login'(ctx, values) {
|
|
714
|
+
if (values.codex === true)
|
|
715
|
+
return ACCOUNT_HANDLERS.signup(ctx, { ...values, setup: false, codex: true, email: undefined }, []);
|
|
585
716
|
const email = emailArg(need(values, 'email', '--email you@example.com'), '--email');
|
|
586
717
|
let factor = factorFlags(values);
|
|
587
718
|
const password = await ctx.input.secret(`Password for ${email}: `, 'The password', pipe(`shard auth login --email ${email}`));
|
|
@@ -762,7 +893,7 @@ export const ACCOUNT_HANDLERS = {
|
|
|
762
893
|
},
|
|
763
894
|
async 'auth step-up'(ctx, values) {
|
|
764
895
|
const account = ctx.account();
|
|
765
|
-
const r = await stepUp(ctx, account, factorFlags(values), 'shard auth step-up');
|
|
896
|
+
const r = await stepUp(ctx, account, factorFlags(values), 'shard auth step-up', values.codex === true);
|
|
766
897
|
out(ctx, `Step-up confirmed until ${time(r.step_up_valid_until)}.\n`, { step_up_valid_until: r.step_up_valid_until });
|
|
767
898
|
return 0;
|
|
768
899
|
},
|
|
@@ -840,119 +971,111 @@ export const ACCOUNT_HANDLERS = {
|
|
|
840
971
|
return 0;
|
|
841
972
|
},
|
|
842
973
|
async setup(ctx, values) {
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
const
|
|
849
|
-
const
|
|
850
|
-
|
|
851
|
-
let
|
|
852
|
-
if (
|
|
853
|
-
|
|
854
|
-
if (
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
let home = null;
|
|
861
|
-
if (keyCheck?.ok && !flagged) {
|
|
862
|
-
try {
|
|
863
|
-
home = { org: await account.organizations.get(keyCheck.organizationId), project: await account.projects.get(keyCheck.projectId) };
|
|
974
|
+
const r = await runSetup(ctx, values, ctx.account());
|
|
975
|
+
out(ctx, r.text, r.data);
|
|
976
|
+
return 0;
|
|
977
|
+
},
|
|
978
|
+
async signup(ctx, values) {
|
|
979
|
+
const email = str(values.email) === undefined ? undefined : emailArg(str(values.email), '--email');
|
|
980
|
+
const name = str(values.name)?.trim();
|
|
981
|
+
let codexNote = null;
|
|
982
|
+
let idToken = null;
|
|
983
|
+
if (values.codex !== false) {
|
|
984
|
+
const proof = await codexIdentityProof({ env: ctx.io.env, clientName: 'shard', clientVersion: CLI_VERSION });
|
|
985
|
+
if (proof.ok) {
|
|
986
|
+
idToken = proof.idToken;
|
|
987
|
+
ctx.addSecret(idToken);
|
|
988
|
+
if (email !== undefined && proof.email !== null && proof.email.toLowerCase() !== email.toLowerCase()) {
|
|
989
|
+
codexNote = `note: signed up with your Codex login (${proof.email}), not --email ${email}; pass --no-codex to use ${email}.\n`;
|
|
990
|
+
}
|
|
864
991
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
throw err;
|
|
868
|
-
if (!ctx.json)
|
|
869
|
-
ctx.io.stderr.write(`note: the saved API key ${profile.api_key.key_id} belongs to a project you cannot see; creating a key for your own.\n`);
|
|
870
|
-
keyCheck = { ok: false, reason: 'another account' };
|
|
992
|
+
else if (email === undefined) {
|
|
993
|
+
throw new UsageError(`No Codex login to sign up with: ${proof.message}`, 'shard signup --email you@example.com');
|
|
871
994
|
}
|
|
872
995
|
}
|
|
873
|
-
if (
|
|
874
|
-
|
|
875
|
-
project = home.project;
|
|
996
|
+
else if (email === undefined) {
|
|
997
|
+
throw new UsageError('--no-codex needs an email address.', 'shard signup --no-codex --email you@example.com');
|
|
876
998
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
throw new NotFoundError(`No project "${projectRef}" in ${org.name} (${org.slug}).`, 'list them: shard projects list');
|
|
900
|
-
project = hit;
|
|
901
|
-
}
|
|
902
|
-
else if (savedProject)
|
|
903
|
-
project = savedProject;
|
|
904
|
-
else if (projects.length === 1)
|
|
905
|
-
project = projects[0];
|
|
906
|
-
else if (projects.length === 0) {
|
|
907
|
-
project = await account.projects.create(org.id, { name: str(values['project-name'])?.trim() || 'Default' });
|
|
908
|
-
created.project = true;
|
|
999
|
+
let latest = null;
|
|
1000
|
+
const { account, result } = await ShardfluxAccount.signup({
|
|
1001
|
+
...ctx.accountOptions(),
|
|
1002
|
+
...(idToken !== null ? { codexIdToken: idToken } : { email: email }),
|
|
1003
|
+
...(name ? { displayName: name } : {}),
|
|
1004
|
+
onSessionToken: (s) => {
|
|
1005
|
+
ctx.addSecret(s.token);
|
|
1006
|
+
latest = s;
|
|
1007
|
+
},
|
|
1008
|
+
});
|
|
1009
|
+
const replaced = ctx.store.profile(ctx.apiUrl).session?.token ?? null;
|
|
1010
|
+
const expiresAt = () => latest?.expiresAt || result.session_expires_at || null;
|
|
1011
|
+
let user = result.user;
|
|
1012
|
+
if (result.status === 'mfa_required') {
|
|
1013
|
+
let factor = factorFlags(values);
|
|
1014
|
+
if (factor === null && ctx.input.interactive)
|
|
1015
|
+
factor = factorOf(await ctx.input.answer('Authenticator code (or a recovery code): '));
|
|
1016
|
+
if (factor === null) {
|
|
1017
|
+
ctx.saveSession({ token: account.sessionToken ?? '', expires_at: expiresAt(), user: null, state: 'pending_mfa' });
|
|
1018
|
+
if (replaced && replaced !== account.sessionToken)
|
|
1019
|
+
await endQuietly(ctx, replaced);
|
|
1020
|
+
throw new MfaPendingError(`Your Codex login matches an account with two-factor authentication on, so the sign-in waits for your code (10 minutes). The pending session is saved in ${ctx.store.file}.`, 'shard auth mfa --code <code from your authenticator app>');
|
|
909
1021
|
}
|
|
910
|
-
|
|
911
|
-
|
|
1022
|
+
user = (await account.auth.completeMfa(factor)).user;
|
|
1023
|
+
}
|
|
1024
|
+
if (!user)
|
|
1025
|
+
throw new UsageError('the API signed you up without a user; run shard auth status');
|
|
1026
|
+
const saved = { token: account.sessionToken ?? '', expires_at: expiresAt(), user: { id: user.id, email: user.email }, state: 'active' };
|
|
1027
|
+
const { switched } = ctx.saveSession(saved, user.email);
|
|
1028
|
+
if (replaced && replaced !== saved.token)
|
|
1029
|
+
await endQuietly(ctx, replaced);
|
|
1030
|
+
if (switched && !ctx.json)
|
|
1031
|
+
ctx.io.stderr.write(SWITCHED_NOTE);
|
|
1032
|
+
if (codexNote && !ctx.json)
|
|
1033
|
+
ctx.io.stderr.write(codexNote);
|
|
1034
|
+
const access = result.access;
|
|
1035
|
+
const how = access === 'verified'
|
|
1036
|
+
? idToken !== null
|
|
1037
|
+
? 'email verified by your Codex login'
|
|
1038
|
+
: 'email verified'
|
|
1039
|
+
: access === 'provisional'
|
|
1040
|
+
? 'email not verified yet: workspaces run on the Free plan now; verify to start the trial: shard verify'
|
|
1041
|
+
: 'email not verified yet: verify it to run workspaces: shard verify';
|
|
1042
|
+
let text = `${result.created ? 'Signed up' : 'Signed in'} as ${userLine(user)} (${how}).\nsession: saved in ${ctx.store.file}\n`;
|
|
1043
|
+
const data = { status: 'authenticated', created: result.created, access, user, method: idToken !== null ? 'codex' : 'email', credentials_file: ctx.store.file };
|
|
1044
|
+
if (access !== 'verification_required' && values.setup !== false) {
|
|
1045
|
+
const setup = await runSetup(ctx, values, account);
|
|
1046
|
+
text += setup.text;
|
|
1047
|
+
data.setup = setup.data;
|
|
912
1048
|
}
|
|
913
|
-
const keep = keyCheck?.ok === true && profile.api_key?.project_id === project.id;
|
|
914
|
-
let key;
|
|
915
|
-
let permissions = tools;
|
|
916
|
-
if (keep)
|
|
917
|
-
key = profile.api_key;
|
|
918
1049
|
else {
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
key = storedKey(res.api_key, res.secret);
|
|
923
|
-
permissions = res.api_key.tool_permissions;
|
|
924
|
-
created.api_key = true;
|
|
1050
|
+
const next = access === 'verification_required' ? 'shard verify' : 'shard setup';
|
|
1051
|
+
text += `next: ${next}\n`;
|
|
1052
|
+
data.next = next;
|
|
925
1053
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
if (
|
|
933
|
-
throw new UsageError(
|
|
934
|
-
if (
|
|
935
|
-
const
|
|
936
|
-
if (
|
|
937
|
-
|
|
1054
|
+
out(ctx, text, data);
|
|
1055
|
+
return 0;
|
|
1056
|
+
},
|
|
1057
|
+
async verify(ctx, _values, [code]) {
|
|
1058
|
+
const account = ctx.account();
|
|
1059
|
+
let digits = code?.replace(/\s+/g, '');
|
|
1060
|
+
if (digits !== undefined && !/^[0-9]{6}$/.test(digits))
|
|
1061
|
+
throw new UsageError('the code is the 6 digits from the email', 'shard verify 123456');
|
|
1062
|
+
if (digits === undefined) {
|
|
1063
|
+
const sent = await account.auth.requestEmailCode();
|
|
1064
|
+
if (sent.status === 'verified') {
|
|
1065
|
+
out(ctx, 'Your email is already verified.\n', { status: 'verified' });
|
|
1066
|
+
return 0;
|
|
1067
|
+
}
|
|
1068
|
+
if (!ctx.input.interactive) {
|
|
1069
|
+
out(ctx, 'A 6-digit code is on its way to your inbox (valid 15 minutes). Then: shard verify <code>\n', { status: 'accepted', next: 'shard verify <code>' });
|
|
1070
|
+
return 0;
|
|
1071
|
+
}
|
|
1072
|
+
ctx.io.stderr.write('A 6-digit code is on its way to your inbox (valid 15 minutes).\n');
|
|
1073
|
+
digits = ((await ctx.input.answer('Code from the email: ')) ?? '').replace(/\s+/g, '');
|
|
1074
|
+
if (!/^[0-9]{6}$/.test(digits))
|
|
1075
|
+
throw new UsageError('the code is the 6 digits from the email', 'shard verify <code>');
|
|
938
1076
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
out(ctx, (keep ? 'Already set up; the saved API key works.\n' : 'Set up.\n') +
|
|
942
|
-
kv([
|
|
943
|
-
['organization', `${org.name} (${org.slug}, ${org.id}) ${tag(created.organization)}`],
|
|
944
|
-
['project', `${project.name} (${project.slug}, ${project.id}) ${tag(created.project)}`],
|
|
945
|
-
['api key', `"${key.name}" ${key.key_id} (tools: ${toolsText(permissions)}) ${keep ? 'saved earlier' : 'created'}, saved in ${ctx.store.file}`],
|
|
946
|
-
]) +
|
|
947
|
-
envNote +
|
|
948
|
-
`next: ${NEXT_WORKSPACE}\n`, {
|
|
949
|
-
organization: org,
|
|
950
|
-
project,
|
|
951
|
-
api_key: { id: key.id, key_id: key.key_id, name: key.name, project_id: key.project_id, tool_permissions: permissions },
|
|
952
|
-
created,
|
|
953
|
-
credentials_file: ctx.store.file,
|
|
954
|
-
next: NEXT_WORKSPACE,
|
|
955
|
-
});
|
|
1077
|
+
await account.auth.verifyEmailCode(digits);
|
|
1078
|
+
out(ctx, 'Email verified. Your session and API keys keep working; a new account gets its trial now.\n', { status: 'verified' });
|
|
956
1079
|
return 0;
|
|
957
1080
|
},
|
|
958
1081
|
env(ctx, values) {
|
package/dist/args.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.
|
|
1
|
+
export declare const CLI_VERSION = "0.10.0";
|
|
2
2
|
export interface OptionSpec {
|
|
3
3
|
type: 'string' | 'boolean';
|
|
4
4
|
short?: string;
|
|
@@ -29,7 +29,6 @@ export interface CommandSpec {
|
|
|
29
29
|
export declare const GLOBAL_OPTIONS: Record<string, OptionSpec>;
|
|
30
30
|
/** Every command: the account plane first (sign in, set up), then the project-key commands. */
|
|
31
31
|
export declare const COMMANDS: CommandSpec[];
|
|
32
|
-
/** Canonical command words: aliases resolved, `workspaces files|operations|executions` folded to the top-level group. */
|
|
33
32
|
export declare function normalizeWords(words: string[]): string[];
|
|
34
33
|
export declare function findCommand(words: string[]): CommandSpec | undefined;
|
|
35
34
|
/** "90s", "5m", "1.5h", "250ms"; a bare number is seconds. */
|