@nestica/cli 0.2.0 → 0.4.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/README.md +7 -3
- package/dist/cli.js +178 -86
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Firebase tokens and the default profile. Precedence for every setting:
|
|
|
32
32
|
| Setting | Flag | Environment | Default |
|
|
33
33
|
| --- | --- | --- | --- |
|
|
34
34
|
| API base URL | `-H, --base-url` | `NESTICA_BASE_URL` | deployed API |
|
|
35
|
-
| Firebase web API key | `--api-key` | `FIREBASE_API_KEY` |
|
|
35
|
+
| Firebase web API key | `--api-key` | `FIREBASE_API_KEY` | the Nestica project's web key |
|
|
36
36
|
| Google OAuth client id | `--google-client-id` | `GOOGLE_CLIENT_ID` | — |
|
|
37
37
|
| Google OAuth client secret | `--google-client-secret` | `GOOGLE_CLIENT_SECRET` | — |
|
|
38
38
|
| Auth emulator | — | `FIREBASE_AUTH_EMULATOR_HOST` | inferred from a localhost base URL |
|
|
@@ -68,12 +68,16 @@ Global flags: `-H/--base-url`, `--api-key`, `--profile`,
|
|
|
68
68
|
### login
|
|
69
69
|
|
|
70
70
|
- Email/password (prompted when omitted; the password is not echoed):
|
|
71
|
-
`nestica login --
|
|
71
|
+
`nestica login --email dev@example.com` (the Firebase web API key
|
|
72
|
+
defaults to the Nestica project's own key)
|
|
72
73
|
- Google, in the browser (OAuth + PKCE against a loopback redirect;
|
|
73
74
|
needs the Firebase project's web OAuth client id):
|
|
74
75
|
`nestica login --google --google-client-id <id> --api-key <key>`
|
|
75
76
|
|
|
76
|
-
`logout` deletes the session file.
|
|
77
|
+
`logout` deletes the session file. Running `login` while already
|
|
78
|
+
signed in just reports the current session — `logout` first to switch
|
|
79
|
+
accounts (ID tokens refresh automatically, so re-login is never needed
|
|
80
|
+
to renew them).
|
|
77
81
|
|
|
78
82
|
### set-profile
|
|
79
83
|
|
package/dist/cli.js
CHANGED
|
@@ -3380,7 +3380,7 @@ var program = new Command();
|
|
|
3380
3380
|
// package.json
|
|
3381
3381
|
var package_default = {
|
|
3382
3382
|
name: "@nestica/cli",
|
|
3383
|
-
version: "0.
|
|
3383
|
+
version: "0.4.0",
|
|
3384
3384
|
type: "module",
|
|
3385
3385
|
description: "Command-line client for the Nestica REST API",
|
|
3386
3386
|
license: "MIT",
|
|
@@ -3513,6 +3513,7 @@ import { chmodSync, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileS
|
|
|
3513
3513
|
import { homedir } from "node:os";
|
|
3514
3514
|
import { dirname, join } from "node:path";
|
|
3515
3515
|
var DEFAULT_BASE_URL = "https://api-tjnuhxmzkq-uc.a.run.app/api/v1.0";
|
|
3516
|
+
var DEFAULT_FIREBASE_API_KEY = "AIzaSyCFltgXUHKKNtFt1QbsOhHZeP8ug_tr4t8";
|
|
3516
3517
|
function sessionPath() {
|
|
3517
3518
|
const override = process.env["NESTICA_CONFIG"];
|
|
3518
3519
|
if (override) {
|
|
@@ -3545,7 +3546,7 @@ function removeSession(path2 = sessionPath()) {
|
|
|
3545
3546
|
function resolveConnection(overrides, session) {
|
|
3546
3547
|
return {
|
|
3547
3548
|
baseUrl: overrides.baseUrl ?? process.env["NESTICA_BASE_URL"] ?? session.baseUrl ?? DEFAULT_BASE_URL,
|
|
3548
|
-
apiKey: overrides.apiKey ?? process.env["FIREBASE_API_KEY"] ?? session.apiKey,
|
|
3549
|
+
apiKey: overrides.apiKey ?? process.env["FIREBASE_API_KEY"] ?? session.apiKey ?? DEFAULT_FIREBASE_API_KEY,
|
|
3549
3550
|
profileId: overrides.profile ?? session.defaultProfileId
|
|
3550
3551
|
};
|
|
3551
3552
|
}
|
|
@@ -3787,40 +3788,148 @@ ${url2}`
|
|
|
3787
3788
|
}
|
|
3788
3789
|
}
|
|
3789
3790
|
|
|
3790
|
-
// src/
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3791
|
+
// src/output/colors.ts
|
|
3792
|
+
var IDENTITY_COLORS = {
|
|
3793
|
+
dim: (text) => text,
|
|
3794
|
+
bold: (text) => text,
|
|
3795
|
+
cyan: (text) => text,
|
|
3796
|
+
green: (text) => text,
|
|
3797
|
+
yellow: (text) => text,
|
|
3798
|
+
red: (text) => text,
|
|
3799
|
+
magenta: (text) => text
|
|
3800
|
+
};
|
|
3801
|
+
function wrap(code) {
|
|
3802
|
+
return (text) => `\x1B[${code}m${text}\x1B[0m`;
|
|
3803
|
+
}
|
|
3804
|
+
var ANSI_COLORS = {
|
|
3805
|
+
dim: wrap("2"),
|
|
3806
|
+
bold: wrap("1"),
|
|
3807
|
+
cyan: wrap("36"),
|
|
3808
|
+
green: wrap("32"),
|
|
3809
|
+
yellow: wrap("33"),
|
|
3810
|
+
red: wrap("31"),
|
|
3811
|
+
magenta: wrap("35")
|
|
3812
|
+
};
|
|
3813
|
+
function pickColors(options) {
|
|
3814
|
+
const stream = options.stream ?? process.stdout;
|
|
3815
|
+
if (options.noColor || process.env["NO_COLOR"] !== void 0 || !stream.isTTY) {
|
|
3816
|
+
return IDENTITY_COLORS;
|
|
3801
3817
|
}
|
|
3818
|
+
return ANSI_COLORS;
|
|
3802
3819
|
}
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3820
|
+
|
|
3821
|
+
// src/commands/prompt.ts
|
|
3822
|
+
import { createInterface, emitKeypressEvents } from "node:readline";
|
|
3823
|
+
var lines = [];
|
|
3824
|
+
var reader;
|
|
3825
|
+
var closed = false;
|
|
3826
|
+
var resolveLine;
|
|
3827
|
+
function ensureReader() {
|
|
3828
|
+
if (reader || closed) {
|
|
3829
|
+
return;
|
|
3830
|
+
}
|
|
3831
|
+
reader = createInterface({ input: process.stdin, output: process.stdout });
|
|
3832
|
+
reader.on("line", (line) => {
|
|
3833
|
+
lines.push(line);
|
|
3834
|
+
resolveLine?.();
|
|
3835
|
+
resolveLine = void 0;
|
|
3807
3836
|
});
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3837
|
+
reader.on("close", () => {
|
|
3838
|
+
closed = true;
|
|
3839
|
+
resolveLine?.();
|
|
3840
|
+
resolveLine = void 0;
|
|
3841
|
+
});
|
|
3842
|
+
}
|
|
3843
|
+
async function nextLine() {
|
|
3844
|
+
ensureReader();
|
|
3845
|
+
if (lines.length > 0) {
|
|
3846
|
+
return lines.shift() ?? "";
|
|
3847
|
+
}
|
|
3848
|
+
if (closed) {
|
|
3849
|
+
return "";
|
|
3817
3850
|
}
|
|
3851
|
+
await new Promise((resolve) => {
|
|
3852
|
+
resolveLine = resolve;
|
|
3853
|
+
});
|
|
3854
|
+
return lines.shift() ?? "";
|
|
3855
|
+
}
|
|
3856
|
+
function closePrompts() {
|
|
3857
|
+
reader?.close();
|
|
3858
|
+
reader = void 0;
|
|
3859
|
+
closed = false;
|
|
3860
|
+
}
|
|
3861
|
+
async function prompt(label2) {
|
|
3862
|
+
process.stdout.write(`${label2} `);
|
|
3863
|
+
return (await nextLine()).trim();
|
|
3864
|
+
}
|
|
3865
|
+
async function promptHidden(label2) {
|
|
3866
|
+
process.stdout.write(`${label2} `);
|
|
3867
|
+
if (!process.stdin.isTTY) {
|
|
3868
|
+
const answer = await nextLine();
|
|
3869
|
+
return answer.replace(/\r$/, "");
|
|
3870
|
+
}
|
|
3871
|
+
if (lines.length > 0) {
|
|
3872
|
+
return lines.shift()?.replace(/\r$/, "") ?? "";
|
|
3873
|
+
}
|
|
3874
|
+
closePrompts();
|
|
3875
|
+
const input = process.stdin;
|
|
3876
|
+
return new Promise((resolve) => {
|
|
3877
|
+
let value = "";
|
|
3878
|
+
const onKey = (ch, key) => {
|
|
3879
|
+
if (key.ctrl && (key.name === "c" || key.name === "d")) {
|
|
3880
|
+
restore();
|
|
3881
|
+
process.stdout.write("\n");
|
|
3882
|
+
process.kill(process.pid, "SIGINT");
|
|
3883
|
+
return;
|
|
3884
|
+
}
|
|
3885
|
+
if (key.name === "return" || key.name === "enter") {
|
|
3886
|
+
restore();
|
|
3887
|
+
process.stdout.write("\n");
|
|
3888
|
+
resolve(value);
|
|
3889
|
+
return;
|
|
3890
|
+
}
|
|
3891
|
+
if (key.name === "backspace") {
|
|
3892
|
+
value = value.slice(0, -1);
|
|
3893
|
+
process.stdout.write("\b \b");
|
|
3894
|
+
return;
|
|
3895
|
+
}
|
|
3896
|
+
if (typeof ch === "string" && ch.length === 1 && !key.ctrl && !key.meta) {
|
|
3897
|
+
value += ch;
|
|
3898
|
+
process.stdout.write("*");
|
|
3899
|
+
}
|
|
3900
|
+
};
|
|
3901
|
+
const restore = () => {
|
|
3902
|
+
input.removeListener("keypress", onKey);
|
|
3903
|
+
input.removeListener("end", onEnd);
|
|
3904
|
+
input.pause();
|
|
3905
|
+
if (input.isRaw) {
|
|
3906
|
+
input.setRawMode(false);
|
|
3907
|
+
}
|
|
3908
|
+
};
|
|
3909
|
+
const onEnd = () => {
|
|
3910
|
+
restore();
|
|
3911
|
+
process.stdout.write("\n");
|
|
3912
|
+
resolve(value);
|
|
3913
|
+
};
|
|
3914
|
+
emitKeypressEvents(input);
|
|
3915
|
+
input.on("keypress", onKey);
|
|
3916
|
+
input.once("end", onEnd);
|
|
3917
|
+
input.setRawMode(true);
|
|
3918
|
+
input.resume();
|
|
3919
|
+
});
|
|
3818
3920
|
}
|
|
3819
3921
|
|
|
3820
3922
|
// src/commands/login.ts
|
|
3821
3923
|
var EXPIRY_MARGIN_MS = 6e4;
|
|
3822
3924
|
async function loginAction(options) {
|
|
3823
3925
|
const session = loadSession();
|
|
3926
|
+
if (session.idToken) {
|
|
3927
|
+
const identity = session.email ?? session.uid;
|
|
3928
|
+
console.log(`Session: ${sessionPath()}`);
|
|
3929
|
+
console.log("Run `nestica logout` first to change account.");
|
|
3930
|
+
console.log(pickColors({}).green(`Already logged in as ${identity}.`));
|
|
3931
|
+
return;
|
|
3932
|
+
}
|
|
3824
3933
|
const connection = resolveConnection(
|
|
3825
3934
|
{ baseUrl: options.baseUrl, apiKey: options.apiKey },
|
|
3826
3935
|
session
|
|
@@ -3831,21 +3940,27 @@ async function loginAction(options) {
|
|
|
3831
3940
|
);
|
|
3832
3941
|
}
|
|
3833
3942
|
const result = options.google ? await googleLogin(connection.apiKey, connection.baseUrl, options, session) : await passwordLogin(connection.baseUrl, connection.apiKey, options);
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3943
|
+
const path2 = sessionPath();
|
|
3944
|
+
saveSession(
|
|
3945
|
+
{
|
|
3946
|
+
...session,
|
|
3947
|
+
baseUrl: connection.baseUrl,
|
|
3948
|
+
apiKey: connection.apiKey,
|
|
3949
|
+
email: result.email ?? session.email,
|
|
3950
|
+
uid: result.uid,
|
|
3951
|
+
idToken: result.idToken,
|
|
3952
|
+
refreshToken: result.refreshToken,
|
|
3953
|
+
expiresAt: Date.now() + result.expiresIn * 1e3 - EXPIRY_MARGIN_MS
|
|
3954
|
+
},
|
|
3955
|
+
path2
|
|
3956
|
+
);
|
|
3957
|
+
console.log(`Session saved to ${path2}`);
|
|
3958
|
+
console.log(pickColors({}).green(`Logged in as ${result.email ?? result.uid}.`));
|
|
3845
3959
|
}
|
|
3846
3960
|
async function passwordLogin(baseUrl, apiKey, options) {
|
|
3847
|
-
const email3 = options.email ?? await prompt("Email:
|
|
3848
|
-
const password = options.password ?? await promptHidden("Password:
|
|
3961
|
+
const email3 = options.email ?? await prompt("Email:");
|
|
3962
|
+
const password = options.password ?? await promptHidden("Password:");
|
|
3963
|
+
closePrompts();
|
|
3849
3964
|
if (!email3 || !password) {
|
|
3850
3965
|
throw new Error("Email and password are required.");
|
|
3851
3966
|
}
|
|
@@ -3864,10 +3979,16 @@ async function googleLogin(apiKey, baseUrl, options, session) {
|
|
|
3864
3979
|
|
|
3865
3980
|
// src/commands/logout.ts
|
|
3866
3981
|
function logoutAction() {
|
|
3982
|
+
const session = loadSession();
|
|
3867
3983
|
if (removeSession()) {
|
|
3868
|
-
|
|
3984
|
+
const identity = session.email ?? session.uid;
|
|
3985
|
+
console.log(`Session removed: ${sessionPath()}`);
|
|
3986
|
+
console.log(
|
|
3987
|
+
pickColors({}).green(identity ? `Logged out as ${identity}.` : "Logged out.")
|
|
3988
|
+
);
|
|
3869
3989
|
} else {
|
|
3870
|
-
console.log(
|
|
3990
|
+
console.log(`(no session file at ${sessionPath()})`);
|
|
3991
|
+
console.log(pickColors({}).green("Not logged in."));
|
|
3871
3992
|
}
|
|
3872
3993
|
}
|
|
3873
3994
|
|
|
@@ -3885,7 +4006,7 @@ function withComma(block) {
|
|
|
3885
4006
|
const last = block.pop() ?? "";
|
|
3886
4007
|
return [...block, `${last},`];
|
|
3887
4008
|
}
|
|
3888
|
-
function
|
|
4009
|
+
function lines2(value, colors, depth) {
|
|
3889
4010
|
const pad = " ".repeat(depth);
|
|
3890
4011
|
const inner = " ".repeat(depth + 1);
|
|
3891
4012
|
if (value === null) {
|
|
@@ -3905,7 +4026,7 @@ function lines(value, colors, depth) {
|
|
|
3905
4026
|
}
|
|
3906
4027
|
const out2 = [pad + colors.dim("[")];
|
|
3907
4028
|
value.forEach((item, index) => {
|
|
3908
|
-
const block =
|
|
4029
|
+
const block = lines2(item, colors, depth + 1);
|
|
3909
4030
|
out2.push(...index < value.length - 1 ? withComma(block) : block);
|
|
3910
4031
|
});
|
|
3911
4032
|
out2.push(pad + colors.dim("]"));
|
|
@@ -3917,7 +4038,7 @@ function lines(value, colors, depth) {
|
|
|
3917
4038
|
}
|
|
3918
4039
|
const out = [pad + colors.dim("{")];
|
|
3919
4040
|
entries.forEach(([key, val], index) => {
|
|
3920
|
-
const block =
|
|
4041
|
+
const block = lines2(val, colors, depth + 1);
|
|
3921
4042
|
const first = block[0] ?? "";
|
|
3922
4043
|
block[0] = `${inner}${colors.cyan(JSON.stringify(key))}: ${first.slice(inner.length)}`;
|
|
3923
4044
|
out.push(...index < entries.length - 1 ? withComma(block) : block);
|
|
@@ -3926,7 +4047,7 @@ function lines(value, colors, depth) {
|
|
|
3926
4047
|
return out;
|
|
3927
4048
|
}
|
|
3928
4049
|
function prettyJson(value, colors) {
|
|
3929
|
-
return
|
|
4050
|
+
return lines2(value, colors, 0).join("\n");
|
|
3930
4051
|
}
|
|
3931
4052
|
function compactJson(value) {
|
|
3932
4053
|
return JSON.stringify(value);
|
|
@@ -4121,8 +4242,9 @@ async function pickProfile(ctx) {
|
|
|
4121
4242
|
items.forEach((profile, index2) => {
|
|
4122
4243
|
console.log(`${index2 + 1}. ${profile.name ?? profile.id}`);
|
|
4123
4244
|
});
|
|
4124
|
-
const answer = await prompt("Select a profile [1]:
|
|
4125
|
-
|
|
4245
|
+
const answer = await prompt("Select a profile [1]:");
|
|
4246
|
+
closePrompts();
|
|
4247
|
+
const index = answer === "" ? 0 : Number(answer) - 1;
|
|
4126
4248
|
const chosen = items[index];
|
|
4127
4249
|
if (!Number.isInteger(index) || index < 0 || chosen === void 0) {
|
|
4128
4250
|
throw new Error(`Pick a number between 1 and ${items.length}.`);
|
|
@@ -4256,36 +4378,6 @@ function makeClient(deps) {
|
|
|
4256
4378
|
};
|
|
4257
4379
|
}
|
|
4258
4380
|
|
|
4259
|
-
// src/output/colors.ts
|
|
4260
|
-
var IDENTITY_COLORS = {
|
|
4261
|
-
dim: (text) => text,
|
|
4262
|
-
bold: (text) => text,
|
|
4263
|
-
cyan: (text) => text,
|
|
4264
|
-
green: (text) => text,
|
|
4265
|
-
yellow: (text) => text,
|
|
4266
|
-
red: (text) => text,
|
|
4267
|
-
magenta: (text) => text
|
|
4268
|
-
};
|
|
4269
|
-
function wrap(code) {
|
|
4270
|
-
return (text) => `\x1B[${code}m${text}\x1B[0m`;
|
|
4271
|
-
}
|
|
4272
|
-
var ANSI_COLORS = {
|
|
4273
|
-
dim: wrap("2"),
|
|
4274
|
-
bold: wrap("1"),
|
|
4275
|
-
cyan: wrap("36"),
|
|
4276
|
-
green: wrap("32"),
|
|
4277
|
-
yellow: wrap("33"),
|
|
4278
|
-
red: wrap("31"),
|
|
4279
|
-
magenta: wrap("35")
|
|
4280
|
-
};
|
|
4281
|
-
function pickColors(options) {
|
|
4282
|
-
const stream = options.stream ?? process.stdout;
|
|
4283
|
-
if (options.noColor || process.env["NO_COLOR"] !== void 0 || !stream.isTTY) {
|
|
4284
|
-
return IDENTITY_COLORS;
|
|
4285
|
-
}
|
|
4286
|
-
return ANSI_COLORS;
|
|
4287
|
-
}
|
|
4288
|
-
|
|
4289
4381
|
// ../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
4290
4382
|
var external_exports = {};
|
|
4291
4383
|
__export(external_exports, {
|
|
@@ -5710,14 +5802,14 @@ function toDotPath(_path) {
|
|
|
5710
5802
|
return segs.join("");
|
|
5711
5803
|
}
|
|
5712
5804
|
function prettifyError(error51) {
|
|
5713
|
-
const
|
|
5805
|
+
const lines3 = [];
|
|
5714
5806
|
const issues = [...error51.issues].sort((a, b) => (a.path ?? []).length - (b.path ?? []).length);
|
|
5715
5807
|
for (const issue2 of issues) {
|
|
5716
|
-
|
|
5808
|
+
lines3.push(`\u2716 ${issue2.message}`);
|
|
5717
5809
|
if (issue2.path?.length)
|
|
5718
|
-
|
|
5810
|
+
lines3.push(` \u2192 at ${toDotPath(issue2.path)}`);
|
|
5719
5811
|
}
|
|
5720
|
-
return
|
|
5812
|
+
return lines3.join("\n");
|
|
5721
5813
|
}
|
|
5722
5814
|
|
|
5723
5815
|
// ../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/parse.js
|
|
@@ -6535,9 +6627,9 @@ var Doc = class {
|
|
|
6535
6627
|
return;
|
|
6536
6628
|
}
|
|
6537
6629
|
const content = arg;
|
|
6538
|
-
const
|
|
6539
|
-
const minIndent = Math.min(...
|
|
6540
|
-
const dedented =
|
|
6630
|
+
const lines3 = content.split("\n").filter((x) => x);
|
|
6631
|
+
const minIndent = Math.min(...lines3.map((x) => x.length - x.trimStart().length));
|
|
6632
|
+
const dedented = lines3.map((x) => x.slice(minIndent)).map((x) => " ".repeat(this.indent * 2) + x);
|
|
6541
6633
|
for (const line of dedented) {
|
|
6542
6634
|
this.content.push(line);
|
|
6543
6635
|
}
|
|
@@ -6546,8 +6638,8 @@ var Doc = class {
|
|
|
6546
6638
|
const F = Function;
|
|
6547
6639
|
const args = this?.args;
|
|
6548
6640
|
const content = this?.content ?? [``];
|
|
6549
|
-
const
|
|
6550
|
-
return new F(...args,
|
|
6641
|
+
const lines3 = [...content.map((x) => ` ${x}`)];
|
|
6642
|
+
return new F(...args, lines3.join("\n"));
|
|
6551
6643
|
}
|
|
6552
6644
|
};
|
|
6553
6645
|
|