@buildinternet/uploads 0.7.0 → 0.9.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 +15 -4
- package/dist/cli-args.d.ts +17 -2
- package/dist/cli-args.js +50 -5
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +67 -24
- package/dist/client.d.ts +69 -0
- package/dist/client.js +99 -5
- package/dist/commands/install.js +110 -52
- package/dist/commands/invite.d.ts +10 -0
- package/dist/commands/invite.js +102 -0
- package/dist/commands/login.d.ts +15 -0
- package/dist/commands/login.js +17 -9
- package/dist/commands.d.ts +4 -0
- package/dist/commands.js +287 -17
- package/dist/config-file.d.ts +3 -1
- package/dist/config-file.js +10 -0
- package/dist/config.js +6 -3
- package/dist/errors.d.ts +1 -1
- package/dist/github-gh.d.ts +7 -0
- package/dist/github-gh.js +24 -0
- package/dist/github.d.ts +15 -0
- package/dist/github.js +31 -7
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/mcp/args.d.ts +17 -0
- package/dist/mcp/args.js +42 -0
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/tools.js +121 -21
- package/dist/metadata.d.ts +32 -0
- package/dist/metadata.js +102 -0
- package/dist/public-urls.d.ts +18 -0
- package/dist/public-urls.js +68 -0
- package/package.json +1 -1
package/dist/commands/install.js
CHANGED
|
@@ -13,26 +13,28 @@ bearer token, so only the token is needed.
|
|
|
13
13
|
Usage:
|
|
14
14
|
uploads install [skill|mcp|all] (default: all)
|
|
15
15
|
|
|
16
|
-
What
|
|
17
|
-
skill npx
|
|
16
|
+
What it does:
|
|
17
|
+
skill Agent skill (via npx skills) — when to host files / embed in PRs
|
|
18
|
+
mcp Hosted MCP server in Claude Code — put, list, attach, galleries
|
|
19
|
+
|
|
20
|
+
What runs under the hood:
|
|
21
|
+
skill npx -y skills add ${SKILL_SOURCE} --skill ${SKILL_NAME} -g -y -a '*'
|
|
18
22
|
mcp claude mcp add --transport http uploads ${DEFAULT_MCP_URL} \\
|
|
19
23
|
--header "Authorization: Bearer <token>"
|
|
20
24
|
|
|
21
25
|
Options:
|
|
22
26
|
--url <endpoint> Remote MCP endpoint (default: ${DEFAULT_MCP_URL})
|
|
23
27
|
--name <name> MCP server name in the client (default: uploads)
|
|
24
|
-
--dry-run Print the
|
|
28
|
+
--dry-run Print the plan without running anything
|
|
29
|
+
--verbose Show underlying command output (default: errors only)
|
|
25
30
|
|
|
26
31
|
Examples:
|
|
27
32
|
uploads install
|
|
28
33
|
uploads install skill
|
|
29
|
-
uploads install mcp
|
|
34
|
+
uploads install mcp
|
|
35
|
+
uploads install --dry-run
|
|
30
36
|
`;
|
|
31
|
-
/**
|
|
32
|
-
* Masks the configured token (and any Bearer credential) in text destined
|
|
33
|
-
* for stdout/stderr/JSON — command echoes, child-process output, and error
|
|
34
|
-
* messages can all embed it.
|
|
35
|
-
*/
|
|
37
|
+
/** Mask Bearer credentials and the configured token in any printed text. */
|
|
36
38
|
function redactor(token) {
|
|
37
39
|
return (text) => {
|
|
38
40
|
let out = text.replace(/Bearer \S+/g, "Bearer ***");
|
|
@@ -48,13 +50,76 @@ function runStep(run, command) {
|
|
|
48
50
|
}
|
|
49
51
|
catch (err) {
|
|
50
52
|
const message = err instanceof Error ? err.message : String(err);
|
|
51
|
-
// execFileSync's ENOENT means the binary itself is missing.
|
|
52
53
|
const hint = err.code === "ENOENT"
|
|
53
|
-
? `${command[0]} not found on PATH — run manually: ${command.join(" ")}`
|
|
54
|
+
? `${command[0]} not found on PATH — install it, or run manually: ${command.join(" ")}`
|
|
54
55
|
: message;
|
|
55
56
|
return { command, ok: false, error: hint };
|
|
56
57
|
}
|
|
57
58
|
}
|
|
59
|
+
function skillCommand() {
|
|
60
|
+
// -g global, -y non-interactive, -a '*' every agent (skips the multi-select TUI)
|
|
61
|
+
return ["npx", "-y", "skills", "add", SKILL_SOURCE, "--skill", SKILL_NAME, "-g", "-y", "-a", "*"];
|
|
62
|
+
}
|
|
63
|
+
function mcpCommand(name, url, bearer) {
|
|
64
|
+
return [
|
|
65
|
+
"claude",
|
|
66
|
+
"mcp",
|
|
67
|
+
"add",
|
|
68
|
+
"--transport",
|
|
69
|
+
"http",
|
|
70
|
+
name,
|
|
71
|
+
url,
|
|
72
|
+
"--header",
|
|
73
|
+
`Authorization: Bearer ${bearer}`,
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
function peekToken(globals) {
|
|
77
|
+
try {
|
|
78
|
+
const config = resolveConfig({
|
|
79
|
+
apiUrl: globals.apiUrl,
|
|
80
|
+
workspace: globals.workspace,
|
|
81
|
+
token: globals.token,
|
|
82
|
+
envFile: globals.envFile,
|
|
83
|
+
requireToken: false,
|
|
84
|
+
});
|
|
85
|
+
return config.token || undefined;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function printHumanSteps(results, redact, verbose) {
|
|
92
|
+
for (const [step, r] of Object.entries(results)) {
|
|
93
|
+
const cmd = redact(r.command.join(" "));
|
|
94
|
+
if (r.skipped === "dry-run") {
|
|
95
|
+
process.stdout.write(`${step}: would run — ${cmd}\n`);
|
|
96
|
+
}
|
|
97
|
+
else if (r.skipped === "sign-in") {
|
|
98
|
+
process.stdout.write(`${step}: skipped — ${redact(r.error ?? "needs sign-in")}\n`);
|
|
99
|
+
}
|
|
100
|
+
else if (r.ok) {
|
|
101
|
+
process.stdout.write(`${step}: ok\n`);
|
|
102
|
+
if (verbose && r.output) {
|
|
103
|
+
process.stdout.write(` ${redact(r.output).split("\n").join("\n ")}\n`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
process.stderr.write(`${step}: failed — ${redact(r.error ?? "")}\n`);
|
|
108
|
+
if (verbose)
|
|
109
|
+
process.stderr.write(` command: ${cmd}\n`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function printSuccessFooter(steps, signedIn) {
|
|
114
|
+
process.stdout.write(`\nDone — ${steps.join(" and ")} ready.\n` +
|
|
115
|
+
"Restart your agent session so it picks up the new skill/server.\n" +
|
|
116
|
+
"Then ask it to host a screenshot or attach images to a PR — for example:\n" +
|
|
117
|
+
' "upload this screenshot and put it in the PR description"\n' +
|
|
118
|
+
' "attach before.png and after.png to this PR"\n');
|
|
119
|
+
if (!signedIn) {
|
|
120
|
+
process.stdout.write("\nNot signed in yet? Run `uploads login` once so put/attach/MCP can authenticate.\n");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
58
123
|
export async function runInstall(args, opts, help = false) {
|
|
59
124
|
const parsed = parseCommandArgs(args);
|
|
60
125
|
if (help || parsed.help) {
|
|
@@ -68,66 +133,59 @@ export async function runInstall(args, opts, help = false) {
|
|
|
68
133
|
const url = flagString(parsed.flags, "--url") ?? DEFAULT_MCP_URL;
|
|
69
134
|
const name = flagString(parsed.flags, "--name") ?? "uploads";
|
|
70
135
|
const dryRun = flagBool(parsed.flags, "--dry-run");
|
|
136
|
+
const verbose = flagBool(parsed.flags, "--verbose");
|
|
71
137
|
const run = opts.runner ?? execRunner;
|
|
138
|
+
const human = !opts.json && !dryRun;
|
|
139
|
+
const token = peekToken(opts.globals);
|
|
140
|
+
const signedIn = Boolean(token);
|
|
141
|
+
const redact = redactor(token);
|
|
72
142
|
const results = {};
|
|
73
|
-
let redact = redactor(undefined);
|
|
74
143
|
if (target === "skill" || target === "all") {
|
|
75
|
-
const command =
|
|
144
|
+
const command = skillCommand();
|
|
145
|
+
if (human)
|
|
146
|
+
process.stdout.write("Installing skill…\n");
|
|
76
147
|
results.skill = dryRun ? { command, ok: true, skipped: "dry-run" } : runStep(run, command);
|
|
77
148
|
}
|
|
78
149
|
if (target === "mcp" || target === "all") {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"
|
|
92
|
-
|
|
93
|
-
"http",
|
|
94
|
-
name,
|
|
95
|
-
url,
|
|
96
|
-
"--header",
|
|
97
|
-
`Authorization: Bearer ${bearer}`,
|
|
98
|
-
];
|
|
99
|
-
results.mcp = dryRun ? { command, ok: true, skipped: "dry-run" } : runStep(run, command);
|
|
150
|
+
if (!dryRun && !token) {
|
|
151
|
+
results.mcp = {
|
|
152
|
+
command: mcpCommand(name, url, "<token>"),
|
|
153
|
+
ok: false,
|
|
154
|
+
skipped: "sign-in",
|
|
155
|
+
error: "needs sign-in — run `uploads login`, then `uploads install mcp`",
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
const command = mcpCommand(name, url, token || "<token>");
|
|
160
|
+
if (human)
|
|
161
|
+
process.stdout.write("Installing MCP server…\n");
|
|
162
|
+
results.mcp = dryRun ? { command, ok: true, skipped: "dry-run" } : runStep(run, command);
|
|
163
|
+
}
|
|
100
164
|
}
|
|
101
165
|
const failed = Object.values(results).some((r) => !r.ok);
|
|
102
166
|
if (opts.json) {
|
|
103
|
-
|
|
104
|
-
// and error text can all embed it.
|
|
105
|
-
const redacted = Object.fromEntries(Object.entries(results).map(([key, r]) => [
|
|
167
|
+
const steps = Object.fromEntries(Object.entries(results).map(([key, r]) => [
|
|
106
168
|
key,
|
|
107
169
|
{
|
|
108
|
-
...r,
|
|
109
170
|
command: r.command.map(redact),
|
|
171
|
+
ok: r.ok,
|
|
172
|
+
skipped: r.skipped,
|
|
110
173
|
output: r.output === undefined ? undefined : redact(r.output),
|
|
111
174
|
error: r.error === undefined ? undefined : redact(r.error),
|
|
112
175
|
},
|
|
113
176
|
]));
|
|
114
|
-
process.stdout.write(JSON.stringify({ ok: !failed, steps
|
|
177
|
+
process.stdout.write(JSON.stringify({ ok: !failed, steps }, null, 2) + "\n");
|
|
115
178
|
return failed ? 1 : 0;
|
|
116
179
|
}
|
|
117
|
-
|
|
118
|
-
const shown = redact(r.command.join(" "));
|
|
119
|
-
if (r.skipped)
|
|
120
|
-
process.stdout.write(`${step}: would run — ${shown}\n`);
|
|
121
|
-
else if (r.ok) {
|
|
122
|
-
process.stdout.write(`${step}: ok — ${shown}\n`);
|
|
123
|
-
if (r.output)
|
|
124
|
-
process.stdout.write(` ${redact(r.output).split("\n").join("\n ")}\n`);
|
|
125
|
-
}
|
|
126
|
-
else
|
|
127
|
-
process.stderr.write(`${step}: failed — ${redact(r.error ?? "")}\n`);
|
|
128
|
-
}
|
|
180
|
+
printHumanSteps(results, redact, verbose);
|
|
129
181
|
if (!failed && !dryRun) {
|
|
130
|
-
|
|
182
|
+
printSuccessFooter(Object.keys(results), signedIn);
|
|
183
|
+
}
|
|
184
|
+
else if (failed && !dryRun && results.skill?.ok && results.mcp && !results.mcp.ok) {
|
|
185
|
+
const next = results.mcp.skipped === "sign-in"
|
|
186
|
+
? "Sign in with `uploads login`, then re-run `uploads install mcp`."
|
|
187
|
+
: "Fix the MCP step above, then re-run `uploads install mcp`.";
|
|
188
|
+
process.stdout.write(`\nSkill is installed. ${next}\n`);
|
|
131
189
|
}
|
|
132
190
|
return failed ? 1 : 0;
|
|
133
191
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type DeviceLoginIo } from "./login.js";
|
|
2
|
+
/** Exported for unit tests. */
|
|
3
|
+
export declare function resolveInviteWorkspace(workspaces: Array<{
|
|
4
|
+
workspace: string;
|
|
5
|
+
role: string;
|
|
6
|
+
}>, requested: string | undefined): string;
|
|
7
|
+
export declare function runInvite(args: string[], opts: {
|
|
8
|
+
json?: boolean;
|
|
9
|
+
apiUrl?: string;
|
|
10
|
+
}, help?: boolean, io?: DeviceLoginIo): Promise<number>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-admin email invite via ephemeral device session.
|
|
3
|
+
* Not ADMIN_TOKEN, not a workspace upload token.
|
|
4
|
+
*/
|
|
5
|
+
import { createWorkspaceInvite, listMintWorkspaces } from "../client.js";
|
|
6
|
+
import { flagBool, flagString, parseCommandArgs, UsageError } from "../cli-args.js";
|
|
7
|
+
import { defaultDeviceIo, obtainDeviceAccessToken, resolveAuthUrl, } from "./login.js";
|
|
8
|
+
const HELP = `uploads invite create [options]
|
|
9
|
+
|
|
10
|
+
Invite someone to a workspace by email. Opens a browser so you approve as
|
|
11
|
+
yourself (device login). You must be an admin or owner of that workspace.
|
|
12
|
+
|
|
13
|
+
The invitee gets email when Email Sending is configured; either way the
|
|
14
|
+
CLI prints an accept URL you can share. After accepting they run uploads login.
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--email <address> Required
|
|
18
|
+
--workspace <name> Required if you admin more than one workspace
|
|
19
|
+
--role member|admin Invitee org role (default: member)
|
|
20
|
+
--auth-url <url> Auth origin (default: derived from --api-url)
|
|
21
|
+
--api-url <url> API origin (default: https://api.uploads.sh)
|
|
22
|
+
--no-open Print the approval URL only
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
uploads invite create --email teammate@example.com
|
|
26
|
+
uploads invite create --workspace acme --email teammate@example.com
|
|
27
|
+
`;
|
|
28
|
+
/** Exported for unit tests. */
|
|
29
|
+
export function resolveInviteWorkspace(workspaces, requested) {
|
|
30
|
+
const adminable = workspaces.filter((w) => w.role === "admin" || w.role === "owner");
|
|
31
|
+
if (requested) {
|
|
32
|
+
const hit = adminable.find((w) => w.workspace === requested);
|
|
33
|
+
if (!hit) {
|
|
34
|
+
const roles = workspaces
|
|
35
|
+
.filter((w) => w.workspace === requested)
|
|
36
|
+
.map((w) => w.role)
|
|
37
|
+
.join(", ");
|
|
38
|
+
if (roles) {
|
|
39
|
+
throw new UsageError(`you are ${roles} on ${requested}, not admin/owner — only workspace admins can invite`);
|
|
40
|
+
}
|
|
41
|
+
throw new UsageError(`no admin access to workspace ${requested}`);
|
|
42
|
+
}
|
|
43
|
+
return hit.workspace;
|
|
44
|
+
}
|
|
45
|
+
if (adminable.length === 1)
|
|
46
|
+
return adminable[0].workspace;
|
|
47
|
+
if (adminable.length === 0) {
|
|
48
|
+
throw new UsageError("your account has no workspace admin access — ask a site operator or existing admin");
|
|
49
|
+
}
|
|
50
|
+
const names = adminable.map((w) => w.workspace).join(", ");
|
|
51
|
+
throw new UsageError(`multiple workspaces you admin (${names}); pass --workspace <name>`);
|
|
52
|
+
}
|
|
53
|
+
export async function runInvite(args, opts, help = false, io = defaultDeviceIo) {
|
|
54
|
+
const parsed = parseCommandArgs(args);
|
|
55
|
+
if (help || parsed.help) {
|
|
56
|
+
process.stderr.write(HELP);
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
if (parsed.positionals[0] !== "create") {
|
|
60
|
+
throw new UsageError("expected: uploads invite create");
|
|
61
|
+
}
|
|
62
|
+
const email = flagString(parsed.flags, "--email");
|
|
63
|
+
if (!email)
|
|
64
|
+
throw new UsageError("--email is required");
|
|
65
|
+
const roleRaw = flagString(parsed.flags, "--role") ?? "member";
|
|
66
|
+
if (roleRaw !== "member" && roleRaw !== "admin") {
|
|
67
|
+
throw new UsageError("--role must be member or admin");
|
|
68
|
+
}
|
|
69
|
+
const role = roleRaw;
|
|
70
|
+
const requestedWorkspace = flagString(parsed.flags, "--workspace");
|
|
71
|
+
const apiUrl = flagString(parsed.flags, "--api-url") ?? opts.apiUrl ?? "https://api.uploads.sh";
|
|
72
|
+
const authUrl = resolveAuthUrl(parsed, apiUrl);
|
|
73
|
+
if (flagBool(parsed.flags, "--non-interactive")) {
|
|
74
|
+
throw new UsageError("invite requires a browser for device approval");
|
|
75
|
+
}
|
|
76
|
+
const accessToken = await obtainDeviceAccessToken(authUrl, {
|
|
77
|
+
noOpen: flagBool(parsed.flags, "--no-open"),
|
|
78
|
+
prompt: "To invite as your account, open:",
|
|
79
|
+
}, io);
|
|
80
|
+
const { workspaces } = await listMintWorkspaces(apiUrl, accessToken);
|
|
81
|
+
const workspace = resolveInviteWorkspace(workspaces, requestedWorkspace);
|
|
82
|
+
const result = await createWorkspaceInvite(apiUrl, accessToken, workspace, { email, role });
|
|
83
|
+
const payload = {
|
|
84
|
+
ok: true,
|
|
85
|
+
workspace,
|
|
86
|
+
email: result.invitation.email,
|
|
87
|
+
role: result.invitation.role,
|
|
88
|
+
invitationId: result.invitation.id,
|
|
89
|
+
status: result.invitation.status,
|
|
90
|
+
acceptUrl: result.acceptUrl ?? null,
|
|
91
|
+
};
|
|
92
|
+
if (opts.json)
|
|
93
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
94
|
+
else {
|
|
95
|
+
process.stdout.write(`Invited ${email} to ${workspace} as ${role} (${result.invitation.status}).\n`);
|
|
96
|
+
if (result.acceptUrl) {
|
|
97
|
+
process.stdout.write(`Accept link (share if email isn't configured):\n ${result.acceptUrl}\n`);
|
|
98
|
+
}
|
|
99
|
+
process.stdout.write("They accept, then run: uploads login\n");
|
|
100
|
+
}
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -18,6 +18,21 @@ export interface DeviceLoginIo {
|
|
|
18
18
|
openUrl: (url: string) => void;
|
|
19
19
|
write: (text: string) => void;
|
|
20
20
|
}
|
|
21
|
+
export declare const defaultDeviceIo: DeviceLoginIo;
|
|
22
|
+
/**
|
|
23
|
+
* Browser device-authorization session only (no workspace token mint).
|
|
24
|
+
* Shared by `uploads login` and `uploads invite create`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function obtainDeviceAccessToken(authUrl: string, opts?: {
|
|
27
|
+
noOpen?: boolean;
|
|
28
|
+
prompt?: string;
|
|
29
|
+
}, io?: DeviceLoginIo): Promise<string>;
|
|
30
|
+
/** Poll device/token honoring interval / slow_down / pending until approved or expired. */
|
|
31
|
+
export declare function pollForDeviceToken(authUrl: string, code: {
|
|
32
|
+
device_code: string;
|
|
33
|
+
interval: number;
|
|
34
|
+
expires_in: number;
|
|
35
|
+
}, io: DeviceLoginIo): Promise<string>;
|
|
21
36
|
export declare function runLogin(args: string[], opts: {
|
|
22
37
|
json?: boolean;
|
|
23
38
|
apiUrl?: string;
|
package/dist/commands/login.js
CHANGED
|
@@ -157,7 +157,7 @@ function openUrl(url) {
|
|
|
157
157
|
// ignore — the URL is printed for manual navigation.
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
|
-
const defaultDeviceIo = {
|
|
160
|
+
export const defaultDeviceIo = {
|
|
161
161
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
162
162
|
now: () => Date.now(),
|
|
163
163
|
openUrl,
|
|
@@ -165,6 +165,20 @@ const defaultDeviceIo = {
|
|
|
165
165
|
process.stderr.write(text);
|
|
166
166
|
},
|
|
167
167
|
};
|
|
168
|
+
/**
|
|
169
|
+
* Browser device-authorization session only (no workspace token mint).
|
|
170
|
+
* Shared by `uploads login` and `uploads invite create`.
|
|
171
|
+
*/
|
|
172
|
+
export async function obtainDeviceAccessToken(authUrl, opts = {}, io = defaultDeviceIo) {
|
|
173
|
+
const code = await requestDeviceCode(authUrl);
|
|
174
|
+
const verifyUrl = code.verification_uri_complete ?? code.verification_uri;
|
|
175
|
+
const prompt = opts.prompt ?? "To sign in, open:";
|
|
176
|
+
io.write(`${prompt}\n\n ${verifyUrl}\n\nand confirm this code:\n\n ${code.user_code}\n\n`);
|
|
177
|
+
if (!opts.noOpen)
|
|
178
|
+
io.openUrl(verifyUrl);
|
|
179
|
+
io.write("Waiting for approval…\n");
|
|
180
|
+
return pollForDeviceToken(authUrl, code, io);
|
|
181
|
+
}
|
|
168
182
|
/**
|
|
169
183
|
* Device-authorization login (RFC 8628): request a code, have the user approve
|
|
170
184
|
* it in a browser, poll for the session token, then mint a workspace token.
|
|
@@ -173,13 +187,7 @@ async function runDeviceLogin(parsed, opts, io) {
|
|
|
173
187
|
const scopes = parseScopes(flagString(parsed.flags, "--scopes"));
|
|
174
188
|
const label = flagString(parsed.flags, "--label") ?? safeHostname();
|
|
175
189
|
const requestedWorkspace = flagString(parsed.flags, "--workspace");
|
|
176
|
-
const
|
|
177
|
-
const verifyUrl = code.verification_uri_complete ?? code.verification_uri;
|
|
178
|
-
io.write(`To sign in, open:\n\n ${verifyUrl}\n\nand confirm this code:\n\n ${code.user_code}\n\n`);
|
|
179
|
-
if (!opts.noOpen)
|
|
180
|
-
io.openUrl(verifyUrl);
|
|
181
|
-
io.write("Waiting for approval…\n");
|
|
182
|
-
const accessToken = await pollForDeviceToken(opts.authUrl, code, io);
|
|
190
|
+
const accessToken = await obtainDeviceAccessToken(opts.authUrl, { noOpen: opts.noOpen }, io);
|
|
183
191
|
const workspace = await resolveMintWorkspace(opts.apiUrl, accessToken, requestedWorkspace);
|
|
184
192
|
const minted = await mintWorkspaceToken(opts.apiUrl, accessToken, { workspace, scopes, label });
|
|
185
193
|
return { workspace: minted.workspace, token: minted.token, apiUrl: opts.apiUrl };
|
|
@@ -193,7 +201,7 @@ function safeHostname() {
|
|
|
193
201
|
}
|
|
194
202
|
}
|
|
195
203
|
/** Poll device/token honoring interval / slow_down / pending until approved or expired. */
|
|
196
|
-
async function pollForDeviceToken(authUrl, code, io) {
|
|
204
|
+
export async function pollForDeviceToken(authUrl, code, io) {
|
|
197
205
|
let intervalMs = Math.max(1, code.interval) * 1000;
|
|
198
206
|
const deadline = io.now() + Math.max(1, code.expires_in) * 1000;
|
|
199
207
|
while (io.now() < deadline) {
|
package/dist/commands.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface CliContext {
|
|
|
13
13
|
quiet: boolean;
|
|
14
14
|
envFile?: string;
|
|
15
15
|
}
|
|
16
|
+
/** Read a local file (or `-` for stdin). Missing path → FILE_NOT_FOUND (exit 2). */
|
|
17
|
+
export declare function readFileArg(fileArg: string): Uint8Array;
|
|
16
18
|
/**
|
|
17
19
|
* Turns a pr/issue pair (+ optional repo) into a GhTarget; undefined when
|
|
18
20
|
* neither is present. Shared by the CLI flags and the MCP tool arguments.
|
|
@@ -43,6 +45,8 @@ export declare function runAttach(ctx: CliContext, args: string[], help?: boolea
|
|
|
43
45
|
export declare function runPut(ctx: CliContext, args: string[], help?: boolean, run?: CommandRunner): Promise<number>;
|
|
44
46
|
export declare function runGallery(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
|
|
45
47
|
export declare function runList(ctx: CliContext, args: string[], help?: boolean, run?: CommandRunner): Promise<number>;
|
|
48
|
+
export declare function runFind(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
|
|
49
|
+
export declare function runMeta(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
|
|
46
50
|
export declare function runDelete(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
|
|
47
51
|
export declare function runComment(ctx: CliContext, args: string[], help?: boolean, run?: CommandRunner): Promise<number>;
|
|
48
52
|
export declare function runUsage(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
|