@buildinternet/uploads 0.1.0 → 0.2.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/LICENSE +21 -0
- package/README.md +79 -64
- package/bin/uploads.js +0 -0
- package/dist/cli.js +81 -48
- package/dist/client.d.ts +68 -1
- package/dist/client.js +75 -12
- package/dist/commands/admin-enrollment.d.ts +7 -0
- package/dist/commands/admin-enrollment.js +59 -0
- package/dist/commands/install.d.ts +8 -0
- package/dist/commands/install.js +133 -0
- package/dist/commands/login.d.ts +11 -0
- package/dist/commands/login.js +160 -0
- package/dist/commands/mcp.d.ts +4 -0
- package/dist/commands/mcp.js +39 -0
- package/dist/commands/setup.js +6 -16
- package/dist/commands.d.ts +47 -0
- package/dist/commands.js +226 -133
- package/dist/config-file.js +14 -2
- package/dist/config.js +1 -0
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/io.d.ts +3 -0
- package/dist/io.js +9 -0
- package/dist/mcp/args.d.ts +4 -0
- package/dist/mcp/args.js +26 -0
- package/dist/mcp/server.d.ts +19 -0
- package/dist/mcp/server.js +109 -0
- package/dist/mcp/stdio.d.ts +3 -0
- package/dist/mcp/stdio.js +14 -0
- package/dist/mcp/tools.d.ts +10 -0
- package/dist/mcp/tools.js +386 -0
- package/package.json +63 -60
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { flagBool, flagString, parseCommandArgs, UsageError, } from "../cli-args.js";
|
|
2
|
+
import { resolveConfig } from "../config.js";
|
|
3
|
+
import { execRunner } from "../github-gh.js";
|
|
4
|
+
export const DEFAULT_MCP_URL = "https://agents.uploads.sh/mcp";
|
|
5
|
+
const SKILL_SOURCE = "buildinternet/uploads";
|
|
6
|
+
const SKILL_NAME = "uploads-cli";
|
|
7
|
+
const INSTALL_HELP = `uploads install — set up agent integrations (skill + remote MCP)
|
|
8
|
+
|
|
9
|
+
Installs the uploads-cli agent skill and registers the hosted MCP server
|
|
10
|
+
with Claude Code. The remote MCP endpoint infers your workspace from the
|
|
11
|
+
bearer token, so only the token is needed.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
uploads install [skill|mcp|all] (default: all)
|
|
15
|
+
|
|
16
|
+
What runs:
|
|
17
|
+
skill npx -y skills add ${SKILL_SOURCE} --skill ${SKILL_NAME}
|
|
18
|
+
mcp claude mcp add --transport http uploads ${DEFAULT_MCP_URL} \\
|
|
19
|
+
--header "Authorization: Bearer <token>"
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--url <endpoint> Remote MCP endpoint (default: ${DEFAULT_MCP_URL})
|
|
23
|
+
--name <name> MCP server name in the client (default: uploads)
|
|
24
|
+
--dry-run Print the commands without running them
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
uploads install
|
|
28
|
+
uploads install skill
|
|
29
|
+
uploads install mcp --dry-run
|
|
30
|
+
`;
|
|
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
|
+
*/
|
|
36
|
+
function redactor(token) {
|
|
37
|
+
return (text) => {
|
|
38
|
+
let out = text.replace(/Bearer \S+/g, "Bearer ***");
|
|
39
|
+
if (token)
|
|
40
|
+
out = out.split(token).join("***");
|
|
41
|
+
return out;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function runStep(run, command) {
|
|
45
|
+
try {
|
|
46
|
+
const output = run(command[0], command.slice(1)).trim();
|
|
47
|
+
return { command, ok: true, output: output || undefined };
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
51
|
+
// execFileSync's ENOENT means the binary itself is missing.
|
|
52
|
+
const hint = err.code === "ENOENT"
|
|
53
|
+
? `${command[0]} not found on PATH — run manually: ${command.join(" ")}`
|
|
54
|
+
: message;
|
|
55
|
+
return { command, ok: false, error: hint };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function runInstall(args, opts, help = false) {
|
|
59
|
+
const parsed = parseCommandArgs(args);
|
|
60
|
+
if (help || parsed.help) {
|
|
61
|
+
process.stderr.write(INSTALL_HELP);
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
const target = parsed.positionals[0] ?? "all";
|
|
65
|
+
if (!["skill", "mcp", "all"].includes(target)) {
|
|
66
|
+
throw new UsageError(`unknown install target: ${target} (expected skill, mcp, or all)`);
|
|
67
|
+
}
|
|
68
|
+
const url = flagString(parsed.flags, "--url") ?? DEFAULT_MCP_URL;
|
|
69
|
+
const name = flagString(parsed.flags, "--name") ?? "uploads";
|
|
70
|
+
const dryRun = flagBool(parsed.flags, "--dry-run");
|
|
71
|
+
const run = opts.runner ?? execRunner;
|
|
72
|
+
const results = {};
|
|
73
|
+
let redact = redactor(undefined);
|
|
74
|
+
if (target === "skill" || target === "all") {
|
|
75
|
+
const command = ["npx", "-y", "skills", "add", SKILL_SOURCE, "--skill", SKILL_NAME];
|
|
76
|
+
results.skill = dryRun ? { command, ok: true, skipped: "dry-run" } : runStep(run, command);
|
|
77
|
+
}
|
|
78
|
+
if (target === "mcp" || target === "all") {
|
|
79
|
+
const config = resolveConfig({
|
|
80
|
+
apiUrl: opts.globals.apiUrl,
|
|
81
|
+
workspace: opts.globals.workspace,
|
|
82
|
+
token: opts.globals.token,
|
|
83
|
+
envFile: opts.globals.envFile,
|
|
84
|
+
requireToken: !dryRun,
|
|
85
|
+
});
|
|
86
|
+
const bearer = config.token || "<token>";
|
|
87
|
+
redact = redactor(config.token || undefined);
|
|
88
|
+
const command = [
|
|
89
|
+
"claude",
|
|
90
|
+
"mcp",
|
|
91
|
+
"add",
|
|
92
|
+
"--transport",
|
|
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);
|
|
100
|
+
}
|
|
101
|
+
const failed = Object.values(results).some((r) => !r.ok);
|
|
102
|
+
if (opts.json) {
|
|
103
|
+
// Never echo the token in structured output — commands, child output,
|
|
104
|
+
// and error text can all embed it.
|
|
105
|
+
const redacted = Object.fromEntries(Object.entries(results).map(([key, r]) => [
|
|
106
|
+
key,
|
|
107
|
+
{
|
|
108
|
+
...r,
|
|
109
|
+
command: r.command.map(redact),
|
|
110
|
+
output: r.output === undefined ? undefined : redact(r.output),
|
|
111
|
+
error: r.error === undefined ? undefined : redact(r.error),
|
|
112
|
+
},
|
|
113
|
+
]));
|
|
114
|
+
process.stdout.write(JSON.stringify({ ok: !failed, steps: redacted }, null, 2) + "\n");
|
|
115
|
+
return failed ? 1 : 0;
|
|
116
|
+
}
|
|
117
|
+
for (const [step, r] of Object.entries(results)) {
|
|
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
|
+
}
|
|
129
|
+
if (!failed && !dryRun) {
|
|
130
|
+
process.stderr.write("hint: restart your agent session to pick up the new skill/server\n");
|
|
131
|
+
}
|
|
132
|
+
return failed ? 1 : 0;
|
|
133
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { parseCommandArgs } from "../cli-args.js";
|
|
2
|
+
export declare function validateEnrollmentCode(raw: string): string;
|
|
3
|
+
export declare function resolveEnrollmentCode(parsed: ReturnType<typeof parseCommandArgs>, io?: {
|
|
4
|
+
isTTY: boolean;
|
|
5
|
+
readLine: () => Promise<string>;
|
|
6
|
+
hiddenPrompt: () => Promise<string>;
|
|
7
|
+
}): Promise<string>;
|
|
8
|
+
export declare function runLogin(args: string[], opts: {
|
|
9
|
+
json?: boolean;
|
|
10
|
+
apiUrl?: string;
|
|
11
|
+
}, help?: boolean): Promise<number>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { stdin, stdout } from "node:process";
|
|
2
|
+
import { loadConfigFile, redactToken, resolveConfigPath, writeConfigKeys, workspaceFromToken, } from "../config.js";
|
|
3
|
+
import { exchangeEnrollment, createUploadsClient } from "../client.js";
|
|
4
|
+
import { flagBool, flagString, parseCommandArgs, UsageError } from "../cli-args.js";
|
|
5
|
+
const HELP = `uploads login [options]
|
|
6
|
+
|
|
7
|
+
Exchange a one-time enrollment code for workspace credentials, save them, and
|
|
8
|
+
verify access. Ask your uploads.sh administrator for an enrollment code.
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--code <code> Code in argv (may be visible in shell history/process lists)
|
|
12
|
+
--code-stdin Read one line from stdin
|
|
13
|
+
--non-interactive Never prompt
|
|
14
|
+
--api-url <url> API base (default: https://api.uploads.sh)
|
|
15
|
+
--path <file> Config destination
|
|
16
|
+
--force Replace existing saved credentials
|
|
17
|
+
--no-check Skip doctor verification
|
|
18
|
+
`;
|
|
19
|
+
export function validateEnrollmentCode(raw) {
|
|
20
|
+
const code = raw.trim();
|
|
21
|
+
if (!/^upe_[A-Za-z0-9_-]{20,}$/.test(code))
|
|
22
|
+
throw new UsageError("invalid enrollment code");
|
|
23
|
+
return code;
|
|
24
|
+
}
|
|
25
|
+
async function readLine() {
|
|
26
|
+
let out = "";
|
|
27
|
+
for await (const chunk of stdin) {
|
|
28
|
+
out += String(chunk);
|
|
29
|
+
if (out.includes("\n"))
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
return out.split(/\r?\n/, 1)[0] ?? "";
|
|
33
|
+
}
|
|
34
|
+
async function hiddenPrompt() {
|
|
35
|
+
if (!stdin.isTTY || typeof stdin.setRawMode !== "function")
|
|
36
|
+
return readLine();
|
|
37
|
+
stdout.write("Enrollment code: ");
|
|
38
|
+
stdin.setRawMode(true);
|
|
39
|
+
stdin.resume();
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
let value = "";
|
|
42
|
+
let settled = false;
|
|
43
|
+
const done = (err) => {
|
|
44
|
+
if (settled)
|
|
45
|
+
return;
|
|
46
|
+
settled = true;
|
|
47
|
+
stdin.off("data", onData);
|
|
48
|
+
stdin.off("error", onError);
|
|
49
|
+
try {
|
|
50
|
+
stdin.setRawMode(false);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
stdin.pause();
|
|
54
|
+
stdout.write("\n");
|
|
55
|
+
}
|
|
56
|
+
if (err)
|
|
57
|
+
reject(err);
|
|
58
|
+
else
|
|
59
|
+
resolve(value);
|
|
60
|
+
};
|
|
61
|
+
const onData = (chunk) => {
|
|
62
|
+
for (const char of chunk.toString("utf8")) {
|
|
63
|
+
if (char === "\r" || char === "\n")
|
|
64
|
+
return done();
|
|
65
|
+
if (char === "\u0003")
|
|
66
|
+
return done(new UsageError("login cancelled"));
|
|
67
|
+
if (char === "\u007f")
|
|
68
|
+
value = value.slice(0, -1);
|
|
69
|
+
else
|
|
70
|
+
value += char;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const onError = (err) => done(err);
|
|
74
|
+
stdin.on("data", onData);
|
|
75
|
+
stdin.on("error", onError);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export async function resolveEnrollmentCode(parsed, io = {
|
|
79
|
+
isTTY: Boolean(stdin.isTTY),
|
|
80
|
+
readLine,
|
|
81
|
+
hiddenPrompt,
|
|
82
|
+
}) {
|
|
83
|
+
const direct = flagString(parsed.flags, "--code");
|
|
84
|
+
const env = process.env.UPLOADS_ENROLLMENT_CODE;
|
|
85
|
+
const fromStdin = flagBool(parsed.flags, "--code-stdin");
|
|
86
|
+
const sources = [Boolean(direct), Boolean(env), fromStdin].filter(Boolean).length;
|
|
87
|
+
if (sources > 1)
|
|
88
|
+
throw new UsageError("provide enrollment code through only one source");
|
|
89
|
+
if (direct)
|
|
90
|
+
return validateEnrollmentCode(direct);
|
|
91
|
+
if (env)
|
|
92
|
+
return validateEnrollmentCode(env);
|
|
93
|
+
if (fromStdin)
|
|
94
|
+
return validateEnrollmentCode(await io.readLine());
|
|
95
|
+
if (flagBool(parsed.flags, "--non-interactive"))
|
|
96
|
+
throw new UsageError("enrollment code required in non-interactive mode");
|
|
97
|
+
if (!io.isTTY)
|
|
98
|
+
return validateEnrollmentCode(await io.readLine());
|
|
99
|
+
return validateEnrollmentCode(await io.hiddenPrompt());
|
|
100
|
+
}
|
|
101
|
+
export async function runLogin(args, opts, help = false) {
|
|
102
|
+
const parsed = parseCommandArgs(args);
|
|
103
|
+
if (help || parsed.help) {
|
|
104
|
+
process.stderr.write(HELP);
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
const apiUrl = flagString(parsed.flags, "--api-url") ?? opts.apiUrl ?? "https://api.uploads.sh";
|
|
108
|
+
const path = flagString(parsed.flags, "--path") ?? resolveConfigPath();
|
|
109
|
+
const force = flagBool(parsed.flags, "--force");
|
|
110
|
+
const existing = loadConfigFile(path);
|
|
111
|
+
if (existing.UPLOADS_TOKEN && !force)
|
|
112
|
+
throw new UsageError(`credentials already exist in ${path}; use --force to replace them`);
|
|
113
|
+
if (process.env.UPLOADS_TOKEN && !force)
|
|
114
|
+
throw new UsageError("UPLOADS_TOKEN is already set in the environment; unset it or use --force");
|
|
115
|
+
const code = await resolveEnrollmentCode(parsed);
|
|
116
|
+
const result = await exchangeEnrollment(apiUrl, code);
|
|
117
|
+
const encoded = workspaceFromToken(result.token);
|
|
118
|
+
if (!encoded || encoded !== result.workspace || /[\r\n]/.test(result.token))
|
|
119
|
+
throw new UsageError("enrollment returned invalid credentials");
|
|
120
|
+
const savedApiUrl = result.apiUrl ?? apiUrl;
|
|
121
|
+
const write = writeConfigKeys(path, {
|
|
122
|
+
UPLOADS_API_URL: savedApiUrl,
|
|
123
|
+
UPLOADS_WORKSPACE: result.workspace,
|
|
124
|
+
UPLOADS_TOKEN: result.token,
|
|
125
|
+
}, { force });
|
|
126
|
+
if (!["UPLOADS_API_URL", "UPLOADS_WORKSPACE", "UPLOADS_TOKEN"].every((key) => write.updated.includes(key)))
|
|
127
|
+
throw new UsageError("credentials were not fully written; retry with --force");
|
|
128
|
+
const checked = !flagBool(parsed.flags, "--no-check");
|
|
129
|
+
let doctor = { ok: true, error: undefined };
|
|
130
|
+
if (checked) {
|
|
131
|
+
try {
|
|
132
|
+
const client = createUploadsClient({
|
|
133
|
+
apiUrl: savedApiUrl,
|
|
134
|
+
workspace: result.workspace,
|
|
135
|
+
token: result.token,
|
|
136
|
+
});
|
|
137
|
+
const health = await client.health();
|
|
138
|
+
if (!health.ok)
|
|
139
|
+
throw new Error("API unhealthy");
|
|
140
|
+
await client.list({ limit: 1 });
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
doctor = { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const payload = {
|
|
147
|
+
ok: doctor.ok,
|
|
148
|
+
configPath: path,
|
|
149
|
+
workspace: result.workspace,
|
|
150
|
+
token: redactToken(result.token),
|
|
151
|
+
doctor: checked ? doctor : { skipped: true },
|
|
152
|
+
};
|
|
153
|
+
if (opts.json)
|
|
154
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
155
|
+
else {
|
|
156
|
+
process.stdout.write(`saved credentials to ${path}\nworkspace: ${result.workspace}\ntoken: ${redactToken(result.token)}\n`);
|
|
157
|
+
process[doctor.ok ? "stdout" : "stderr"].write(`doctor: ${checked ? (doctor.ok ? "ok" : `failed — ${doctor.error}`) : "skipped"}\n`);
|
|
158
|
+
}
|
|
159
|
+
return doctor.ok ? 0 : 1;
|
|
160
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { parseCommandArgs } from "../cli-args.js";
|
|
3
|
+
import { createMcpServer } from "../mcp/server.js";
|
|
4
|
+
import { serveStdio } from "../mcp/stdio.js";
|
|
5
|
+
import { createUploadsMcpTools } from "../mcp/tools.js";
|
|
6
|
+
const MCP_HELP = `uploads [globals] mcp
|
|
7
|
+
|
|
8
|
+
Serve the Model Context Protocol (MCP) over stdio for agent clients. Tools
|
|
9
|
+
mirror the CLI commands: put, attach, list, delete, usage, reconcile,
|
|
10
|
+
purge_expired, comment, health, doctor.
|
|
11
|
+
Global flags before "mcp" (--api-url, --token, --workspace, --env-file)
|
|
12
|
+
configure every tool call; a per-call "workspace" argument overrides
|
|
13
|
+
--workspace, like the CLI's per-command flag.
|
|
14
|
+
|
|
15
|
+
Example MCP client config:
|
|
16
|
+
{
|
|
17
|
+
"command": "uploads",
|
|
18
|
+
"args": ["--env-file", "/path/.env", "mcp"]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
uploads --env-file .env mcp
|
|
23
|
+
uploads --token up_default_… mcp
|
|
24
|
+
`;
|
|
25
|
+
// Same relative depth from src/commands/ and dist/commands/, so this works
|
|
26
|
+
// both under vitest (src) and at runtime (dist).
|
|
27
|
+
const { version } = createRequire(import.meta.url)("../../package.json");
|
|
28
|
+
export async function runMcp(args, opts, help = false) {
|
|
29
|
+
if (help || parseCommandArgs(args).help) {
|
|
30
|
+
process.stderr.write(MCP_HELP);
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
const server = createMcpServer({
|
|
34
|
+
serverInfo: { name: "uploads", version },
|
|
35
|
+
tools: createUploadsMcpTools({ globals: opts.globals }),
|
|
36
|
+
});
|
|
37
|
+
await serveStdio(server);
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -13,7 +13,7 @@ With flags: saves provided values, then optionally verifies with doctor.
|
|
|
13
13
|
Options:
|
|
14
14
|
--api-url <url> API base (default: ${DEFAULT_API_URL})
|
|
15
15
|
--workspace, -w <name>
|
|
16
|
-
--token <token>
|
|
16
|
+
--token <token> Existing bearer token (or use uploads login)
|
|
17
17
|
--prefix <path> Default key prefix for put/list (default: screenshots)
|
|
18
18
|
--repo <owner/repo> Default repo segment for put
|
|
19
19
|
--ref <id> Default ref segment for put (PR/issue/branch/date)
|
|
@@ -41,15 +41,6 @@ function buildStatus(envFile) {
|
|
|
41
41
|
sources: describeConfigSources({ envFile }),
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
function mintTokenCommand(apiUrl, workspace) {
|
|
45
|
-
const body = workspace && workspace !== DEFAULT_WORKSPACE
|
|
46
|
-
? ` \\\n -H "Content-Type: application/json" \\\n -d '{"workspace":"${workspace}","label":"cli"}'`
|
|
47
|
-
: "";
|
|
48
|
-
return [
|
|
49
|
-
`curl -XPOST ${apiUrl}/admin/tokens \\`,
|
|
50
|
-
` -H "Authorization: Bearer $ADMIN_TOKEN"${body}`,
|
|
51
|
-
].join("\n");
|
|
52
|
-
}
|
|
53
44
|
function formatWizard(status) {
|
|
54
45
|
const lines = ["uploads setup", ""];
|
|
55
46
|
lines.push(`Config file: ${status.configPath}`);
|
|
@@ -72,11 +63,10 @@ function formatWizard(status) {
|
|
|
72
63
|
}
|
|
73
64
|
lines.push("");
|
|
74
65
|
if (!status.token) {
|
|
75
|
-
lines.push("Step 1 —
|
|
76
|
-
lines.push("
|
|
77
|
-
lines.push("
|
|
78
|
-
lines.push(
|
|
79
|
-
lines.push(" Save:");
|
|
66
|
+
lines.push("Step 1 — Sign in");
|
|
67
|
+
lines.push(" Ask your uploads.sh administrator for a one-time enrollment code, then run:");
|
|
68
|
+
lines.push(" uploads login");
|
|
69
|
+
lines.push(" If you already have a bearer token:");
|
|
80
70
|
lines.push(" uploads setup --token up_<workspace>_…");
|
|
81
71
|
lines.push("");
|
|
82
72
|
}
|
|
@@ -217,7 +207,7 @@ export async function runSetup(args, opts, help = false) {
|
|
|
217
207
|
process.stderr.write("hint: run uploads doctor to verify\n");
|
|
218
208
|
}
|
|
219
209
|
else {
|
|
220
|
-
process.stderr.write("hint: run uploads
|
|
210
|
+
process.stderr.write("hint: run uploads login with an admin-provided enrollment code\n");
|
|
221
211
|
}
|
|
222
212
|
return doctorOk === false ? 1 : 0;
|
|
223
213
|
}
|
package/dist/commands.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type UploadsClient } from "./client.js";
|
|
2
2
|
import { type ResolvedConfig } from "./config.js";
|
|
3
|
+
import { type GhTarget } from "./github.js";
|
|
3
4
|
import { type CommandRunner } from "./github-gh.js";
|
|
4
5
|
export interface CliContext {
|
|
5
6
|
config: ResolvedConfig;
|
|
@@ -8,12 +9,58 @@ export interface CliContext {
|
|
|
8
9
|
quiet: boolean;
|
|
9
10
|
envFile?: string;
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Turns a pr/issue pair (+ optional repo) into a GhTarget; undefined when
|
|
14
|
+
* neither is present. Shared by the CLI flags and the MCP tool arguments.
|
|
15
|
+
*/
|
|
16
|
+
export declare function makeGhTarget(pr: number | undefined, issue: number | undefined, repoArg: string | undefined, run: CommandRunner): GhTarget | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* List every attachment under the target's prefix and create/update the
|
|
19
|
+
* managed comment. Throws on gh failure — callers decide whether that is
|
|
20
|
+
* fatal (`comment` command) or a warning (`put --comment`).
|
|
21
|
+
*/
|
|
22
|
+
export declare function syncAttachmentsComment(client: UploadsClient, target: GhTarget, run: CommandRunner): Promise<{
|
|
23
|
+
action: "created" | "updated" | "skipped";
|
|
24
|
+
count: number;
|
|
25
|
+
}>;
|
|
11
26
|
export declare function runAttach(ctx: CliContext, args: string[], help?: boolean, run?: CommandRunner): Promise<number>;
|
|
12
27
|
export declare function runPut(ctx: CliContext, args: string[], help?: boolean, run?: CommandRunner): Promise<number>;
|
|
13
28
|
export declare function runList(ctx: CliContext, args: string[], help?: boolean, run?: CommandRunner): Promise<number>;
|
|
14
29
|
export declare function runDelete(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
|
|
15
30
|
export declare function runComment(ctx: CliContext, args: string[], help?: boolean, run?: CommandRunner): Promise<number>;
|
|
31
|
+
export declare function runUsage(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
|
|
32
|
+
export declare function runReconcile(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
|
|
33
|
+
export declare function runPurgeExpired(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
|
|
16
34
|
export declare function runHealth(ctx: Pick<CliContext, "json"> & {
|
|
17
35
|
apiUrl: string;
|
|
18
36
|
}, args: string[], help?: boolean): Promise<number>;
|
|
37
|
+
export interface DoctorReport {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
apiUrl: string;
|
|
40
|
+
workspace: string;
|
|
41
|
+
workspaceSource: ResolvedConfig["workspaceSource"];
|
|
42
|
+
workspaceFromToken: string | undefined;
|
|
43
|
+
configPath: string;
|
|
44
|
+
configExists: boolean;
|
|
45
|
+
health: {
|
|
46
|
+
ok: boolean;
|
|
47
|
+
};
|
|
48
|
+
auth: {
|
|
49
|
+
ok: boolean;
|
|
50
|
+
error: string | undefined;
|
|
51
|
+
};
|
|
52
|
+
/** Usage snapshot when auth works (optional fields when the endpoint fails). */
|
|
53
|
+
usage?: {
|
|
54
|
+
ok: boolean;
|
|
55
|
+
bytes?: number;
|
|
56
|
+
objects?: number;
|
|
57
|
+
uploadsInPeriod?: number;
|
|
58
|
+
error?: string;
|
|
59
|
+
};
|
|
60
|
+
/** Workspace/token mismatch warning (also present in hints). */
|
|
61
|
+
warning?: string;
|
|
62
|
+
hints: string[];
|
|
63
|
+
}
|
|
64
|
+
/** Doctor's health + auth + workspace checks, shared by the CLI and the MCP tool. */
|
|
65
|
+
export declare function buildDoctorReport(config: ResolvedConfig, client: UploadsClient): Promise<DoctorReport>;
|
|
19
66
|
export declare function runDoctor(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
|