@stndrds/cli 1.0.0-alpha.226 → 1.0.0-alpha.228

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.
Files changed (2) hide show
  1. package/dist/bin.mjs +136 -39
  2. package/package.json +1 -1
package/dist/bin.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/program.ts
4
- import chalk6 from "chalk";
4
+ import chalk7 from "chalk";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/client.ts
@@ -32,16 +32,10 @@ function createClient(config) {
32
32
  "Content-Type": "application/json",
33
33
  ...tenantId ? { "X-Tenant-ID": tenantId } : {}
34
34
  };
35
- async function request(method, path, options) {
36
- const url = buildUrl(apiUrl, path, options?.params);
35
+ async function sendRequest(url, init) {
37
36
  let response;
38
37
  try {
39
- response = await fetch(url, {
40
- method,
41
- headers,
42
- body: options?.body ? JSON.stringify(options.body) : void 0,
43
- signal: AbortSignal.timeout(3e4)
44
- });
38
+ response = await fetch(url, { ...init, signal: AbortSignal.timeout(3e4) });
45
39
  } catch (error) {
46
40
  if (error instanceof TypeError || error instanceof DOMException && error.name === "TimeoutError") {
47
41
  throw new ApiClientError(0, `Could not connect to ${apiUrl}. Is the server running?`);
@@ -58,6 +52,19 @@ function createClient(config) {
58
52
  }
59
53
  return data;
60
54
  }
55
+ function request(method, path, options) {
56
+ const url = buildUrl(apiUrl, path, options?.params);
57
+ return sendRequest(url, {
58
+ method,
59
+ headers,
60
+ body: options?.body ? JSON.stringify(options.body) : void 0
61
+ });
62
+ }
63
+ function postMultipart(path, form) {
64
+ const url = buildUrl(apiUrl, path);
65
+ const { "Content-Type": _ct, ...multipartHeaders } = headers;
66
+ return sendRequest(url, { method: "POST", headers: multipartHeaders, body: form });
67
+ }
61
68
  return {
62
69
  get(path, params) {
63
70
  return request("GET", path, { params });
@@ -73,7 +80,8 @@ function createClient(config) {
73
80
  },
74
81
  delete(path) {
75
82
  return request("DELETE", path);
76
- }
83
+ },
84
+ postMultipart
77
85
  };
78
86
  }
79
87
 
@@ -200,6 +208,43 @@ function registerAutofillCommand(program) {
200
208
  );
201
209
  }
202
210
 
211
+ // src/commands/connectors.ts
212
+ import chalk3 from "chalk";
213
+ var PROVIDERS = ["gmail", "outlook"];
214
+ function resolveScope(scope) {
215
+ return scope === "actor" ? "actor" : "tenant";
216
+ }
217
+ function registerConnectorsCommand(program) {
218
+ const connectors = program.command("connectors").description("Manage email connectors (Gmail / Outlook OAuth connections)");
219
+ connectors.command("list").description("List connector connections").option("--scope <scope>", "connection scope (tenant or actor)", "tenant").action(async (opts, cmd) => {
220
+ const client = getClientFromCommand(cmd);
221
+ const result = await client.get("/connectors/connections", {
222
+ scope: resolveScope(opts.scope)
223
+ });
224
+ formatOutput(result, getFormat(cmd));
225
+ });
226
+ connectors.command("connect").description("Start an OAuth flow and return the authorization URL to open").requiredOption("--provider <provider>", "connector provider (gmail or outlook)").option("--scope <scope>", "connection scope (tenant or actor)", "tenant").action(async (opts, cmd) => {
227
+ if (!PROVIDERS.includes(opts.provider)) {
228
+ throw new Error(`--provider must be one of: ${PROVIDERS.join(", ")}`);
229
+ }
230
+ const client = getClientFromCommand(cmd);
231
+ const result = await client.post("/connectors/auth/start", {
232
+ provider: opts.provider,
233
+ scope: resolveScope(opts.scope)
234
+ });
235
+ formatOutput(result, getFormat(cmd));
236
+ });
237
+ connectors.command("disconnect").description("Disconnect (delete) a connector connection").argument("<id>", "connection ID").option("--yes", "disconnect without confirmation").action(async (id, opts, cmd) => {
238
+ if (!opts.yes) {
239
+ throw new Error('Disconnecting a connector is explicit. Re-run with "--yes" to confirm.');
240
+ }
241
+ const client = getClientFromCommand(cmd);
242
+ await client.delete(`/connectors/connections/${id}`);
243
+ process.stdout.write(`${chalk3.green("\u2713")} Connector connection ${id} disconnected.
244
+ `);
245
+ });
246
+ }
247
+
203
248
  // src/commands/documents.ts
204
249
  function registerDocumentsCommand(program) {
205
250
  const documents = program.command("documents").description("Manage documents");
@@ -263,8 +308,40 @@ function registerDocumentsCommand(program) {
263
308
  });
264
309
  }
