@standards/cli 1.0.0-alpha.411 → 1.0.0-alpha.413
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 +1 -1
- package/dist/bin.mjs +131 -19
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ The Standards CLI connects a terminal to a Standards instance. See the public
|
|
|
6
6
|
## Devices
|
|
7
7
|
|
|
8
8
|
The CLI no longer runs a device worker. A machine your agents run commands on is enrolled with
|
|
9
|
-
the `standards-device` worker and an enrollment token from Settings → Devices (**Enroll your
|
|
9
|
+
the `standards-device` worker and an enrollment token from Settings → Agents → Devices (**Enroll your
|
|
10
10
|
computer** for your own machine, **Enroll machines** for a managed fleet): see the
|
|
11
11
|
[device fleet guide](../../content/docs/10-agents/07-device-fleet.mdx).
|
|
12
12
|
|
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
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "1.0.0-alpha.413",
|
|
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.
|
|
30
|
-
"@standards/constants": "1.0.0-alpha.
|
|
31
|
-
"@standards/schema": "1.0.0-alpha.
|
|
29
|
+
"@standards/client": "1.0.0-alpha.413",
|
|
30
|
+
"@standards/constants": "1.0.0-alpha.413",
|
|
31
|
+
"@standards/schema": "1.0.0-alpha.413"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^25.6.0",
|