@propper-ai/cli 0.3.3 → 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 CHANGED
@@ -37,6 +37,7 @@ propper sign agreements create --name "NDA" --input-json @nda.json
37
37
  propper sign audit certificate --agreement-id ag_123 --output-file audit.pdf
38
38
  propper docgen batches list
39
39
  propper locker risks list
40
+ propper click templates list
40
41
  ```
41
42
 
42
43
  ## Authentication
@@ -136,6 +137,10 @@ propper docgen approvals | batches | delivery-configs | documents | templates ..
136
137
 
137
138
  # locker (Propper Locker API, /v1/locker)
138
139
  propper locker chat | documents | risks | settings ...
140
+
141
+ # click (Propper Click API — consent / clickwrap)
142
+ propper click templates | deployments | acceptances | analytics | render |
143
+ users | identity | verification | validation ...
139
144
  ```
140
145
 
141
146
  Run any group bare (`propper`, `propper sign`, `propper sign agreements`) to see
@@ -144,6 +149,7 @@ its help.
144
149
  Hand-authored:
145
150
 
146
151
  ```
152
+ propper api <path-or-url> raw HTTP escape hatch (see below)
147
153
  propper auth login | logout | status (whoami) | export [--unmasked]
148
154
  propper configure [wizard] | set | get | list-profiles
149
155
  propper completion bash | zsh | fish
@@ -174,6 +180,41 @@ propper sign documents upload --agreement-id ag_123 --file ./contract.pdf --file
174
180
  echo '{"name":"MSA"}' | propper sign agreements create --input-json -
175
181
  ```
176
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
+
177
218
  ## Use as a library (SDK)
178
219
 
179
220
  The generated client is API-namespaced and exported as `@propper-ai/cli/generated/client`:
@@ -4,8 +4,9 @@ import {
4
4
  AuthError,
5
5
  UsageError,
6
6
  callOperation,
7
- manifest
8
- } from "../chunk-CBIH6V3I.js";
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.3.3",
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 readFileSync3, writeFileSync as writeFileSync3 } from "fs";
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) => readFileSync3(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") Object.assign(body, readInputJson(inputJson, deps));
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);