265
310
 
311
+ // src/commands/folders.ts
312
+ import { readFile } from "fs/promises";
313
+ function registerFoldersCommand(program) {
314
+ const folders = program.command("folders").description("Manage record drive folders");
315
+ folders.command("reconcile-paths").description("Create a folder tree on a record drive from paths").argument("<object>", "object name (e.g. contacts)").argument("<recordId>", "record ID owning the drive").option(
316
+ "--path <path>",
317
+ "folder path to create (repeatable)",
318
+ (val, acc) => {
319
+ acc.push(val);
320
+ return acc;
321
+ },
322
+ []
323
+ ).option("--paths-file <file>", "file containing one path per line").option("--mode <mode>", "reconcile mode: repair or dryRun", "repair").action(async (objectName, recordId, opts, cmd) => {
324
+ const paths = [...opts.path];
325
+ if (opts.pathsFile) {
326
+ const content = await readFile(opts.pathsFile, "utf-8");
327
+ const filePaths = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
328
+ paths.push(...filePaths);
329
+ }
330
+ if (paths.length === 0) {
331
+ throw new Error("No paths provided. Pass at least one --path or a non-empty --paths-file.");
332
+ }
333
+ const client = getClientFromCommand(cmd);
334
+ const result = await client.post(`/folders/record-drive/${recordId}/reconcile-paths`, {
335
+ objectName,
336
+ paths,
337
+ mode: opts.mode
338
+ });
339
+ formatOutput(result, getFormat(cmd));
340
+ });
341
+ }
342
+
266
343
  // src/commands/keys.ts
