@standards/cli 1.0.0-alpha.411 → 1.0.0-alpha.412

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 +131 -19
  2. package/package.json +4 -4
package/dist/bin.mjs CHANGED
@@ -5,7 +5,7 @@ import { createRequire } from "node:module";
5
5
  import { styleText } from "node:util";
6
6
  import { BundleConflictError, StandardsRequestError, createTransport, syncBundle } from "@standards/client";
7
7
  import { Command } from "commander";
8
- import { BUNDLE_LOCALES, COUNT_MODES, MEETING_RESPONSES, RECORDING_OVERRIDES, RECORD_ACCESS_ROLES, RESOURCE_VISIBILITIES, ValidationError, canonicalStringify, diffSchemaState, formatByteSize, parseRecordAccessRole, parseSchemaState, schemaStateSourcePayload } from "@standards/schema";
8
+ import { BUNDLE_LOCALES, COUNT_MODES, MEETING_RESPONSES, RECORDING_OVERRIDES, RECORD_ACCESS_ROLES, RESOURCE_VISIBILITIES, ValidationError, canonicalStringify, diffSchemaState, formatByteSize, parseRecordAccessRole, parseSchemaState, parseSkillSource, schemaStateSourcePayload } from "@standards/schema";
9
9
  import Table from "cli-table3";
10
10
  import { createAppClient } from "@standards/client/app";
11
11
  import { createWriteStream } from "node:fs";
@@ -102,6 +102,16 @@ function parseCountFlag(count) {
102
102
  if (COUNT_MODES.includes(count)) return count;
103
103
  throw new ValidationError(`Invalid --count value "${count}". Expected one of: ${COUNT_MODES.join(", ")}.`, []);
104
104
  }
