gigabuddy 0.0.1
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 +61 -0
- package/bin/cli.js +431 -0
- package/index.js +342 -0
- package/package.json +27 -0
- package/src/bin/cli.d.ts +2 -0
- package/src/index.d.ts +5 -0
- package/src/lib/commands.d.ts +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# gigabuddy
|
|
2
|
+
|
|
3
|
+
The Gigabuddy CLI. Sign in, and install `@gigabuddy` packages as yourself.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g gigabuddy # or: npx gigabuddy login --npm
|
|
7
|
+
|
|
8
|
+
gigabuddy login --npm
|
|
9
|
+
npm install @gigabuddy/app-kit
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
That's the whole onboarding. `login --npm` signs you in if you haven't already,
|
|
13
|
+
so there is no two-step dance.
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
| Command | What it does |
|
|
18
|
+
| ----------------------- | --------------------------------------------------------- |
|
|
19
|
+
| `gigabuddy login` | Sign in (device flow — opens your browser) |
|
|
20
|
+
| `gigabuddy login --npm` | Set up `~/.npmrc` to install `@gigabuddy` packages as you |
|
|
21
|
+
| `gigabuddy status` | Who you're signed in as |
|
|
22
|
+
| `gigabuddy logout` | Sign out (`--npm` clears only the npm credential) |
|
|
23
|
+
|
|
24
|
+
`--staging` targets the staging environment; its credentials are stored
|
|
25
|
+
separately, so a staging login never becomes your production one.
|
|
26
|
+
|
|
27
|
+
## What lands on disk
|
|
28
|
+
|
|
29
|
+
`login` stores a **refresh token** in `~/.gigabuddy/credentials.json` (0600).
|
|
30
|
+
Platform tokens are minted from it on demand and live an hour, so nothing
|
|
31
|
+
long-lived and fully-authoritative sits on your filesystem.
|
|
32
|
+
|
|
33
|
+
`login --npm` adds two lines to `~/.npmrc` (0600), leaving everything else in
|
|
34
|
+
that file exactly where it was:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
@gigabuddy:registry=https://npm.gigabuddy.com/
|
|
38
|
+
//npm.gigabuddy.com/:_authToken=<registry credential>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**That credential is registry-scoped.** It can install what you can install and
|
|
42
|
+
nothing else on the platform — because `.npmrc` is a file that ends up in CI
|
|
43
|
+
logs and is readable by every postinstall script that runs on your machine. It
|
|
44
|
+
lives 30 days (npm has nowhere to put a token refresh), and it dies with your
|
|
45
|
+
session: signing out, or an admin revoking the session, stops installs within
|
|
46
|
+
about a minute. Re-run `gigabuddy login --npm` to get a fresh one.
|
|
47
|
+
|
|
48
|
+
Which packages you can install is decided per install, against your own access —
|
|
49
|
+
the credential only proves who you are.
|
|
50
|
+
|
|
51
|
+
## Scope
|
|
52
|
+
|
|
53
|
+
This CLI does authentication and local setup, and nothing else. Gigabuddy's
|
|
54
|
+
capabilities live behind hosted APIs; this is how you prove who you are to them.
|
|
55
|
+
|
|
56
|
+
Requires Node 20+. No dependencies.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
Internal notes for maintainers live in `NOTES.md` in the source repo — they are
|
|
61
|
+
deliberately not shipped in this package.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// libs/gigabuddy/src/bin/cli.ts
|
|
4
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
5
|
+
import { dirname, join as join3 } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { parseArgs } from "node:util";
|
|
8
|
+
|
|
9
|
+
// libs/client-core/dist/lib/credentials.js
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as os from "node:os";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
var CONFIG_DIR = path.join(os.homedir(), ".gigabuddy");
|
|
14
|
+
var USER_CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
|
|
15
|
+
var USER_STAGING_CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.staging.json");
|
|
16
|
+
function getUserCredentialsPath(staging = false) {
|
|
17
|
+
return staging ? USER_STAGING_CREDENTIALS_FILE : USER_CREDENTIALS_FILE;
|
|
18
|
+
}
|
|
19
|
+
function loadUserCredentials(staging = false) {
|
|
20
|
+
try {
|
|
21
|
+
const content = fs.readFileSync(getUserCredentialsPath(staging), "utf-8");
|
|
22
|
+
const raw = JSON.parse(content);
|
|
23
|
+
return raw.refreshToken ? raw : null;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function saveUserCredentials(credentials, staging = false) {
|
|
29
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
30
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
31
|
+
}
|
|
32
|
+
writePrivateFile(getUserCredentialsPath(staging), JSON.stringify(credentials, null, 2));
|
|
33
|
+
}
|
|
34
|
+
function clearUserCredentials(staging = false) {
|
|
35
|
+
const file = getUserCredentialsPath(staging);
|
|
36
|
+
if (fs.existsSync(file)) {
|
|
37
|
+
fs.unlinkSync(file);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function writePrivateFile(file, contents) {
|
|
41
|
+
fs.writeFileSync(file, contents, { mode: 384 });
|
|
42
|
+
try {
|
|
43
|
+
fs.chmodSync(file, 384);
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// libs/client-core/dist/lib/device-flow.js
|
|
49
|
+
async function openBrowser(url) {
|
|
50
|
+
const { exec } = await import("node:child_process");
|
|
51
|
+
const { promisify } = await import("node:util");
|
|
52
|
+
const execAsync = promisify(exec);
|
|
53
|
+
const platform = process.platform;
|
|
54
|
+
const command = platform === "darwin" ? `open "${url}"` : platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
55
|
+
await execAsync(command);
|
|
56
|
+
}
|
|
57
|
+
function sleep(ms) {
|
|
58
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
59
|
+
}
|
|
60
|
+
async function deviceLogin(options) {
|
|
61
|
+
const { authUrl } = options;
|
|
62
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
63
|
+
const startRes = await fetchImpl(`${authUrl}/api/auth/device/start`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: { "Content-Type": "application/json" },
|
|
66
|
+
body: JSON.stringify({
|
|
67
|
+
clientId: options.clientId ?? "gigabuddy-cli",
|
|
68
|
+
productId: options.productId ?? "gigabuddy/studio"
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
if (!startRes.ok) {
|
|
72
|
+
throw new Error(`Failed to start device flow: ${await startRes.text()}`);
|
|
73
|
+
}
|
|
74
|
+
const { deviceCode, userCode, verificationUrl, expiresIn, interval } = await startRes.json();
|
|
75
|
+
options.onPrompt?.({ verificationUrl, userCode });
|
|
76
|
+
(options.openBrowserImpl ?? openBrowser)(verificationUrl).catch(() => void 0);
|
|
77
|
+
const deadline = Date.now() + expiresIn * 1e3;
|
|
78
|
+
const pollInterval = (interval || 5) * 1e3;
|
|
79
|
+
while (Date.now() < deadline) {
|
|
80
|
+
await sleep(pollInterval);
|
|
81
|
+
const pollRes = await fetchImpl(`${authUrl}/api/auth/device/poll`, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: { "Content-Type": "application/json" },
|
|
84
|
+
body: JSON.stringify({ deviceCode })
|
|
85
|
+
});
|
|
86
|
+
if (pollRes.status === 429) {
|
|
87
|
+
await sleep(pollInterval);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!pollRes.ok)
|
|
91
|
+
continue;
|
|
92
|
+
const pollData = await pollRes.json();
|
|
93
|
+
if (pollData.status === "complete" && pollData.platformToken && pollData.refreshToken && pollData.userId) {
|
|
94
|
+
return {
|
|
95
|
+
refreshToken: pollData.refreshToken,
|
|
96
|
+
platformToken: pollData.platformToken,
|
|
97
|
+
userId: pollData.userId
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (pollData.status === "expired") {
|
|
101
|
+
throw new Error("Device code expired. Please try again.");
|
|
102
|
+
}
|
|
103
|
+
options.onPending?.();
|
|
104
|
+
}
|
|
105
|
+
throw new Error("Authentication timed out. Please try again.");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// libs/client-core/dist/lib/npmrc.js
|
|
109
|
+
function registryAuthKey(registryUrl) {
|
|
110
|
+
const url = new URL(registryUrl);
|
|
111
|
+
const path3 = url.pathname.replace(/\/+$/, "");
|
|
112
|
+
return `//${url.host}${path3}/:_authToken`;
|
|
113
|
+
}
|
|
114
|
+
function scopeRegistryKey(scope) {
|
|
115
|
+
return `${scope}:registry`;
|
|
116
|
+
}
|
|
117
|
+
function normalizedRegistryUrl(registryUrl) {
|
|
118
|
+
return registryUrl.replace(/\/+$/, "") + "/";
|
|
119
|
+
}
|
|
120
|
+
function upsertNpmrc(existing, entries) {
|
|
121
|
+
const registryUrl = normalizedRegistryUrl(entries.registryUrl);
|
|
122
|
+
const desired = /* @__PURE__ */ new Map([
|
|
123
|
+
[scopeRegistryKey(entries.scope).toLowerCase(), `${scopeRegistryKey(entries.scope)}=${registryUrl}`],
|
|
124
|
+
[registryAuthKey(registryUrl).toLowerCase(), `${registryAuthKey(registryUrl)}=${entries.token}`]
|
|
125
|
+
]);
|
|
126
|
+
const lines = existing.length > 0 ? existing.split("\n") : [];
|
|
127
|
+
const written = /* @__PURE__ */ new Set();
|
|
128
|
+
const merged = lines.map((line) => {
|
|
129
|
+
const key = lineKey(line);
|
|
130
|
+
if (key && desired.has(key) && !written.has(key)) {
|
|
131
|
+
written.add(key);
|
|
132
|
+
return desired.get(key);
|
|
133
|
+
}
|
|
134
|
+
return line;
|
|
135
|
+
});
|
|
136
|
+
const additions = [...desired.entries()].filter(([key]) => !written.has(key)).map(([, line]) => line);
|
|
137
|
+
if (additions.length === 0)
|
|
138
|
+
return merged.join("\n");
|
|
139
|
+
while (merged.length > 0 && merged[merged.length - 1].trim() === "")
|
|
140
|
+
merged.pop();
|
|
141
|
+
return [...merged, ...additions, ""].join("\n");
|
|
142
|
+
}
|
|
143
|
+
function removeNpmrcEntries(existing, entries) {
|
|
144
|
+
const registryUrl = normalizedRegistryUrl(entries.registryUrl);
|
|
145
|
+
const drop = /* @__PURE__ */ new Set([scopeRegistryKey(entries.scope).toLowerCase(), registryAuthKey(registryUrl).toLowerCase()]);
|
|
146
|
+
const kept = existing.split("\n").filter((line) => {
|
|
147
|
+
const key = lineKey(line);
|
|
148
|
+
return !(key && drop.has(key));
|
|
149
|
+
});
|
|
150
|
+
const text = kept.join("\n");
|
|
151
|
+
return text.trim().length === 0 ? "" : text;
|
|
152
|
+
}
|
|
153
|
+
function lineKey(line) {
|
|
154
|
+
const trimmed = line.trim();
|
|
155
|
+
if (trimmed.length === 0 || trimmed.startsWith("#") || trimmed.startsWith(";"))
|
|
156
|
+
return null;
|
|
157
|
+
const eq = trimmed.indexOf("=");
|
|
158
|
+
if (eq === -1)
|
|
159
|
+
return null;
|
|
160
|
+
return trimmed.slice(0, eq).trim().toLowerCase();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// libs/client-core/dist/lib/registry-credential.js
|
|
164
|
+
import * as fs2 from "node:fs";
|
|
165
|
+
import * as os2 from "node:os";
|
|
166
|
+
import * as path2 from "node:path";
|
|
167
|
+
var GIGABUDDY_NPM_SCOPE = "@gigabuddy";
|
|
168
|
+
async function mintRegistryCredential(options) {
|
|
169
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
170
|
+
const res = await fetchImpl(`${options.authUrl}/api/token/registry`, {
|
|
171
|
+
method: "POST",
|
|
172
|
+
headers: { "Content-Type": "application/json" },
|
|
173
|
+
body: JSON.stringify({ refreshToken: options.refreshToken })
|
|
174
|
+
});
|
|
175
|
+
if (!res.ok) {
|
|
176
|
+
throw new Error(`Could not mint a registry credential (${res.status}): ${await res.text()}`);
|
|
177
|
+
}
|
|
178
|
+
const data = await res.json();
|
|
179
|
+
if (!data.token || !data.registryUrl) {
|
|
180
|
+
throw new Error("Auth returned an incomplete registry credential");
|
|
181
|
+
}
|
|
182
|
+
return { token: data.token, expiresIn: data.expiresIn ?? 0, registryUrl: data.registryUrl };
|
|
183
|
+
}
|
|
184
|
+
function npmrcPath() {
|
|
185
|
+
return process.env["NPM_CONFIG_USERCONFIG"] ?? path2.join(os2.homedir(), ".npmrc");
|
|
186
|
+
}
|
|
187
|
+
function readNpmrc(file) {
|
|
188
|
+
try {
|
|
189
|
+
return fs2.readFileSync(file, "utf-8");
|
|
190
|
+
} catch {
|
|
191
|
+
return "";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function writeRegistryCredentialToNpmrc(credential, file = npmrcPath()) {
|
|
195
|
+
const updated = upsertNpmrc(readNpmrc(file), {
|
|
196
|
+
scope: GIGABUDDY_NPM_SCOPE,
|
|
197
|
+
registryUrl: credential.registryUrl,
|
|
198
|
+
token: credential.token
|
|
199
|
+
});
|
|
200
|
+
writePrivateFile(file, updated);
|
|
201
|
+
return file;
|
|
202
|
+
}
|
|
203
|
+
function removeRegistryCredentialFromNpmrc(registryUrl, file = npmrcPath()) {
|
|
204
|
+
const existing = readNpmrc(file);
|
|
205
|
+
const updated = removeNpmrcEntries(existing, { scope: GIGABUDDY_NPM_SCOPE, registryUrl });
|
|
206
|
+
if (updated === existing)
|
|
207
|
+
return null;
|
|
208
|
+
writePrivateFile(file, updated);
|
|
209
|
+
return file;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// libs/gigabuddy/src/lib/commands.ts
|
|
213
|
+
var PRODUCTION_AUTH_URL = "https://auth.gigabuddy.com";
|
|
214
|
+
var PRODUCTION_API_URL = "https://api.gigabuddy.com";
|
|
215
|
+
var STAGING_AUTH_URL = "https://auth.staging.gigabuddy.com";
|
|
216
|
+
var STAGING_API_URL = "https://api.staging.gigabuddy.com";
|
|
217
|
+
function resolveEnv(options) {
|
|
218
|
+
const staging = options.staging ?? false;
|
|
219
|
+
return {
|
|
220
|
+
staging,
|
|
221
|
+
authUrl: options.authUrl ?? (staging ? STAGING_AUTH_URL : PRODUCTION_AUTH_URL),
|
|
222
|
+
apiUrl: options.apiUrl ?? (staging ? STAGING_API_URL : PRODUCTION_API_URL)
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function say(line = "") {
|
|
226
|
+
console.error(line);
|
|
227
|
+
}
|
|
228
|
+
async function signIn(env) {
|
|
229
|
+
say(`Signing in to ${env.staging ? "Gigabuddy Staging" : "Gigabuddy"}\u2026`);
|
|
230
|
+
const result = await deviceLogin({
|
|
231
|
+
authUrl: env.authUrl,
|
|
232
|
+
apiUrl: env.apiUrl,
|
|
233
|
+
clientId: "gigabuddy-cli",
|
|
234
|
+
onPrompt: ({ verificationUrl, userCode }) => {
|
|
235
|
+
say();
|
|
236
|
+
say(` Open: ${verificationUrl}`);
|
|
237
|
+
say(` Code: ${userCode}`);
|
|
238
|
+
say();
|
|
239
|
+
},
|
|
240
|
+
onPending: () => process.stderr.write(".")
|
|
241
|
+
});
|
|
242
|
+
const credentials = {
|
|
243
|
+
refreshToken: result.refreshToken,
|
|
244
|
+
userId: result.userId,
|
|
245
|
+
userEmail: result.userEmail,
|
|
246
|
+
authUrl: env.authUrl,
|
|
247
|
+
apiUrl: env.apiUrl,
|
|
248
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
249
|
+
};
|
|
250
|
+
saveUserCredentials(credentials, env.staging);
|
|
251
|
+
return credentials;
|
|
252
|
+
}
|
|
253
|
+
async function loginCommand(env) {
|
|
254
|
+
const credentials = await signIn(env);
|
|
255
|
+
say();
|
|
256
|
+
say(" Signed in.");
|
|
257
|
+
if (credentials.userEmail)
|
|
258
|
+
say(` Account: ${credentials.userEmail}`);
|
|
259
|
+
say(` Saved to: ${getUserCredentialsPath(env.staging)}`);
|
|
260
|
+
say();
|
|
261
|
+
say(" Next: gigabuddy login --npm (to install @gigabuddy packages)");
|
|
262
|
+
}
|
|
263
|
+
function describeValidity(expiresIn) {
|
|
264
|
+
if (expiresIn <= 0)
|
|
265
|
+
return "";
|
|
266
|
+
const days = Math.round(expiresIn / 86400);
|
|
267
|
+
return days >= 1 ? ` (valid ~${days} days)` : ` (valid ${Math.round(expiresIn / 3600)}h)`;
|
|
268
|
+
}
|
|
269
|
+
async function npmLoginCommand(env) {
|
|
270
|
+
let credentials = loadUserCredentials(env.staging);
|
|
271
|
+
if (!credentials) {
|
|
272
|
+
say("Not signed in yet \u2014 signing in first.");
|
|
273
|
+
say();
|
|
274
|
+
credentials = await signIn(env);
|
|
275
|
+
say();
|
|
276
|
+
say(" Signed in.");
|
|
277
|
+
}
|
|
278
|
+
const credential = await mintRegistryCredential({
|
|
279
|
+
authUrl: credentials.authUrl,
|
|
280
|
+
refreshToken: credentials.refreshToken
|
|
281
|
+
});
|
|
282
|
+
const file = writeRegistryCredentialToNpmrc(credential);
|
|
283
|
+
say();
|
|
284
|
+
say(` npm is set up for ${GIGABUDDY_NPM_SCOPE} packages${describeValidity(credential.expiresIn)}`);
|
|
285
|
+
say(` Registry: ${credential.registryUrl}`);
|
|
286
|
+
say(` Written to: ${file}`);
|
|
287
|
+
say();
|
|
288
|
+
say(` Try it: npm install ${GIGABUDDY_NPM_SCOPE}/app-kit`);
|
|
289
|
+
say();
|
|
290
|
+
say(" This credential is registry-only \u2014 it installs what you can install and nothing else.");
|
|
291
|
+
say(" Run this again when it expires, or after your access changes.");
|
|
292
|
+
}
|
|
293
|
+
function registryUrlFor(staging) {
|
|
294
|
+
const creds = loadUserCredentials(staging);
|
|
295
|
+
const fromEnv = process.env["GIGABUDDY_NPM_REGISTRY_URL"];
|
|
296
|
+
if (fromEnv)
|
|
297
|
+
return fromEnv;
|
|
298
|
+
return creds?.authUrl.includes("staging") ? "https://npm.staging.gigabuddy.com" : "https://npm.gigabuddy.com";
|
|
299
|
+
}
|
|
300
|
+
function npmLogoutCommand(env) {
|
|
301
|
+
const file = removeRegistryCredentialFromNpmrc(registryUrlFor(env.staging));
|
|
302
|
+
if (!file) {
|
|
303
|
+
say(`No ${GIGABUDDY_NPM_SCOPE} registry entries found in ${npmrcPath()}`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
say(`Removed ${GIGABUDDY_NPM_SCOPE} registry credentials from ${file}`);
|
|
307
|
+
}
|
|
308
|
+
function logoutCommand(env) {
|
|
309
|
+
npmLogoutCommand(env);
|
|
310
|
+
if (!loadUserCredentials(env.staging)) {
|
|
311
|
+
say("Not signed in.");
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
clearUserCredentials(env.staging);
|
|
315
|
+
say(`Signed out${env.staging ? " of staging" : ""}.`);
|
|
316
|
+
}
|
|
317
|
+
function statusCommand() {
|
|
318
|
+
const production = loadUserCredentials(false);
|
|
319
|
+
const staging = loadUserCredentials(true);
|
|
320
|
+
if (!production && !staging) {
|
|
321
|
+
say("Not signed in.");
|
|
322
|
+
say();
|
|
323
|
+
say(" gigabuddy login");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
for (const [label, creds] of [
|
|
327
|
+
["Production", production],
|
|
328
|
+
["Staging", staging]
|
|
329
|
+
]) {
|
|
330
|
+
if (!creds)
|
|
331
|
+
continue;
|
|
332
|
+
say(`${label}:`);
|
|
333
|
+
say(` Account: ${creds.userEmail ?? creds.userId}`);
|
|
334
|
+
say(` Since: ${creds.createdAt}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// libs/gigabuddy/src/bin/cli.ts
|
|
339
|
+
function version() {
|
|
340
|
+
try {
|
|
341
|
+
const pkgPath = join3(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
342
|
+
return JSON.parse(readFileSync3(pkgPath, "utf-8")).version;
|
|
343
|
+
} catch {
|
|
344
|
+
return "unknown";
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function showHelp() {
|
|
348
|
+
console.error(`
|
|
349
|
+
gigabuddy \u2014 the Gigabuddy CLI
|
|
350
|
+
|
|
351
|
+
Usage:
|
|
352
|
+
gigabuddy <command> [options]
|
|
353
|
+
|
|
354
|
+
Commands:
|
|
355
|
+
login Sign in (opens your browser)
|
|
356
|
+
login --npm Set up npm to install @gigabuddy packages as you
|
|
357
|
+
logout Sign out (--npm clears only the npm credential)
|
|
358
|
+
status Show who you are signed in as
|
|
359
|
+
|
|
360
|
+
Options:
|
|
361
|
+
--npm Act on npm registry access in ~/.npmrc
|
|
362
|
+
--staging Use the staging environment
|
|
363
|
+
--auth-url Auth URL override
|
|
364
|
+
--api-url API URL override
|
|
365
|
+
-v, --version Show version
|
|
366
|
+
-h, --help Show this help
|
|
367
|
+
|
|
368
|
+
Installing packages:
|
|
369
|
+
gigabuddy login --npm
|
|
370
|
+
npm install @gigabuddy/app-kit
|
|
371
|
+
|
|
372
|
+
The npm credential is registry-scoped: it installs what you can install,
|
|
373
|
+
and cannot do anything else on the platform.
|
|
374
|
+
`);
|
|
375
|
+
}
|
|
376
|
+
async function main() {
|
|
377
|
+
const { values, positionals } = parseArgs({
|
|
378
|
+
options: {
|
|
379
|
+
npm: { type: "boolean" },
|
|
380
|
+
staging: { type: "boolean" },
|
|
381
|
+
"auth-url": { type: "string" },
|
|
382
|
+
"api-url": { type: "string" },
|
|
383
|
+
help: { type: "boolean", short: "h" },
|
|
384
|
+
version: { type: "boolean", short: "v" }
|
|
385
|
+
},
|
|
386
|
+
allowPositionals: true
|
|
387
|
+
});
|
|
388
|
+
if (values.version) {
|
|
389
|
+
console.log(`gigabuddy v${version()}`);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const command = positionals[0];
|
|
393
|
+
if (values.help || !command) {
|
|
394
|
+
showHelp();
|
|
395
|
+
process.exitCode = values.help ? 0 : 1;
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const env = resolveEnv({
|
|
399
|
+
staging: values.staging,
|
|
400
|
+
authUrl: values["auth-url"],
|
|
401
|
+
apiUrl: values["api-url"]
|
|
402
|
+
});
|
|
403
|
+
switch (command) {
|
|
404
|
+
case "login":
|
|
405
|
+
if (values.npm)
|
|
406
|
+
await npmLoginCommand(env);
|
|
407
|
+
else
|
|
408
|
+
await loginCommand(env);
|
|
409
|
+
return;
|
|
410
|
+
case "logout":
|
|
411
|
+
if (values.npm)
|
|
412
|
+
npmLogoutCommand(env);
|
|
413
|
+
else
|
|
414
|
+
logoutCommand(env);
|
|
415
|
+
return;
|
|
416
|
+
case "status":
|
|
417
|
+
statusCommand();
|
|
418
|
+
return;
|
|
419
|
+
default:
|
|
420
|
+
console.error(`Unknown command: ${command}`);
|
|
421
|
+
console.error("");
|
|
422
|
+
showHelp();
|
|
423
|
+
process.exitCode = 1;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
main().catch((error) => {
|
|
427
|
+
console.error("");
|
|
428
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
429
|
+
process.exit(1);
|
|
430
|
+
});
|
|
431
|
+
//# sourceMappingURL=cli.js.map
|
package/index.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// libs/client-core/dist/lib/credentials.js
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
var CONFIG_DIR = path.join(os.homedir(), ".gigabuddy");
|
|
6
|
+
var USER_CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
|
|
7
|
+
var USER_STAGING_CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.staging.json");
|
|
8
|
+
function getUserCredentialsPath(staging = false) {
|
|
9
|
+
return staging ? USER_STAGING_CREDENTIALS_FILE : USER_CREDENTIALS_FILE;
|
|
10
|
+
}
|
|
11
|
+
function loadUserCredentials(staging = false) {
|
|
12
|
+
try {
|
|
13
|
+
const content = fs.readFileSync(getUserCredentialsPath(staging), "utf-8");
|
|
14
|
+
const raw = JSON.parse(content);
|
|
15
|
+
return raw.refreshToken ? raw : null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function saveUserCredentials(credentials, staging = false) {
|
|
21
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
22
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
23
|
+
}
|
|
24
|
+
writePrivateFile(getUserCredentialsPath(staging), JSON.stringify(credentials, null, 2));
|
|
25
|
+
}
|
|
26
|
+
function clearUserCredentials(staging = false) {
|
|
27
|
+
const file = getUserCredentialsPath(staging);
|
|
28
|
+
if (fs.existsSync(file)) {
|
|
29
|
+
fs.unlinkSync(file);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function writePrivateFile(file, contents) {
|
|
33
|
+
fs.writeFileSync(file, contents, { mode: 384 });
|
|
34
|
+
try {
|
|
35
|
+
fs.chmodSync(file, 384);
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// libs/client-core/dist/lib/device-flow.js
|
|
41
|
+
async function openBrowser(url) {
|
|
42
|
+
const { exec } = await import("node:child_process");
|
|
43
|
+
const { promisify } = await import("node:util");
|
|
44
|
+
const execAsync = promisify(exec);
|
|
45
|
+
const platform = process.platform;
|
|
46
|
+
const command = platform === "darwin" ? `open "${url}"` : platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
47
|
+
await execAsync(command);
|
|
48
|
+
}
|
|
49
|
+
function sleep(ms) {
|
|
50
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
51
|
+
}
|
|
52
|
+
async function deviceLogin(options) {
|
|
53
|
+
const { authUrl } = options;
|
|
54
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
55
|
+
const startRes = await fetchImpl(`${authUrl}/api/auth/device/start`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { "Content-Type": "application/json" },
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
clientId: options.clientId ?? "gigabuddy-cli",
|
|
60
|
+
productId: options.productId ?? "gigabuddy/studio"
|
|
61
|
+
})
|
|
62
|
+
});
|
|
63
|
+
if (!startRes.ok) {
|
|
64
|
+
throw new Error(`Failed to start device flow: ${await startRes.text()}`);
|
|
65
|
+
}
|
|
66
|
+
const { deviceCode, userCode, verificationUrl, expiresIn, interval } = await startRes.json();
|
|
67
|
+
options.onPrompt?.({ verificationUrl, userCode });
|
|
68
|
+
(options.openBrowserImpl ?? openBrowser)(verificationUrl).catch(() => void 0);
|
|
69
|
+
const deadline = Date.now() + expiresIn * 1e3;
|
|
70
|
+
const pollInterval = (interval || 5) * 1e3;
|
|
71
|
+
while (Date.now() < deadline) {
|
|
72
|
+
await sleep(pollInterval);
|
|
73
|
+
const pollRes = await fetchImpl(`${authUrl}/api/auth/device/poll`, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: { "Content-Type": "application/json" },
|
|
76
|
+
body: JSON.stringify({ deviceCode })
|
|
77
|
+
});
|
|
78
|
+
if (pollRes.status === 429) {
|
|
79
|
+
await sleep(pollInterval);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (!pollRes.ok)
|
|
83
|
+
continue;
|
|
84
|
+
const pollData = await pollRes.json();
|
|
85
|
+
if (pollData.status === "complete" && pollData.platformToken && pollData.refreshToken && pollData.userId) {
|
|
86
|
+
return {
|
|
87
|
+
refreshToken: pollData.refreshToken,
|
|
88
|
+
platformToken: pollData.platformToken,
|
|
89
|
+
userId: pollData.userId
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (pollData.status === "expired") {
|
|
93
|
+
throw new Error("Device code expired. Please try again.");
|
|
94
|
+
}
|
|
95
|
+
options.onPending?.();
|
|
96
|
+
}
|
|
97
|
+
throw new Error("Authentication timed out. Please try again.");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// libs/client-core/dist/lib/npmrc.js
|
|
101
|
+
function registryAuthKey(registryUrl) {
|
|
102
|
+
const url = new URL(registryUrl);
|
|
103
|
+
const path3 = url.pathname.replace(/\/+$/, "");
|
|
104
|
+
return `//${url.host}${path3}/:_authToken`;
|
|
105
|
+
}
|
|
106
|
+
function scopeRegistryKey(scope) {
|
|
107
|
+
return `${scope}:registry`;
|
|
108
|
+
}
|
|
109
|
+
function normalizedRegistryUrl(registryUrl) {
|
|
110
|
+
return registryUrl.replace(/\/+$/, "") + "/";
|
|
111
|
+
}
|
|
112
|
+
function upsertNpmrc(existing, entries) {
|
|
113
|
+
const registryUrl = normalizedRegistryUrl(entries.registryUrl);
|
|
114
|
+
const desired = /* @__PURE__ */ new Map([
|
|
115
|
+
[scopeRegistryKey(entries.scope).toLowerCase(), `${scopeRegistryKey(entries.scope)}=${registryUrl}`],
|
|
116
|
+
[registryAuthKey(registryUrl).toLowerCase(), `${registryAuthKey(registryUrl)}=${entries.token}`]
|
|
117
|
+
]);
|
|
118
|
+
const lines = existing.length > 0 ? existing.split("\n") : [];
|
|
119
|
+
const written = /* @__PURE__ */ new Set();
|
|
120
|
+
const merged = lines.map((line) => {
|
|
121
|
+
const key = lineKey(line);
|
|
122
|
+
if (key && desired.has(key) && !written.has(key)) {
|
|
123
|
+
written.add(key);
|
|
124
|
+
return desired.get(key);
|
|
125
|
+
}
|
|
126
|
+
return line;
|
|
127
|
+
});
|
|
128
|
+
const additions = [...desired.entries()].filter(([key]) => !written.has(key)).map(([, line]) => line);
|
|
129
|
+
if (additions.length === 0)
|
|
130
|
+
return merged.join("\n");
|
|
131
|
+
while (merged.length > 0 && merged[merged.length - 1].trim() === "")
|
|
132
|
+
merged.pop();
|
|
133
|
+
return [...merged, ...additions, ""].join("\n");
|
|
134
|
+
}
|
|
135
|
+
function removeNpmrcEntries(existing, entries) {
|
|
136
|
+
const registryUrl = normalizedRegistryUrl(entries.registryUrl);
|
|
137
|
+
const drop = /* @__PURE__ */ new Set([scopeRegistryKey(entries.scope).toLowerCase(), registryAuthKey(registryUrl).toLowerCase()]);
|
|
138
|
+
const kept = existing.split("\n").filter((line) => {
|
|
139
|
+
const key = lineKey(line);
|
|
140
|
+
return !(key && drop.has(key));
|
|
141
|
+
});
|
|
142
|
+
const text = kept.join("\n");
|
|
143
|
+
return text.trim().length === 0 ? "" : text;
|
|
144
|
+
}
|
|
145
|
+
function lineKey(line) {
|
|
146
|
+
const trimmed = line.trim();
|
|
147
|
+
if (trimmed.length === 0 || trimmed.startsWith("#") || trimmed.startsWith(";"))
|
|
148
|
+
return null;
|
|
149
|
+
const eq = trimmed.indexOf("=");
|
|
150
|
+
if (eq === -1)
|
|
151
|
+
return null;
|
|
152
|
+
return trimmed.slice(0, eq).trim().toLowerCase();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// libs/client-core/dist/lib/registry-credential.js
|
|
156
|
+
import * as fs2 from "node:fs";
|
|
157
|
+
import * as os2 from "node:os";
|
|
158
|
+
import * as path2 from "node:path";
|
|
159
|
+
var GIGABUDDY_NPM_SCOPE = "@gigabuddy";
|
|
160
|
+
async function mintRegistryCredential(options) {
|
|
161
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
162
|
+
const res = await fetchImpl(`${options.authUrl}/api/token/registry`, {
|
|
163
|
+
method: "POST",
|
|
164
|
+
headers: { "Content-Type": "application/json" },
|
|
165
|
+
body: JSON.stringify({ refreshToken: options.refreshToken })
|
|
166
|
+
});
|
|
167
|
+
if (!res.ok) {
|
|
168
|
+
throw new Error(`Could not mint a registry credential (${res.status}): ${await res.text()}`);
|
|
169
|
+
}
|
|
170
|
+
const data = await res.json();
|
|
171
|
+
if (!data.token || !data.registryUrl) {
|
|
172
|
+
throw new Error("Auth returned an incomplete registry credential");
|
|
173
|
+
}
|
|
174
|
+
return { token: data.token, expiresIn: data.expiresIn ?? 0, registryUrl: data.registryUrl };
|
|
175
|
+
}
|
|
176
|
+
function npmrcPath() {
|
|
177
|
+
return process.env["NPM_CONFIG_USERCONFIG"] ?? path2.join(os2.homedir(), ".npmrc");
|
|
178
|
+
}
|
|
179
|
+
function readNpmrc(file) {
|
|
180
|
+
try {
|
|
181
|
+
return fs2.readFileSync(file, "utf-8");
|
|
182
|
+
} catch {
|
|
183
|
+
return "";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function writeRegistryCredentialToNpmrc(credential, file = npmrcPath()) {
|
|
187
|
+
const updated = upsertNpmrc(readNpmrc(file), {
|
|
188
|
+
scope: GIGABUDDY_NPM_SCOPE,
|
|
189
|
+
registryUrl: credential.registryUrl,
|
|
190
|
+
token: credential.token
|
|
191
|
+
});
|
|
192
|
+
writePrivateFile(file, updated);
|
|
193
|
+
return file;
|
|
194
|
+
}
|
|
195
|
+
function removeRegistryCredentialFromNpmrc(registryUrl, file = npmrcPath()) {
|
|
196
|
+
const existing = readNpmrc(file);
|
|
197
|
+
const updated = removeNpmrcEntries(existing, { scope: GIGABUDDY_NPM_SCOPE, registryUrl });
|
|
198
|
+
if (updated === existing)
|
|
199
|
+
return null;
|
|
200
|
+
writePrivateFile(file, updated);
|
|
201
|
+
return file;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// libs/gigabuddy/src/lib/commands.ts
|
|
205
|
+
var PRODUCTION_AUTH_URL = "https://auth.gigabuddy.com";
|
|
206
|
+
var PRODUCTION_API_URL = "https://api.gigabuddy.com";
|
|
207
|
+
var STAGING_AUTH_URL = "https://auth.staging.gigabuddy.com";
|
|
208
|
+
var STAGING_API_URL = "https://api.staging.gigabuddy.com";
|
|
209
|
+
function resolveEnv(options) {
|
|
210
|
+
const staging = options.staging ?? false;
|
|
211
|
+
return {
|
|
212
|
+
staging,
|
|
213
|
+
authUrl: options.authUrl ?? (staging ? STAGING_AUTH_URL : PRODUCTION_AUTH_URL),
|
|
214
|
+
apiUrl: options.apiUrl ?? (staging ? STAGING_API_URL : PRODUCTION_API_URL)
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function say(line = "") {
|
|
218
|
+
console.error(line);
|
|
219
|
+
}
|
|
220
|
+
async function signIn(env) {
|
|
221
|
+
say(`Signing in to ${env.staging ? "Gigabuddy Staging" : "Gigabuddy"}\u2026`);
|
|
222
|
+
const result = await deviceLogin({
|
|
223
|
+
authUrl: env.authUrl,
|
|
224
|
+
apiUrl: env.apiUrl,
|
|
225
|
+
clientId: "gigabuddy-cli",
|
|
226
|
+
onPrompt: ({ verificationUrl, userCode }) => {
|
|
227
|
+
say();
|
|
228
|
+
say(` Open: ${verificationUrl}`);
|
|
229
|
+
say(` Code: ${userCode}`);
|
|
230
|
+
say();
|
|
231
|
+
},
|
|
232
|
+
onPending: () => process.stderr.write(".")
|
|
233
|
+
});
|
|
234
|
+
const credentials = {
|
|
235
|
+
refreshToken: result.refreshToken,
|
|
236
|
+
userId: result.userId,
|
|
237
|
+
userEmail: result.userEmail,
|
|
238
|
+
authUrl: env.authUrl,
|
|
239
|
+
apiUrl: env.apiUrl,
|
|
240
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
241
|
+
};
|
|
242
|
+
saveUserCredentials(credentials, env.staging);
|
|
243
|
+
return credentials;
|
|
244
|
+
}
|
|
245
|
+
async function loginCommand(env) {
|
|
246
|
+
const credentials = await signIn(env);
|
|
247
|
+
say();
|
|
248
|
+
say(" Signed in.");
|
|
249
|
+
if (credentials.userEmail)
|
|
250
|
+
say(` Account: ${credentials.userEmail}`);
|
|
251
|
+
say(` Saved to: ${getUserCredentialsPath(env.staging)}`);
|
|
252
|
+
say();
|
|
253
|
+
say(" Next: gigabuddy login --npm (to install @gigabuddy packages)");
|
|
254
|
+
}
|
|
255
|
+
function describeValidity(expiresIn) {
|
|
256
|
+
if (expiresIn <= 0)
|
|
257
|
+
return "";
|
|
258
|
+
const days = Math.round(expiresIn / 86400);
|
|
259
|
+
return days >= 1 ? ` (valid ~${days} days)` : ` (valid ${Math.round(expiresIn / 3600)}h)`;
|
|
260
|
+
}
|
|
261
|
+
async function npmLoginCommand(env) {
|
|
262
|
+
let credentials = loadUserCredentials(env.staging);
|
|
263
|
+
if (!credentials) {
|
|
264
|
+
say("Not signed in yet \u2014 signing in first.");
|
|
265
|
+
say();
|
|
266
|
+
credentials = await signIn(env);
|
|
267
|
+
say();
|
|
268
|
+
say(" Signed in.");
|
|
269
|
+
}
|
|
270
|
+
const credential = await mintRegistryCredential({
|
|
271
|
+
authUrl: credentials.authUrl,
|
|
272
|
+
refreshToken: credentials.refreshToken
|
|
273
|
+
});
|
|
274
|
+
const file = writeRegistryCredentialToNpmrc(credential);
|
|
275
|
+
say();
|
|
276
|
+
say(` npm is set up for ${GIGABUDDY_NPM_SCOPE} packages${describeValidity(credential.expiresIn)}`);
|
|
277
|
+
say(` Registry: ${credential.registryUrl}`);
|
|
278
|
+
say(` Written to: ${file}`);
|
|
279
|
+
say();
|
|
280
|
+
say(` Try it: npm install ${GIGABUDDY_NPM_SCOPE}/app-kit`);
|
|
281
|
+
say();
|
|
282
|
+
say(" This credential is registry-only \u2014 it installs what you can install and nothing else.");
|
|
283
|
+
say(" Run this again when it expires, or after your access changes.");
|
|
284
|
+
}
|
|
285
|
+
function registryUrlFor(staging) {
|
|
286
|
+
const creds = loadUserCredentials(staging);
|
|
287
|
+
const fromEnv = process.env["GIGABUDDY_NPM_REGISTRY_URL"];
|
|
288
|
+
if (fromEnv)
|
|
289
|
+
return fromEnv;
|
|
290
|
+
return creds?.authUrl.includes("staging") ? "https://npm.staging.gigabuddy.com" : "https://npm.gigabuddy.com";
|
|
291
|
+
}
|
|
292
|
+
function npmLogoutCommand(env) {
|
|
293
|
+
const file = removeRegistryCredentialFromNpmrc(registryUrlFor(env.staging));
|
|
294
|
+
if (!file) {
|
|
295
|
+
say(`No ${GIGABUDDY_NPM_SCOPE} registry entries found in ${npmrcPath()}`);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
say(`Removed ${GIGABUDDY_NPM_SCOPE} registry credentials from ${file}`);
|
|
299
|
+
}
|
|
300
|
+
function logoutCommand(env) {
|
|
301
|
+
npmLogoutCommand(env);
|
|
302
|
+
if (!loadUserCredentials(env.staging)) {
|
|
303
|
+
say("Not signed in.");
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
clearUserCredentials(env.staging);
|
|
307
|
+
say(`Signed out${env.staging ? " of staging" : ""}.`);
|
|
308
|
+
}
|
|
309
|
+
function statusCommand() {
|
|
310
|
+
const production = loadUserCredentials(false);
|
|
311
|
+
const staging = loadUserCredentials(true);
|
|
312
|
+
if (!production && !staging) {
|
|
313
|
+
say("Not signed in.");
|
|
314
|
+
say();
|
|
315
|
+
say(" gigabuddy login");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
for (const [label, creds] of [
|
|
319
|
+
["Production", production],
|
|
320
|
+
["Staging", staging]
|
|
321
|
+
]) {
|
|
322
|
+
if (!creds)
|
|
323
|
+
continue;
|
|
324
|
+
say(`${label}:`);
|
|
325
|
+
say(` Account: ${creds.userEmail ?? creds.userId}`);
|
|
326
|
+
say(` Since: ${creds.createdAt}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
export {
|
|
330
|
+
PRODUCTION_API_URL,
|
|
331
|
+
PRODUCTION_AUTH_URL,
|
|
332
|
+
STAGING_API_URL,
|
|
333
|
+
STAGING_AUTH_URL,
|
|
334
|
+
loginCommand,
|
|
335
|
+
logoutCommand,
|
|
336
|
+
npmLoginCommand,
|
|
337
|
+
npmLogoutCommand,
|
|
338
|
+
resolveEnv,
|
|
339
|
+
signIn,
|
|
340
|
+
statusCommand
|
|
341
|
+
};
|
|
342
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gigabuddy",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "The Gigabuddy CLI — sign in, and install @gigabuddy packages as yourself.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"homepage": "https://gigabuddy.com",
|
|
8
|
+
"bin": {
|
|
9
|
+
"gigabuddy": "bin/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"main": "./index.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./index.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin/**/*.js",
|
|
17
|
+
"index.js",
|
|
18
|
+
"src/**/*.d.ts",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/bin/cli.d.ts
ADDED
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `gigabuddy` commands. Kept separate from bin/cli.ts (which only parses
|
|
3
|
+
* argv and dispatches) so each one is testable without spawning a process.
|
|
4
|
+
*
|
|
5
|
+
* Everything here is deliberately thin: sign in, prove who you are to the npm
|
|
6
|
+
* door, say what state you're in, sign out. The platform's actual capabilities
|
|
7
|
+
* live behind hosted doors — this client never grows tool implementations.
|
|
8
|
+
*/
|
|
9
|
+
import { type UserCredentials } from '@gigabuddy/client-core';
|
|
10
|
+
export declare const PRODUCTION_AUTH_URL = "https://auth.gigabuddy.com";
|
|
11
|
+
export declare const PRODUCTION_API_URL = "https://api.gigabuddy.com";
|
|
12
|
+
export declare const STAGING_AUTH_URL = "https://auth.staging.gigabuddy.com";
|
|
13
|
+
export declare const STAGING_API_URL = "https://api.staging.gigabuddy.com";
|
|
14
|
+
export interface Env {
|
|
15
|
+
authUrl: string;
|
|
16
|
+
apiUrl: string;
|
|
17
|
+
staging: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function resolveEnv(options: {
|
|
20
|
+
staging?: boolean;
|
|
21
|
+
authUrl?: string;
|
|
22
|
+
apiUrl?: string;
|
|
23
|
+
}): Env;
|
|
24
|
+
export declare function signIn(env: Env): Promise<UserCredentials>;
|
|
25
|
+
export declare function loginCommand(env: Env): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* `gigabuddy login --npm` — make `npm install @gigabuddy/…` work as you.
|
|
28
|
+
*
|
|
29
|
+
* Signs in first if needed, then mints a registry-scoped credential (never a
|
|
30
|
+
* full platform token) and merges two lines into `.npmrc`. Which packages you
|
|
31
|
+
* can install is still decided per install, against your own access; this only
|
|
32
|
+
* proves who you are.
|
|
33
|
+
*/
|
|
34
|
+
export declare function npmLoginCommand(env: Env): Promise<void>;
|
|
35
|
+
export declare function npmLogoutCommand(env: Env): void;
|
|
36
|
+
export declare function logoutCommand(env: Env): void;
|
|
37
|
+
export declare function statusCommand(): void;
|