267
- import chalk3 from "chalk";
344
+ import chalk4 from "chalk";
268
345
  function registerKeysCommand(program) {
269
346
  const keys = program.command("keys").description("Manage Standards API keys");
270
347
  keys.command("list").description("List API keys").action(async (_opts, cmd) => {
@@ -291,12 +368,14 @@ function registerKeysCommand(program) {
291
368
  }
292
369
  const client = getClientFromCommand(cmd);
293
370
  await client.delete(`/api-keys/${id}`);
294
- process.stdout.write(`${chalk3.green("\u2713")} API key ${id} revoked.
371
+ process.stdout.write(`${chalk4.green("\u2713")} API key ${id} revoked.
295
372
  `);
296
373
  });
297
374
  }
298
375
 
299
376
  // src/commands/records.ts
377
+ import { readFile as readFile2 } from "fs/promises";
378
+ import { basename } from "path";
300
379
  function parseSortFlag(sort) {
301
380
  const [attribute, direction = "asc"] = sort.split(":");
302
381
  return [{ attribute, direction }];
@@ -353,15 +432,31 @@ function registerRecordsCommand(program) {
353
432
  const result = await client.post(`/records/${objectName}/search`, body);
354
433
  formatOutput(result, getFormat(cmd));
355
434
  });
435
+ records.command("attach-document").description("Upload and attach a local file to a record").argument("<object>", "object name (e.g. contacts)").argument("<recordId>", "record ID").requiredOption("--file <path>", "local file path to upload").option("--attribute <attr>", "attribute name to attach the document to").option("--parent-id <folderId>", "parent folder ID").option("--title <title>", "document title (defaults to filename)").action(async (objectName, recordId, opts, cmd) => {
436
+ const fileContent = await readFile2(opts.file);
437
+ const fileName = basename(opts.file);
438
+ const title = opts.title ?? fileName;
439
+ const form = new FormData();
440
+ form.append("files", new Blob([fileContent]), fileName);
441
+ form.append("title", title);
442
+ if (opts.attribute) form.append("attributeName", opts.attribute);
443
+ if (opts.parentId) form.append("parentId", opts.parentId);
444
+ const client = getClientFromCommand(cmd);
445
+ const result = await client.postMultipart(
446
+ `/records/${objectName}/${recordId}/documents/attach`,
447
+ form
448
+ );
449
+ formatOutput(result, getFormat(cmd));
450
+ });
356
451
  }
357
452
 
358
453
  // src/commands/root.ts
359
454
  import { stdin as input, stdout as output } from "process";
360
455
  import { createInterface } from "readline/promises";
361
- import chalk4 from "chalk";
456
+ import chalk5 from "chalk";
362
457
 
363
458
  // src/config.ts
364
- import { mkdir, readFile, rm, writeFile } from "fs/promises";
459
+ import { mkdir, readFile as readFile3, rm, writeFile } from "fs/promises";
365
460
  import { homedir } from "os";
366
461
  import { dirname, join } from "path";
367
462
  var DEFAULT_API_URL = "http://localhost:4100/v1";
@@ -375,7 +470,7 @@ function getConfigPath() {
375
470
  }
376
471
  async function readConfig() {
377
472
  try {
378
- const raw = await readFile(getConfigPath(), "utf8");
473
+ const raw = await readFile3(getConfigPath(), "utf8");
379
474
  const parsed = JSON.parse(raw);
380
475
  return {
381
476
  currentProfile: parsed.currentProfile,
@@ -498,7 +593,7 @@ function registerRootCommands(program) {
498
593
  await client.get("/api-keys");
499
594
  await upsertProfile({ name: opts.name, apiUrl: opts.url, apiKey });
500
595
  process.stdout.write(
501
- `${chalk4.green("\u2713")} Standards instance "${opts.name}" saved and selected.
596
+ `${chalk5.green("\u2713")} Standards instance "${opts.name}" saved and selected.
502
597
  `
503
598
  );
504
599
  process.stdout.write(` API URL: ${opts.url}
@@ -510,7 +605,7 @@ function registerRootCommands(program) {
510
605
  });
511
606
  program.command("use").description("Select the active Standards instance").argument("<name>", "instance name").action(async (name) => {
512
607
  await setCurrentProfile(name);
513
- process.stdout.write(`${chalk4.green("\u2713")} Standards instance "${name}" selected.
608
+ process.stdout.write(`${chalk5.green("\u2713")} Standards instance "${name}" selected.
514
609
  `);
515
610
  });
516
611
  program.command("instances").description("List configured Standards instances").action(async (_opts, cmd) => {
@@ -529,7 +624,7 @@ function registerRootCommands(program) {
529
624
  });
530
625
  program.command("logout").description("Remove a Standards instance from local CLI config").argument("[name]", "instance name, defaults to current").action(async (name) => {
531
626
  await removeProfile(name);
532
- process.stdout.write(`${chalk4.green("\u2713")} Standards instance removed.
627
+ process.stdout.write(`${chalk5.green("\u2713")} Standards instance removed.
533
628
  `);
534
629
  });
535
630
  }
@@ -539,7 +634,7 @@ import { createHash } from "crypto";
539
634
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
540
635
  import { join as join2 } from "path";
541
636
  import { fileURLToPath } from "url";
542
- import chalk5 from "chalk";
637
+ import chalk6 from "chalk";
543
638
  function registerSchemaCommand(program) {
544
639
  const schema = program.command("schema").description("Inspect schema objects and attributes");
545
640
  schema.command("list").description("List all schema objects").action(async (_opts, cmd) => {
@@ -566,7 +661,7 @@ function registerSchemaCommand(program) {
566
661
  const result = await fetchDrift(getClientFromCommand(cmd), opts.object);
567
662
  const totalDrift = result.summary.totalCustomObjects + result.summary.totalCustomAttributes;
568
663
  if (totalDrift === 0) {
569
- console.info(chalk5.green("\u2713 No drift detected \u2014 nothing to pull."));
664
+ console.info(chalk6.green("\u2713 No drift detected \u2014 nothing to pull."));
570
665
  process.exit(0);
571
666
  }
572
667
  const globalOpts = cmd.optsWithGlobals();
@@ -574,7 +669,7 @@ function registerSchemaCommand(program) {
574
669
  const shouldSave = opts.save !== false;
575
670
  if (opts.output) {
576
671
  writeFileSync(opts.output, prompt, "utf-8");
577
- console.info(chalk5.green(`\u2713 Prompt written to ${opts.output}`));
672
+ console.info(chalk6.green(`\u2713 Prompt written to ${opts.output}`));
578
673
  return;
579
674
  }
580
675
  if (shouldSave) {
@@ -588,8 +683,8 @@ function registerSchemaCommand(program) {
588
683
  const filename = join2(pullsDir, `${date}-${hash}.md`);
589
684
  writeFileSync(filename, prompt, "utf-8");
590
685
  writeFileSync(join2(diffDir, "latest.json"), JSON.stringify(result, null, 2), "utf-8");
591
- console.info(chalk5.green(`\u2713 Pull prompt written to ${filename}`));
592
- console.info(chalk5.dim(" Copy-paste its contents into your LLM to resolve the drift."));
686
+ console.info(chalk6.green(`\u2713 Pull prompt written to ${filename}`));
687
+ console.info(chalk6.dim(" Copy-paste its contents into your LLM to resolve the drift."));
593
688
  } else {
594
689
  process.stdout.write(prompt);
595
690
  }
@@ -604,46 +699,46 @@ var attrInline = (attr) => {
604
699
  function printDiffOutput(result) {
605
700
  const lines = [];
606
701
  for (const obj of result.customObjects) {
607
- lines.push(chalk5.yellow(`\u26A0 ${obj.name} (${obj.label}) \u2014 custom object, not in code`));
608
- lines.push(chalk5.dim(' \u2192 Run "standards pull" to promote or leave.'));
702
+ lines.push(chalk6.yellow(`\u26A0 ${obj.name} (${obj.label}) \u2014 custom object, not in code`));
703
+ lines.push(chalk6.dim(' \u2192 Run "standards pull" to promote or leave.'));
609
704
  }
610
705
  for (const entry of result.systemObjectDrift) {
611
706
  const unexpected = entry.sealed ? entry.customAttributes.filter((a) => !a.tolerated) : [];
612
707
  const tolerated = entry.customAttributes.filter((a) => a.tolerated);
613
708
  if (entry.sealed && unexpected.length > 0) {
614
709
  lines.push(
615
- chalk5.red(
710
+ chalk6.red(
616
711
  `\u2717 ${entry.objectName} \u2014 ${unexpected.length} unexpected custom attribute(s) (sealed object)`
617
712
  )
618
713
  );
619
- for (const attr of unexpected) lines.push(chalk5.red(attrInline(attr)));
620
- lines.push(chalk5.dim(' \u2192 Run "standards pull" to promote or tolerate these attributes.'));
714
+ for (const attr of unexpected) lines.push(chalk6.red(attrInline(attr)));
715
+ lines.push(chalk6.dim(' \u2192 Run "standards pull" to promote or tolerate these attributes.'));
621
716
  if (tolerated.length > 0) {
622
- lines.push(chalk5.dim(` Also tolerated: ${tolerated.length} attribute(s)`));
623
- for (const attr of tolerated) lines.push(chalk5.dim(attrInline(attr)));
717
+ lines.push(chalk6.dim(` Also tolerated: ${tolerated.length} attribute(s)`));
718
+ for (const attr of tolerated) lines.push(chalk6.dim(attrInline(attr)));
624
719
  }
625
720
  continue;
626
721
  }
627
722
  const okAttrs = entry.sealed ? tolerated : entry.customAttributes;
628
723
  if (okAttrs.length === 0) continue;
629
724
  const okSuffix = entry.sealed ? "tolerated custom attribute(s) (sealed)" : "custom attribute(s) (extensible)";
630
- lines.push(chalk5.green(`\u2713 ${entry.objectName} \u2014 ${okAttrs.length} ${okSuffix}`));
631
- for (const attr of okAttrs) lines.push(chalk5.dim(attrInline(attr)));
725
+ lines.push(chalk6.green(`\u2713 ${entry.objectName} \u2014 ${okAttrs.length} ${okSuffix}`));
726
+ for (const attr of okAttrs) lines.push(chalk6.dim(attrInline(attr)));
632
727
  }
633
728
  if (lines.length === 0) {
634
- console.info(chalk5.green("\u2713 No drift detected."));
729
+ console.info(chalk6.green("\u2713 No drift detected."));
635
730
  return;
636
731
  }
637
732
  console.info(lines.join("\n"));
638
733
  if (result.hasUnexpectedDrift) {
639
734
  console.info(
640
- chalk5.red(
735
+ chalk6.red(
641
736
  `
642
737
  \u2717 Unexpected drift: ${result.summary.totalUnexpected} attribute(s) on sealed object(s).`
643
738
  )
644
739
  );
645
740
  } else {
646
- console.info(chalk5.green("\n\u2713 No unexpected drift."));
741
+ console.info(chalk6.green("\n\u2713 No unexpected drift."));
647
742
  }
648
743
  }
649
744
  function resolveStandardsDir() {
@@ -758,9 +853,11 @@ function createProgram() {
758
853
  registerRecordsCommand(program);
759
854
  registerSchemaCommand(program);
760
855
  registerDocumentsCommand(program);
856
+ registerFoldersCommand(program);
761
857
  registerKeysCommand(program);
762
858
  registerAuthCommand(program);
763
859
  registerAutofillCommand(program);
860
+ registerConnectorsCommand(program);
764
861
  program.hook("preAction", async (_thisCommand, actionCommand) => {
765
862
  const raw = program.opts();
766
863
  const resolved = await resolveCliConfig({
@@ -774,7 +871,7 @@ function createProgram() {
774
871
  program.setOptionValue("tenant", raw.tenant);
775
872
  if (!(resolved.apiKey || isPublicCommand(actionCommand))) {
776
873
  console.error(
777
- chalk6.red(
874
+ chalk7.red(
778
875
  `\u2717 Error: No Standards instance configured. Run "standards login" or pass --api-key.`
779
876
  )
780
877
  );
@@ -788,12 +885,12 @@ async function runProgram(argv = process.argv) {
788
885
  await program.parseAsync(argv).catch((error) => {
789
886
  if (error instanceof ApiClientError) {
790
887
  if (error.statusCode > 0) {
791
- console.error(chalk6.red(`\u2717 Error (${error.statusCode}): ${error.message}`));
888
+ console.error(chalk7.red(`\u2717 Error (${error.statusCode}): ${error.message}`));
792
889
  } else {
793
- console.error(chalk6.red(`\u2717 Error: ${error.message}`));
890
+ console.error(chalk7.red(`\u2717 Error: ${error.message}`));
794
891
  }
795
892
  } else if (error instanceof Error) {
796
- console.error(chalk6.red(`\u2717 Error: ${error.message}`));
893
+ console.error(chalk7.red(`\u2717 Error: ${error.message}`));
797
894
  }
798
895
  process.exit(1);
799
896
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stndrds/cli",
3
- "version": "1.0.0-alpha.226",
3
+ "version": "1.0.0-alpha.228",
4
4
  "description": "CLI tool to interact with Standards API",
5
5
  "type": "module",
6
6
  "bin": {