105
+ /**
106
+ * A bound the API reads as a number: `Number("soon")` is `NaN`, which serializes
107
+ * to `null` in JSON and would reach the server as an absent bound rather than as
108
+ * the mistake it is.
109
+ */
110
+ function parseCount(flag, value) {
111
+ const parsed = Number(value);
112
+ if (!Number.isInteger(parsed) || parsed < 0) throw new ValidationError(`${flag} must be a non-negative integer`, []);
113
+ return parsed;
114
+ }
105
115
  function getClientFromCommand(cmd) {
106
116
  const root = getGlobalOptions(cmd);
107
117
  if (!(root.apiUrl && root.apiKey)) throw new Error("No Standards instance configured. Run \"standards login\" or pass --api-key.");
@@ -326,6 +336,29 @@ const CAPABILITIES = Object.keys({
326
336
  modify: true,
327
337
  send: true
328
338
  });
339
+ const MAIL_PROVIDERS = ["gmail", "outlook"];
340
+ const CALENDAR_PROVIDERS = ["google-calendar", "outlook-calendar"];
341
+ /**
342
+ * What the app's dialog says before a provider consent, for the person who
343
+ * opens the URL from a terminal. `null` for GitHub and Slack.
344
+ */
345
+ function connectDisclosure(provider, capabilities = []) {
346
+ const modify = capabilities.includes("modify");
347
+ const send = capabilities.includes("send");
348
+ const lines = ["Standards will be able to:"];
349
+ if (MAIL_PROVIDERS.includes(provider)) {
350
+ lines.push("- read your emails and their attachments");
351
+ if (modify || send && provider === "outlook") lines.push("- change your emails");
352
+ if (send) lines.push("- send emails from this address");
353
+ lines.push("It shows them in the workspace, links them to records and makes them searchable.", "AI providers process your email automatically: new emails are classified, your email history is indexed for search by meaning, and important exchanges feed memory. The default assistant can read this mailbox, and AI providers also process your email whenever an agent works on it.");
354
+ } else if (CALENDAR_PROVIDERS.includes(provider)) {
355
+ lines.push("- read your events, their guests and their details");
356
+ if (modify) lines.push("- create, change and delete your events");
357
+ lines.push("It shows them in the workspace, links them to records and makes them searchable.", "The default assistant can read this calendar, and AI providers process your events whenever an agent works on them.");
358
+ } else return null;
359
+ lines.push("Your data is not sold and not used for ads.", "Disconnect at any time in Settings > Integrations.", "Details: https://docs.standards.new/docs/integrations/connected-accounts");
360
+ return lines.join("\n");
361
+ }
329
362
  /** Parse a comma-separated `--capabilities` flag; unknown tiers are refused before the request. */
330
363
  function parseCapabilities(raw) {
331
364
  if (raw === void 0) return void 0;
@@ -345,9 +378,11 @@ function registerConnectorsCommand(program) {
345
378
  connectors.command("list").description("List connector connections").option("--scope <scope>", "connection scope (tenant or actor)", "tenant").action(async (opts, cmd) => {
346
379
  formatOutput(await getAppClientFromCommand(cmd).connectors.list({ scope: resolveScope(opts.scope) }), getFormat(cmd));
347
380
  });
348
- connectors.command("connect").description("Start an OAuth flow and return the authorization URL to open").requiredOption("--provider <provider>", `connector provider (${PROVIDERS.join(", ")}); run "connectors providers" for the ones this deployment configured`).option("--scope <scope>", "connection scope (tenant or actor)", "tenant").option("--capabilities <list>", `comma-separated capability tiers beyond read (${CAPABILITIES.join(", ")})`).action(async (opts, cmd) => {
381
+ connectors.command("connect").description("Start an OAuth flow and return the authorization URL to open").requiredOption("--provider <provider>", `connector provider (${PROVIDERS.join(", ")}); run "connectors providers" for the ones this deployment configured`).option("--scope <scope>", "connection scope (tenant or actor)", "tenant").option("--capabilities <list>", `comma-separated capability tiers beyond read (${CAPABILITIES.join(", ")})`).option("--yes", "confirm the access a mailbox or calendar connection gives Standards").action(async (opts, cmd) => {
349
382
  if (!PROVIDERS.includes(opts.provider)) throw new ValidationError(`--provider must be one of: ${PROVIDERS.join(", ")}`, []);
350
383
  const capabilities = parseCapabilities(opts.capabilities);
384
+ const disclosure = connectDisclosure(opts.provider, capabilities);
385
+ if (disclosure && !opts.yes) throw new Error(`${disclosure}\n\nRe-run with "--yes" to continue.`);
351
386
  formatOutput(await getAppClientFromCommand(cmd).connectors.startAuth({
352
387
  provider: opts.provider,
353
388
  scope: resolveScope(opts.scope),
@@ -655,16 +690,6 @@ function parseOrder(order) {
655
690
  if (ORDERS.includes(order)) return order;
656
691
  throw new ValidationError(`--order must be one of: ${ORDERS.join(", ")}`, []);
657
692
  }
658
- /**
659
- * A bound the API reads as a number: `Number("soon")` is `NaN`, which serializes
660
- * to `null` in JSON and would reach the server as an absent bound rather than as
661
- * the mistake it is.
662
- */
663
- function parseCount(flag, value) {
664
- const parsed = Number(value);
665
- if (!Number.isInteger(parsed) || parsed < 0) throw new ValidationError(`${flag} must be a non-negative integer`, []);
666
- return parsed;
667
- }
668
693
  /** Build the `MeetingsQuery` body every meetings route accepts. Absent flags stay absent. */
669
694
  function buildQuery(opts) {
670
695
  const query = {};
@@ -1159,6 +1184,7 @@ function registerSearchCommand(program) {
1159
1184
  //#endregion
1160
1185
  //#region src/commands/skills.ts
1161
1186
  const SKILL_FILE = "SKILL.md";
1187
+ const DEFAULT_SEARCH_LIMIT = 20;
1162
1188
  /** A folder name: one path segment under the rule (`isSafeSkillFilePath`). */
1163
1189
  function isSafeSegment(segment) {
1164
1190
  return !segment.includes("/") && isSafeSkillFilePath(segment);
@@ -1278,19 +1304,90 @@ async function runSkillsPull(client, dir, names) {
1278
1304
  }
1279
1305
  return written;
1280
1306
  }
1307
+ /**
1308
+ * `standards skills search`: our index, or skills.sh with `community`. Every
1309
+ * row carries the same keys, because a table takes its columns from the first.
1310
+ */
1311
+ async function runSkillsSearch(client, query, options = {}) {
1312
+ const limit = options.limit === void 0 ? DEFAULT_SEARCH_LIMIT : parseCount("--limit", options.limit);
1313
+ return (await client.skillCatalog.search({
1314
+ query,
1315
+ scope: options.community === true ? "community" : "official",
1316
+ limit
1317
+ })).map(({ source, name, state, reason, description, installs }) => ({
1318
+ source,
1319
+ name,
1320
+ state,
1321
+ reason,
1322
+ description,
1323
+ installs
1324
+ }));
1325
+ }
1326
+ /** `standards skills preview`: what a source would install, the source checked here first. */
1327
+ async function runSkillsPreview(client, source) {
1328
+ parseSkillSource(source);
1329
+ return await client.skillCatalog.preview({ source });
1330
+ }
1331
+ /**
1332
+ * `standards skills add`: the source is previewed to pin it to a commit, then
1333
+ * that pinned source is what installs.
1334
+ */
1335
+ async function runSkillsAdd(client, source) {
1336
+ const { pinnedSource, follows, audience } = await runSkillsPreview(client, source);
1337
+ return {
1338
+ ...await client.skillCatalog.install({
1339
+ pinnedSource,
1340
+ follows,
1341
+ audience
1342
+ }),
1343
+ pinnedSource,
1344
+ follows,
1345
+ audience
1346
+ };
1347
+ }
1348
+ /** Whether a refusal names the `discardChanges` field (the `ValidationError` details the client keeps). */
1349
+ function refusesWithoutDiscard(error) {
1350
+ if (typeof error !== "object" || error === null) return false;
1351
+ const errors = error.details?.errors;
1352
+ return Array.isArray(errors) && errors.some((field) => Array.isArray(field.path) && field.path.includes("discardChanges"));
1353
+ }
1354
+ /**
1355
+ * `standards skills update`: `discardChanges` confirms losing the workspace's
1356
+ * edits. The server's refusal names that API field; the person types a flag,
1357
+ * so the CLI names the command that does it (#6635).
1358
+ */
1359
+ async function runSkillsUpdate(client, name, options = {}) {
1360
+ try {
1361
+ return await client.skillCatalog.update({
1362
+ name,
1363
+ discardChanges: options.discardChanges === true
1364
+ });
1365
+ } catch (error) {
1366
+ if (!refusesWithoutDiscard(error)) throw error;
1367
+ throw new Error(`\`${name}\` was changed in this workspace: run \`standards skills update ${name} --discard-changes\` to overwrite those edits`, { cause: error });
1368
+ }
1369
+ }
1370
+ /** `standards skills refresh`: checks the caller's private catalogue skills. */
1371
+ async function runSkillsRefresh(client) {
1372
+ return await client.skillCatalog.refresh({});
1373
+ }
1281
1374
  function collectName(value, names) {
1282
1375
  return [...names, value];
1283
1376
  }
1377
+ /** Prints what a command answers; any failure prints once on stderr and exits 1. */
1378
+ async function printAnswer(cmd, answer) {
1379
+ try {
1380
+ formatOutput(await answer(), getFormat(cmd));
1381
+ } catch (error) {
1382
+ console.error(styleText("red", `✗ ${messageOf(error)}`));
1383
+ process.exit(1);
1384
+ }
1385
+ }
1284
1386
  /** Skills as Agent Skills folders: pull them to disk, push folders back. */
1285
1387
  function registerSkillsCommand(program) {
1286
- const skills = program.command("skills").description("Pull and push skills as Agent Skills folders (SKILL.md and its files)");
1388
+ const skills = program.command("skills").description("Pull, push, find, add and update skills (Agent Skills folders: SKILL.md and its files)");
1287
1389
  skills.command("pull").description("Write each skill as a folder <dir>/<name>/ (every skill you can open by default)").argument("[dir]", "directory to write the folders into", ".").option("--name <name>", "a skill to pull (repeat for several)", collectName, []).action(async (dir, opts, cmd) => {
1288
- try {
1289
- formatOutput({ written: await runSkillsPull(getAppClientFromCommand(cmd), dir, opts.name) }, getFormat(cmd));
1290
- } catch (error) {
1291
- console.error(styleText("red", `✗ ${messageOf(error)}`));
1292
- process.exit(1);
1293
- }
1390
+ await printAnswer(cmd, async () => ({ written: await runSkillsPull(getAppClientFromCommand(cmd), dir, opts.name) }));
1294
1391
  });
1295
1392
  skills.command("push").description("Import skill folders: a folder holding SKILL.md, or a directory of such folders. All or nothing").argument("<dirs...>", "skill folders, or directories of skill folders").option("--replace", "rewrite the skill a name already reads instead of refusing it").action(async (dirs, opts, cmd) => {
1296
1393
  const outcome = await runSkillsPush(getAppClientFromCommand(cmd), dirs, opts);
@@ -1302,6 +1399,21 @@ function registerSkillsCommand(program) {
1302
1399
  for (const warning of outcome.result.warnings) console.warn(styleText("yellow", `! ${warning}`));
1303
1400
  formatOutput(outcome.result, getFormat(cmd));
1304
1401
  });
1402
+ skills.command("search").description("Find skills to add: the official catalogue, or skills.sh with --community").argument("<query>", "what the skill should do").option("--community", "search skills.sh instead of the official catalogue").option("--limit <n>", "max results to return (up to 50)").action(async (query, opts, cmd) => {
1403
+ await printAnswer(cmd, () => runSkillsSearch(getAppClientFromCommand(cmd), query, opts));
1404
+ });
1405
+ skills.command("preview").description("Show what a source would install, without installing it").argument("<source>", "owner/repo@skill or owner/repo@skill#ref").action(async (source, _opts, cmd) => {
1406
+ await printAnswer(cmd, () => runSkillsPreview(getAppClientFromCommand(cmd), source));
1407
+ });
1408
+ skills.command("add").description("Install a skill from a GitHub repository: what its preview read lands, and an official skill then follows stable").argument("<source>", "owner/repo@skill or owner/repo@skill#ref").action(async (source, _opts, cmd) => {
1409
+ await printAnswer(cmd, () => runSkillsAdd(getAppClientFromCommand(cmd), source));
1410
+ });
1411
+ skills.command("update").description("Bring an installed catalogue skill to the latest version of its source").argument("<name>", "name of the installed skill").option("--discard-changes", "overwrite the edits the workspace made to the skill").action(async (name, opts, cmd) => {
1412
+ await printAnswer(cmd, () => runSkillsUpdate(getAppClientFromCommand(cmd), name, opts));
1413
+ });
1414
+ skills.command("refresh").description("Check your private catalogue skills against their sources").action(async (_opts, cmd) => {
1415
+ await printAnswer(cmd, () => runSkillsRefresh(getAppClientFromCommand(cmd)));
1416
+ });
1305
1417
  }
1306
1418
  //#endregion
1307
1419
  //#region src/version.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@standards/cli",
3
- "version": "1.0.0-alpha.411",
3
+ "version": "1.0.0-alpha.412",
4
4
  "description": "CLI tool to interact with Standards API",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,9 +26,9 @@
26
26
  "cli-table3": "^0.6.5",
27
27
  "commander": "^13.1.0",
28
28
  "jiti": "2.7.0",
29
- "@standards/client": "1.0.0-alpha.411",
30
- "@standards/constants": "1.0.0-alpha.411",
31
- "@standards/schema": "1.0.0-alpha.411"
29
+ "@standards/client": "1.0.0-alpha.412",
30
+ "@standards/constants": "1.0.0-alpha.412",
31
+ "@standards/schema": "1.0.0-alpha.412"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^25.6.0",