@propper-ai/cli 0.4.0 → 0.5.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 +36 -0
- package/dist/bin/propper.js +176 -26
- package/dist/bin/propper.js.map +1 -1
- package/dist/{chunk-XSRXBEWN.js → chunk-K664LS27.js} +52 -27
- package/dist/chunk-K664LS27.js.map +1 -0
- package/dist/generated/client.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-XSRXBEWN.js.map +0 -1
package/README.md
CHANGED
|
@@ -149,6 +149,7 @@ its help.
|
|
|
149
149
|
Hand-authored:
|
|
150
150
|
|
|
151
151
|
```
|
|
152
|
+
propper api <path-or-url> raw HTTP escape hatch (see below)
|
|
152
153
|
propper auth login | logout | status (whoami) | export [--unmasked]
|
|
153
154
|
propper configure [wizard] | set | get | list-profiles
|
|
154
155
|
propper completion bash | zsh | fish
|
|
@@ -179,6 +180,41 @@ propper sign documents upload --agreement-id ag_123 --file ./contract.pdf --file
|
|
|
179
180
|
echo '{"name":"MSA"}' | propper sign agreements create --input-json -
|
|
180
181
|
```
|
|
181
182
|
|
|
183
|
+
### Raw API access
|
|
184
|
+
|
|
185
|
+
`propper api` is an escape hatch for endpoints not yet surfaced as commands —
|
|
186
|
+
modeled on `gh api`. It reuses your stored credentials, `--query`/`--output`,
|
|
187
|
+
and the standard error handling.
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# GET (path resolved against the API base URL; auth attached automatically)
|
|
191
|
+
propper api /v1/sign/agreements
|
|
192
|
+
|
|
193
|
+
# query params travel in the path; JMESPath + table output still apply
|
|
194
|
+
propper api "/v1/sign/agreements?limit=10" --query "[].id" --output table
|
|
195
|
+
|
|
196
|
+
# custom method + JSON body (method defaults to POST when a body is supplied)
|
|
197
|
+
propper api -X POST /v1/sign/agreements --input-json '{"name":"MSA"}'
|
|
198
|
+
echo '{"name":"MSA"}' | propper api /v1/sign/agreements --input-json -
|
|
199
|
+
|
|
200
|
+
# custom headers (repeatable); -i shows the status line + response headers
|
|
201
|
+
propper api -i -H "X-Trace-Id: abc" /v1/sign/agreements
|
|
202
|
+
|
|
203
|
+
# follow pagination and concatenate the results
|
|
204
|
+
propper api --paginate /v1/sign/agreements
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Flags: `-X, --method`, `-H, --header "Key: Value"` (repeatable; `"Key:"` removes
|
|
208
|
+
a default header), `--input-json <json|@file|->`, `-i, --include`, `--paginate`,
|
|
209
|
+
plus all global options.
|
|
210
|
+
|
|
211
|
+
- An absolute `http(s)://` URL is used as-is; otherwise the path is resolved
|
|
212
|
+
against `--base-url` (default `https://api.propper.ai`).
|
|
213
|
+
- Your stored bearer token is attached automatically. Supply your own
|
|
214
|
+
`-H "Authorization: …"` to override it (no login required).
|
|
215
|
+
- JSON responses are formatted (so `--query`/`--output` apply); other content
|
|
216
|
+
types are written through unchanged.
|
|
217
|
+
|
|
182
218
|
## Use as a library (SDK)
|
|
183
219
|
|
|
184
220
|
The generated client is API-namespaced and exported as `@propper-ai/cli/generated/client`:
|
package/dist/bin/propper.js
CHANGED
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
AuthError,
|
|
5
5
|
UsageError,
|
|
6
6
|
callOperation,
|
|
7
|
-
manifest
|
|
8
|
-
|
|
7
|
+
manifest,
|
|
8
|
+
rawRequest
|
|
9
|
+
} from "../chunk-K664LS27.js";
|
|
9
10
|
|
|
10
11
|
// src/bin/propper.ts
|
|
11
12
|
import { CommanderError } from "commander";
|
|
@@ -235,7 +236,7 @@ import { Command } from "commander";
|
|
|
235
236
|
// package.json
|
|
236
237
|
var package_default = {
|
|
237
238
|
name: "@propper-ai/cli",
|
|
238
|
-
version: "0.
|
|
239
|
+
version: "0.5.0",
|
|
239
240
|
description: "Propper CLI \u2014 an AWS-style, OpenAPI-generated command-line interface for the Propper Sign + Auth APIs",
|
|
240
241
|
type: "module",
|
|
241
242
|
license: "MIT",
|
|
@@ -623,6 +624,147 @@ async function resolveToken(ctx) {
|
|
|
623
624
|
);
|
|
624
625
|
}
|
|
625
626
|
|
|
627
|
+
// src/runtime/input.ts
|
|
628
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
629
|
+
var defaultInputDeps = {
|
|
630
|
+
readFile: (path) => readFileSync3(path),
|
|
631
|
+
readStdin: () => readFileSync3(0, "utf8")
|
|
632
|
+
};
|
|
633
|
+
function resolveInput(raw, deps = defaultInputDeps) {
|
|
634
|
+
if (raw === "-") return deps.readStdin();
|
|
635
|
+
if (raw.startsWith("@")) return deps.readFile(raw.slice(1)).toString("utf8");
|
|
636
|
+
return raw;
|
|
637
|
+
}
|
|
638
|
+
function readInputJson(raw, deps = defaultInputDeps) {
|
|
639
|
+
const text = resolveInput(raw, deps);
|
|
640
|
+
try {
|
|
641
|
+
return JSON.parse(text);
|
|
642
|
+
} catch (err) {
|
|
643
|
+
throw new UsageError(`--input-json is not valid JSON: ${err.message}`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// src/runtime/pagination.ts
|
|
648
|
+
var MAX_PAGES = 50;
|
|
649
|
+
function pageItems(data) {
|
|
650
|
+
if (Array.isArray(data)) return data;
|
|
651
|
+
if (data && typeof data === "object") {
|
|
652
|
+
for (const key of ["data", "items", "results", "agreements", "templates", "recipients"]) {
|
|
653
|
+
const value = data[key];
|
|
654
|
+
if (Array.isArray(value)) return value;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return void 0;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// src/commands/api/index.ts
|
|
661
|
+
var defaultApiDeps = {
|
|
662
|
+
resolveToken,
|
|
663
|
+
rawRequest,
|
|
664
|
+
input: defaultInputDeps,
|
|
665
|
+
stdout: process.stdout
|
|
666
|
+
};
|
|
667
|
+
function resolveUrl(pathOrUrl, baseUrl) {
|
|
668
|
+
if (/^https?:\/\//i.test(pathOrUrl)) return new URL(pathOrUrl);
|
|
669
|
+
return new URL(pathOrUrl.replace(/^\//, ""), `${baseUrl.replace(/\/$/, "")}/`);
|
|
670
|
+
}
|
|
671
|
+
function parseHeaders(raw) {
|
|
672
|
+
const headers = {};
|
|
673
|
+
for (const item of raw ?? []) {
|
|
674
|
+
const idx = item.indexOf(":");
|
|
675
|
+
if (idx === -1) throw new UsageError(`Invalid header "${item}" \u2014 expected "Key: Value"`);
|
|
676
|
+
const key = item.slice(0, idx).trim();
|
|
677
|
+
if (!key) throw new UsageError(`Invalid header "${item}" \u2014 missing name`);
|
|
678
|
+
headers[key] = item.slice(idx + 1).trim();
|
|
679
|
+
}
|
|
680
|
+
return headers;
|
|
681
|
+
}
|
|
682
|
+
function hasHeader(headers, name) {
|
|
683
|
+
const lower = name.toLowerCase();
|
|
684
|
+
return Object.keys(headers).some((k) => k.toLowerCase() === lower);
|
|
685
|
+
}
|
|
686
|
+
function printInclude(res, out) {
|
|
687
|
+
out.write(`HTTP ${res.status}
|
|
688
|
+
`);
|
|
689
|
+
for (const [key, value] of res.headers.entries()) out.write(`${key}: ${value}
|
|
690
|
+
`);
|
|
691
|
+
out.write("\n");
|
|
692
|
+
}
|
|
693
|
+
function printBody(res, ctx, out) {
|
|
694
|
+
if (res.bytes.length === 0) return;
|
|
695
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
696
|
+
if (contentType === "" || /json/i.test(contentType)) {
|
|
697
|
+
const text = res.bytes.toString("utf8");
|
|
698
|
+
try {
|
|
699
|
+
printResult(JSON.parse(text), { output: ctx.output, query: ctx.query }, out);
|
|
700
|
+
return;
|
|
701
|
+
} catch {
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
out.write(res.bytes);
|
|
705
|
+
}
|
|
706
|
+
async function runApi(pathOrUrl, opts, ctx, deps = defaultApiDeps) {
|
|
707
|
+
const headers = parseHeaders(opts.header);
|
|
708
|
+
let body;
|
|
709
|
+
if (typeof opts.inputJson === "string") {
|
|
710
|
+
body = JSON.stringify(readInputJson(opts.inputJson, deps.input));
|
|
711
|
+
if (!hasHeader(headers, "content-type")) headers["content-type"] = "application/json";
|
|
712
|
+
}
|
|
713
|
+
const method = (opts.method ?? (body !== void 0 ? "POST" : "GET")).toUpperCase();
|
|
714
|
+
let token;
|
|
715
|
+
if (!hasHeader(headers, "authorization")) {
|
|
716
|
+
token = await deps.resolveToken({
|
|
717
|
+
explicitToken: ctx.explicitToken,
|
|
718
|
+
profile: ctx.profile,
|
|
719
|
+
authBaseUrl: ctx.authBaseUrl,
|
|
720
|
+
clientId: ctx.clientId,
|
|
721
|
+
clientSecret: ctx.clientSecret,
|
|
722
|
+
env: ctx.env
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
const reqCtx = {
|
|
726
|
+
apiBaseUrl: ctx.apiBaseUrl,
|
|
727
|
+
token,
|
|
728
|
+
userAgent: USER_AGENT,
|
|
729
|
+
debug: ctx.debug
|
|
730
|
+
};
|
|
731
|
+
const url = resolveUrl(pathOrUrl, ctx.apiBaseUrl);
|
|
732
|
+
const errorMeta = { method, path: pathOrUrl };
|
|
733
|
+
if (opts.paginate) {
|
|
734
|
+
const collected = [];
|
|
735
|
+
for (let page = 1; page <= MAX_PAGES; page++) {
|
|
736
|
+
url.searchParams.set("page", String(page));
|
|
737
|
+
const res2 = await deps.rawRequest(
|
|
738
|
+
method,
|
|
739
|
+
url.toString(),
|
|
740
|
+
{ headers, body },
|
|
741
|
+
reqCtx,
|
|
742
|
+
errorMeta
|
|
743
|
+
);
|
|
744
|
+
let data;
|
|
745
|
+
try {
|
|
746
|
+
const text = res2.bytes.toString("utf8");
|
|
747
|
+
data = text ? JSON.parse(text) : void 0;
|
|
748
|
+
} catch {
|
|
749
|
+
throw new UsageError("--paginate requires a JSON response");
|
|
750
|
+
}
|
|
751
|
+
const items = pageItems(data);
|
|
752
|
+
if (!items) {
|
|
753
|
+
throw new UsageError("--paginate requires a JSON array or paged-envelope response");
|
|
754
|
+
}
|
|
755
|
+
if (items.length === 0) break;
|
|
756
|
+
collected.push(...items);
|
|
757
|
+
const limit = Number(url.searchParams.get("limit") ?? items.length);
|
|
758
|
+
if (items.length < limit) break;
|
|
759
|
+
}
|
|
760
|
+
printResult(collected, { output: ctx.output, query: ctx.query }, deps.stdout);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
const res = await deps.rawRequest(method, url.toString(), { headers, body }, reqCtx, errorMeta);
|
|
764
|
+
if (opts.include) printInclude(res, deps.stdout);
|
|
765
|
+
printBody(res, ctx, deps.stdout);
|
|
766
|
+
}
|
|
767
|
+
|
|
626
768
|
// src/commands/auth/deps.ts
|
|
627
769
|
var defaultAuthDeps = {
|
|
628
770
|
resolveToken,
|
|
@@ -900,7 +1042,10 @@ var GLOBAL_FLAGS = [
|
|
|
900
1042
|
var STATIC_GROUPS = {
|
|
901
1043
|
auth: ["login", "logout", "status", "whoami", "export"],
|
|
902
1044
|
configure: ["set", "get", "list-profiles"],
|
|
903
|
-
completion: [...SUPPORTED_SHELLS]
|
|
1045
|
+
completion: [...SUPPORTED_SHELLS],
|
|
1046
|
+
// `api <path>` takes a freeform path argument, so there are no sub-candidates;
|
|
1047
|
+
// an empty list still surfaces `api` itself at the top level.
|
|
1048
|
+
api: []
|
|
904
1049
|
};
|
|
905
1050
|
function operationFlags(entry) {
|
|
906
1051
|
const flags = [
|
|
@@ -1083,7 +1228,7 @@ async function runConfigureWizard(ctx, input = process.stdin, output = process.s
|
|
|
1083
1228
|
|
|
1084
1229
|
// src/runtime/dispatch.ts
|
|
1085
1230
|
import { Buffer } from "buffer";
|
|
1086
|
-
import { readFileSync as
|
|
1231
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1087
1232
|
|
|
1088
1233
|
// src/manifest/types.ts
|
|
1089
1234
|
function kebabCase(input) {
|
|
@@ -1130,7 +1275,7 @@ function flagKey(flag) {
|
|
|
1130
1275
|
var defaultDispatchDeps = {
|
|
1131
1276
|
resolveToken,
|
|
1132
1277
|
call: (entry, req, ctx) => callOperation(entry.api, entry.operationId, req, ctx),
|
|
1133
|
-
readFile: (path) =>
|
|
1278
|
+
readFile: (path) => readFileSync4(path),
|
|
1134
1279
|
writeFile: (path, data) => writeFileSync3(path, data),
|
|
1135
1280
|
stdout: process.stdout
|
|
1136
1281
|
};
|
|
@@ -1139,14 +1284,6 @@ function coerce(value, type) {
|
|
|
1139
1284
|
if (type === "number" || type === "integer") return Number(value);
|
|
1140
1285
|
return String(value);
|
|
1141
1286
|
}
|
|
1142
|
-
function readInputJson(raw, deps) {
|
|
1143
|
-
const text = raw === "-" ? readFileSync3(0, "utf8") : raw.startsWith("@") ? deps.readFile(raw.slice(1)).toString("utf8") : raw;
|
|
1144
|
-
try {
|
|
1145
|
-
return JSON.parse(text);
|
|
1146
|
-
} catch (err) {
|
|
1147
|
-
throw new UsageError(`--input-json is not valid JSON: ${err.message}`);
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
1287
|
function buildRequest(entry, flags, deps) {
|
|
1151
1288
|
const pathParams = {};
|
|
1152
1289
|
for (const p of entry.pathParams) {
|
|
@@ -1165,7 +1302,13 @@ function buildRequest(entry, flags, deps) {
|
|
|
1165
1302
|
if (entry.hasBody) {
|
|
1166
1303
|
body = {};
|
|
1167
1304
|
const inputJson = flags.inputJson;
|
|
1168
|
-
if (typeof inputJson === "string")
|
|
1305
|
+
if (typeof inputJson === "string") {
|
|
1306
|
+
const parsed = readInputJson(inputJson, {
|
|
1307
|
+
readFile: deps.readFile,
|
|
1308
|
+
readStdin: defaultInputDeps.readStdin
|
|
1309
|
+
});
|
|
1310
|
+
Object.assign(body, parsed);
|
|
1311
|
+
}
|
|
1169
1312
|
for (const field of entry.bodyFields) {
|
|
1170
1313
|
const value = flags[flagKey(field.flag)];
|
|
1171
1314
|
if (value !== void 0) body[field.name] = coerce(value, field.type);
|
|
@@ -1177,17 +1320,6 @@ function buildRequest(entry, flags, deps) {
|
|
|
1177
1320
|
}
|
|
1178
1321
|
return { pathParams, query, body };
|
|
1179
1322
|
}
|
|
1180
|
-
function pageItems(data) {
|
|
1181
|
-
if (Array.isArray(data)) return data;
|
|
1182
|
-
if (data && typeof data === "object") {
|
|
1183
|
-
for (const key of ["data", "items", "results", "agreements", "templates", "recipients"]) {
|
|
1184
|
-
const value = data[key];
|
|
1185
|
-
if (Array.isArray(value)) return value;
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
return void 0;
|
|
1189
|
-
}
|
|
1190
|
-
var MAX_PAGES = 50;
|
|
1191
1323
|
async function dispatch(entry, flags, ctx, deps = defaultDispatchDeps) {
|
|
1192
1324
|
const token = await deps.resolveToken({
|
|
1193
1325
|
explicitToken: ctx.explicitToken,
|
|
@@ -1245,6 +1377,9 @@ var defaultProgramDeps = {
|
|
|
1245
1377
|
configureGet,
|
|
1246
1378
|
configureListProfiles,
|
|
1247
1379
|
runConfigureWizard
|
|
1380
|
+
},
|
|
1381
|
+
api: {
|
|
1382
|
+
runApi
|
|
1248
1383
|
}
|
|
1249
1384
|
};
|
|
1250
1385
|
function addGlobalOptions(cmd) {
|
|
@@ -1338,6 +1473,20 @@ function registerApiTopics(program, manifest2, deps) {
|
|
|
1338
1473
|
addApiCommand(topicCmd, entry, deps);
|
|
1339
1474
|
}
|
|
1340
1475
|
}
|
|
1476
|
+
function registerApi(program, deps) {
|
|
1477
|
+
const collect = (value, prev = []) => prev.concat(value);
|
|
1478
|
+
addGlobalOptions(program.command("api")).argument("<path>", "Request path (resolved against the API base URL) or a full http(s) URL").description("Call an arbitrary Propper API endpoint (raw HTTP escape hatch)").option("-X, --method <verb>", "HTTP method (default GET, or POST when a body is supplied)").option(
|
|
1479
|
+
"-H, --header <header>",
|
|
1480
|
+
"Header as 'Key: Value' (repeatable; 'Key:' removes a default header)",
|
|
1481
|
+
collect
|
|
1482
|
+
).option(
|
|
1483
|
+
"--input-json <json|@file>",
|
|
1484
|
+
"JSON request body: a string, @file.json, or '-' for stdin"
|
|
1485
|
+
).option("-i, --include", "Print the response status line and headers before the body").option("--paginate", "Auto-follow pages and concatenate the results").action(async (path, _opts, c) => {
|
|
1486
|
+
const ctx = deps.buildContext(c.optsWithGlobals());
|
|
1487
|
+
await deps.api.runApi(path, c.opts(), ctx);
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1341
1490
|
function registerAuth(program, deps) {
|
|
1342
1491
|
const auth = program.command("auth").description("Authenticate with Propper");
|
|
1343
1492
|
showHelpWhenEmpty(auth);
|
|
@@ -1433,6 +1582,7 @@ function buildProgram(manifest2, deps = defaultProgramDeps) {
|
|
|
1433
1582
|
program.addHelpText("after", `
|
|
1434
1583
|
${renderEnvHelp()}`);
|
|
1435
1584
|
registerApiTopics(program, manifest2, deps);
|
|
1585
|
+
registerApi(program, deps);
|
|
1436
1586
|
registerAuth(program, deps);
|
|
1437
1587
|
registerConfigure(program, deps);
|
|
1438
1588
|
registerCompletion(program, manifest2);
|