@standards/cli 1.0.0-alpha.442 → 1.0.0-alpha.444

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/dist/bin.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  import { t as discoverBundles } from "./discover-bundles-DPpEZVv0.mjs";
4
4
  import { createRequire } from "node:module";
5
5
  import { styleText } from "node:util";
6
- import { BundleConflictError, StandardsRequestError, createTransport, syncBundle } from "@standards/client";
6
+ import { BundleConflictError, StandardsRequestError, createTransport, previewBundleSync, syncBundle } from "@standards/client";
7
7
  import { Command, Option } from "commander";
8
8
  import { BUNDLE_LOCALES, COUNT_MODES, DECISION_TYPE_AGENT_SETUP, DECISION_TYPE_BUNDLE_SETUP, ENVIRONMENT_HARNESSES, ENVIRONMENT_IMAGES, ENVIRONMENT_SIZES, LINK_MAX_MS, NotFoundError, PROVIDER_NAMES, RECORD_ACCESS_ROLES, RESOURCE_VISIBILITIES, SchemaError, SchemaErrorCode, ValidationError, canonicalStringify, diffSchemaState, environmentHarness, formatByteSize, formatDecisionLink, isEnvironmentHarnessId, isPlainRecord, isSkillRepository, parseRecordAccessRole, parseSchemaState, parseSkillSource, schemaStateSourcePayload } from "@standards/schema";
9
9
  import { createWriteStream } from "node:fs";
@@ -287,13 +287,107 @@ async function run(body) {
287
287
  process.exit(1);
288
288
  }
289
289
  }
290
- async function syncDiscovered(cmd, cwd) {
290
+ /**
291
+ * A refused confirmation carries no counts, and a bundle sync's refusal does
292
+ * not even keep its details, so the plan is previewed again: when its
293
+ * fingerprint or its record total is no longer what was sent, the plan is
294
+ * stale and the message names the total to confirm. Anything else, or a
295
+ * caller who cannot count, gets the refusal as the server sent it.
296
+ */
297
+ async function refuseStale(error, bundle, sent, currentPlan) {
298
+ if (!(error instanceof StandardsRequestError) || error.status !== 400) throw error;
299
+ const current = await currentPlan().catch(() => void 0);
300
+ if (current === void 0) throw error;
301
+ const { fingerprint, total } = current;
302
+ if (!(fingerprint !== sent.fingerprint || sent.confirmedRecords !== void 0 && total !== sent.confirmedRecords) || total === void 0) throw error;
303
+ throw new Error(`${messageOf(error)}\n${bundle}: ${total} records`);
304
+ }
305
+ /** Every record the removal of a bundle deletes. */
306
+ function removalTotal(preview) {
307
+ return preview.objects.reduce((total, object) => total + object.records, 0);
308
+ }
309
+ /** `<bundle>=<value>`, one per bundle, with a message naming the flag when it is malformed. */
310
+ function parseKeyed(flag, shape, value) {
311
+ const separator = value.indexOf("=");
312
+ if (separator > 0 && separator < value.length - 1) return [value.slice(0, separator), value.slice(separator + 1)];
313
+ throw new ValidationError(`${flag} must read ${shape} (received: ${value})`, []);
314
+ }
315
+ function collectFingerprint(value, fingerprints) {
316
+ const [bundle, fingerprint] = parseKeyed("--fingerprint", "<bundle>=<fingerprint>", value);
317
+ return {
318
+ ...fingerprints,
319
+ [bundle]: fingerprint
320
+ };
321
+ }
322
+ function collectConfirmRecords(value, confirmations) {
323
+ const [bundle, count] = parseKeyed("--confirm-records", "<bundle>=<n>", value);
324
+ if (!/^\d+$/.test(count)) throw new ValidationError(`--confirm-records must read <bundle>=<n> with n a whole number (received: ${value})`, []);
325
+ return {
326
+ ...confirmations,
327
+ [bundle]: Number(count)
328
+ };
329
+ }
330
+ /** The records confirmed for the one bundle a command acts on; another bundle's confirmation is refused. */
331
+ function confirmedFor(name, confirmations, { required }) {
332
+ const others = Object.keys(confirmations).filter((bundle) => bundle !== name);
333
+ const confirmed = confirmations[name];
334
+ if (others.length === 0 && (confirmed !== void 0 || !required)) return confirmed;
335
+ throw new ValidationError(`Pass --confirm-records ${name}=<n> with the records "${name}" removes: run "bundles ${required ? "remove" : "update"} ${name} --dry-run" first.`, []);
336
+ }
337
+ /** Each confirmed bundle needs the fingerprint of its own sync preview, and the bundle must be one being synced. */
338
+ function checkSyncConfirmations(names, opts) {
339
+ for (const bundle of Object.keys(opts.confirmRecords)) {
340
+ if (!names.includes(bundle)) throw new ValidationError(`--confirm-records names "${bundle}", which is not a discovered bundle`, []);
341
+ if (opts.fingerprint[bundle] === void 0) throw new ValidationError(`Pass --fingerprint ${bundle}=<fingerprint> with --confirm-records ${bundle}=<n>: run "bundles sync --dry-run" first.`, []);
342
+ }
343
+ }
344
+ function pendingRemovalsLine(bundle, removals) {
345
+ if (removals.length === 0) return void 0;
346
+ const records = removals.reduce((total, removal) => total + (removal.records ?? 0), 0);
347
+ const counted = removals.some((removal) => removal.records !== void 0);
348
+ return `${bundle}: ${removals.length} removal${removals.length === 1 ? "" : "s"} held${counted ? `, ${records} records` : ""} (run with --dry-run, then --fingerprint and --confirm-records)`;
349
+ }
350
+ async function syncDiscovered(cmd, opts) {
291
351
  const transport = getClientFromCommand(cmd);
292
- for (const { bundle } of await orderByUses(await discoverBundles({ cwd }))) try {
293
- const { applied, hash } = await syncBundle(transport, bundle);
294
- console.info(`${bundle.name} ${applied ? "applied" : "unchanged"} ${shortHash(hash)}`);
295
- } catch (error) {
296
- throw new Error(describeFailure(bundle.name, error));
352
+ const ordered = await orderByUses(await discoverBundles({ cwd: opts.cwd }));
353
+ checkSyncConfirmations(ordered.map(({ bundle }) => bundle.name), opts);
354
+ if (opts.dryRun) {
355
+ const plans = [];
356
+ for (const { bundle } of ordered) try {
357
+ plans.push({
358
+ bundle: bundle.name,
359
+ ...await previewBundleSync(transport, bundle)
360
+ });
361
+ } catch (error) {
362
+ throw new Error(describeFailure(bundle.name, error));
363
+ }
364
+ formatOutput(plans, getFormat(cmd));
365
+ return;
366
+ }
367
+ for (const { bundle } of ordered) {
368
+ const confirmedRecords = opts.confirmRecords[bundle.name];
369
+ const fingerprint = opts.fingerprint[bundle.name];
370
+ try {
371
+ const { applied, hash, pendingRemovals } = await syncBundle(transport, bundle, { ...confirmedRecords === void 0 || fingerprint === void 0 ? {} : {
372
+ fingerprint,
373
+ confirmedRecords
374
+ } });
375
+ console.info(`${bundle.name} ${applied ? "applied" : "unchanged"} ${shortHash(hash)}`);
376
+ const held = pendingRemovalsLine(bundle.name, pendingRemovals);
377
+ if (held) console.info(held);
378
+ } catch (error) {
379
+ const refused = confirmedRecords === void 0 || fingerprint === void 0 ? error : await refuseStale(error, bundle.name, {
380
+ fingerprint,
381
+ confirmedRecords
382
+ }, async () => {
383
+ const { fingerprint: current, totalRecords } = await previewBundleSync(transport, bundle);
384
+ return {
385
+ fingerprint: current,
386
+ total: totalRecords
387
+ };
388
+ }).catch((stale) => stale);
389
+ throw new Error(describeFailure(bundle.name, refused));
390
+ }
297
391
  }
298
392
  }
299
393
  /** Reject a locale typo instead of letting the API answer a 400 on a bare enum. */
@@ -350,7 +444,7 @@ function withAddOptions(command) {
350
444
  */
351
445
  function registerBundlesCommand(program) {
352
446
  const bundles = program.command("bundles").description("Sync and manage the workspace's bundles");
353
- bundles.command("sync").description("Discover the project's bundles (standards/bundles/*.ts) and sync each one").option("--cwd <dir>", "project root to discover bundles in").action((opts, cmd) => run(() => syncDiscovered(cmd, opts.cwd)));
447
+ bundles.command("sync").description("Discover the project's bundles (standards/bundles/*.ts) and sync each one; a removal is held until confirmed").option("--cwd <dir>", "project root to discover bundles in").option("--dry-run", "print each bundle's pieces, the removals it would make with their record counts, and its fingerprint").option("--fingerprint <bundle=fingerprint>", "the fingerprint `bundles sync --dry-run` printed for a bundle (repeatable)", collectFingerprint, {}).option("--confirm-records <bundle=n>", "apply the removals a bundle's sync holds, confirming the records they delete (repeatable); without it they are held", collectConfirmRecords, {}).action((opts, cmd) => run(() => syncDiscovered(cmd, opts)));
354
448
  bundles.command("list").description("List the bundles installed in the workspace").action((_opts, cmd) => run(async () => {
355
449
  const { installed } = await getAppClientFromCommand(cmd).bundles.list({});
356
450
  formatOutput(installed.map((row) => ({
@@ -379,7 +473,7 @@ function registerBundlesCommand(program) {
379
473
  }), getFormat(cmd));
380
474
  });
381
475
  });
382
- bundles.command("update").description("Update an added bundle as its plan showed; nothing is removed (--dry-run prints the plan)").argument("<name>", "bundle name").option("--dry-run", "print the update plan, piece by piece, and its fingerprint").option("--fingerprint <fingerprint>", "the fingerprint `bundles update --dry-run` printed").option("--take <pieceId>", "take the new version of a piece you changed (repeatable)", collectPieceId, []).option("--skip-missing <pieceId>", "do not reinstall a missing piece (repeatable)", collectPieceId, []).option("--decision <pieceId=choice>", "how to settle a collision: bind, skip or rename:<name> (repeatable)", collectDecision, {}).action((name, opts, cmd) => {
476
+ bundles.command("update").description("Update an added bundle as its plan showed; a piece the plan removes goes with its records only when --confirm-records matches (--dry-run prints the plan)").argument("<name>", "bundle name").option("--dry-run", "print the update plan, piece by piece, and its fingerprint").option("--fingerprint <fingerprint>", "the fingerprint `bundles update --dry-run` printed").option("--confirm-records <bundle=n>", "the records the plan's removals delete, as `--dry-run` counted them (only when it removes)", collectConfirmRecords, {}).option("--take <pieceId>", "take the new version of a piece you changed (repeatable)", collectPieceId, []).option("--skip-missing <pieceId>", "do not reinstall a missing piece (repeatable)", collectPieceId, []).option("--decision <pieceId=choice>", "how to settle a collision: bind, skip or rename:<name> (repeatable)", collectDecision, {}).action((name, opts, cmd) => {
383
477
  const fingerprint = opts.dryRun ? void 0 : requireFingerprint(opts.fingerprint, `bundles update ${name} --dry-run`);
384
478
  return run(async () => {
385
479
  const client = getAppClientFromCommand(cmd);
@@ -387,20 +481,60 @@ function registerBundlesCommand(program) {
387
481
  formatOutput(await client.bundles.updatePreview({ name }), getFormat(cmd));
388
482
  return;
389
483
  }
390
- const { applied } = await client.bundles.update({
391
- name,
392
- fingerprint,
393
- take: opts.take,
394
- skipMissing: opts.skipMissing,
395
- decisions: opts.decision
396
- });
397
- console.info(`${name} updated, ${applied} applied`);
484
+ const confirmedRecords = confirmedFor(name, opts.confirmRecords, { required: false });
485
+ try {
486
+ const { applied } = await client.bundles.update({
487
+ name,
488
+ fingerprint,
489
+ take: opts.take,
490
+ skipMissing: opts.skipMissing,
491
+ decisions: opts.decision,
492
+ ...confirmedRecords === void 0 ? {} : { confirmedRecords }
493
+ });
494
+ console.info(`${name} updated, ${applied} applied`);
495
+ } catch (error) {
496
+ await refuseStale(error, name, {
497
+ fingerprint,
498
+ confirmedRecords
499
+ }, async () => {
500
+ const { fingerprint: current, totalRecords } = await client.bundles.updatePreview({ name });
501
+ return {
502
+ fingerprint: current,
503
+ total: totalRecords
504
+ };
505
+ });
506
+ }
398
507
  });
399
508
  });
400
- bundles.command("remove").description("Remove a synced bundle; its pieces are demoted, never deleted. An added bundle is refused: uninstall it from the Bundles window").argument("<name>", "bundle name").option("--yes", "remove without confirmation").action((name, opts, cmd) => run(async () => {
401
- requireConfirmation(opts.yes, `Removing bundle "${name}" demotes its objects to workspace-owned. Re-run with "--yes" to confirm.`);
402
- await getAppClientFromCommand(cmd).bundles.remove({ name });
403
- console.info(`${name} removed`);
509
+ bundles.command("remove").description("Remove a synced bundle: its objects are deleted with their records, only when --confirm-records matches the count (--dry-run prints it). An added bundle is removed the same way").argument("<name>", "bundle name").option("--dry-run", "print what the removal deletes, its record counts and the fingerprint").option("--fingerprint <fingerprint>", "the fingerprint `bundles remove --dry-run` printed").option("--confirm-records <bundle=n>", "the records the removal deletes, as `--dry-run` counted them", collectConfirmRecords, {}).action((name, opts, cmd) => run(async () => {
510
+ const client = getAppClientFromCommand(cmd);
511
+ if (opts.dryRun) {
512
+ const preview = await client.bundles.removalPreview({ name });
513
+ formatOutput({
514
+ ...preview,
515
+ totalRecords: removalTotal(preview)
516
+ }, getFormat(cmd));
517
+ return;
518
+ }
519
+ const confirmation = {
520
+ confirmedRecords: confirmedFor(name, opts.confirmRecords, { required: true }),
521
+ fingerprint: requireFingerprint(opts.fingerprint, `bundles remove ${name} --dry-run`)
522
+ };
523
+ try {
524
+ await client.bundles.remove({
525
+ name,
526
+ ...confirmation
527
+ });
528
+ console.info(`${name} removed`);
529
+ } catch (error) {
530
+ await refuseStale(error, name, confirmation, async () => {
531
+ const preview = await client.bundles.removalPreview({ name });
532
+ return {
533
+ fingerprint: preview.fingerprint,
534
+ total: removalTotal(preview)
535
+ };
536
+ });
537
+ }
404
538
  }));
405
539
  }
406
540
  // src/commands/connectors.ts
@@ -412,10 +546,7 @@ const PROVIDERS = Object.keys({
412
546
  "google-calendar": true,
413
547
  "outlook-calendar": true
414
548
  });
415
- const CAPABILITIES = Object.keys({
416
- modify: true,
417
- send: true
418
- });
549
+ const CAPABILITIES = Object.keys({ modify: true });
419
550
  const MAIL_PROVIDERS = ["gmail", "outlook"];
420
551
  const CALENDAR_PROVIDERS = ["google-calendar", "outlook-calendar"];
421
552
  /**
@@ -424,14 +555,9 @@ const CALENDAR_PROVIDERS = ["google-calendar", "outlook-calendar"];
424
555
  */
425
556
  function connectDisclosure(provider, capabilities = []) {
426
557
  const modify = capabilities.includes("modify");
427
- const send = capabilities.includes("send");
428
558
  const lines = ["Standards will be able to:"];
429
- if (MAIL_PROVIDERS.includes(provider)) {
430
- lines.push("- read your emails and their attachments");
431
- if (modify || send && provider === "outlook") lines.push("- change your emails");
432
- if (send) lines.push("- send emails from this address");
433
- 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.");
434
- } else if (CALENDAR_PROVIDERS.includes(provider)) {
559
+ if (MAIL_PROVIDERS.includes(provider)) lines.push("- read your emails and their attachments", "- change your emails", "- send emails from this address", "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.");
560
+ else if (CALENDAR_PROVIDERS.includes(provider)) {
435
561
  lines.push("- read your events, their guests and their details");
436
562
  if (modify) lines.push("- create, change and delete your events");
437
563
  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.");
@@ -458,20 +584,29 @@ function registerConnectorsCommand(program) {
458
584
  connectors.command("list").description("List connector connections").option("--scope <scope>", "connection scope (tenant or actor)", "tenant").action(async (opts, cmd) => {
459
585
  formatOutput(await getAppClientFromCommand(cmd).connectors.list({ scope: resolveScope(opts.scope) }), getFormat(cmd));
460
586
  });
461
- connectors.command("connect").description("Check a provider and capabilities; the account itself is connected from the app").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((opts) => {
587
+ connectors.command("connect").description("Check a provider and capabilities; the account itself is connected from the app").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>", `calendar capability tiers beyond read (${CAPABILITIES.join(", ")}); a mailbox takes none`).option("--yes", "confirm the access a mailbox or calendar connection gives Standards").action((opts) => {
462
588
  if (!PROVIDERS.includes(opts.provider)) throw new ValidationError(`--provider must be one of: ${PROVIDERS.join(", ")}`, []);
463
589
  const capabilities = parseCapabilities(opts.capabilities);
590
+ if (capabilities && MAIL_PROVIDERS.includes(opts.provider)) throw new ValidationError("--capabilities applies to calendars only: a mailbox always asks for reading, changing and sending", []);
464
591
  const disclosure = connectDisclosure(opts.provider, capabilities);
465
592
  if (disclosure && !opts.yes) throw new Error(`${disclosure}\n\nRe-run with "--yes" to continue.`);
466
593
  process.stdout.write(`${opts.provider} is connected from the Standards app: open Connectors in the sidebar and connect it there.\n`);
467
594
  });
468
- connectors.command("disconnect").description("Disconnect (delete) a connector connection").argument("<id>", "connection ID").option("--yes", "disconnect without confirmation").option("--remove-imported", "also remove what a mail or calendar connection imported (kept by default)").action(async (id, opts, cmd) => {
469
- if (!opts.yes) throw new Error("Disconnecting a connector is explicit. Re-run with \"--yes\" to confirm.");
595
+ connectors.command("disconnect").description("Disconnect a connector connection: it stops syncing, and everything it imported stays. Reconnect it from the app").argument("<id>", "connection ID").action(async (id, _opts, cmd) => {
596
+ await getAppClientFromCommand(cmd).connectors.disconnect({ id });
597
+ printDone(`Connector connection ${id} disconnected. Everything it imported stays.`);
598
+ });
599
+ connectors.command("retry").description("Retry a failing connector connection now: its backoff is lifted and one sync pass is scheduled. A connection that does not sync (disconnected, waiting for a reconnect) is left as it is").argument("<id>", "connection ID").action(async (id, _opts, cmd) => {
600
+ await getAppClientFromCommand(cmd).connectors.retrySync({ id });
601
+ printDone(`Connector connection ${id}: retry requested. A connection that syncs gets a pass now; one that does not is left as it is.`);
602
+ });
603
+ connectors.command("delete").description("Delete a connector connection. Deleting a mailbox's connection deletes its mail: every email no other connected mailbox holds").argument("<id>", "connection ID").option("--yes", "delete without confirmation").option("--remove-imported", "also remove the meetings a calendar connection imported (kept by default)").action(async (id, opts, cmd) => {
604
+ if (!opts.yes) throw new Error("This deletes the connection. With a mailbox it also deletes its mail: every email no other connected mailbox holds. Re-run with \"--yes\" to confirm.");
470
605
  await getAppClientFromCommand(cmd).connectors.delete({
471
606
  id,
472
607
  ...opts.removeImported ? { removeImported: "true" } : {}
473
608
  });
474
- printDone(`Connector connection ${id} disconnected.`);
609
+ printDone(`Connector connection ${id} deleted. A mailbox's mail is removed in the background.`);
475
610
  });
476
611
  }
477
612
  // src/commands/decisions.ts
package/guide/core.md CHANGED
@@ -50,7 +50,7 @@ One exception: `standards schema diff` exits 0 when equal, 1 when there are diff
50
50
  - Pipe JSON into `jq`: `standards records list contacts | jq '.data[].id'`.
51
51
  - Collections are `{ data, page: { hasMore, total, countMode } }`. For a row table, select `.data` first.
52
52
  - Single-quote JSON arguments: `--data '{"email":"a@b.com"}'`.
53
- - Destructive commands refuse without `--yes`. Preview first with `--dry-run` where it exists.
53
+ - Destructive commands refuse without `--yes`. Preview first with `--dry-run` where it exists. A bundle removal confirms with `--confirm-records <bundle>=<n>`, the record count its `--dry-run` printed, instead.
54
54
  - Never put a secret in argv. Pipe it: `printf %s "$VALUE" | standards secrets set KEY`.
55
55
 
56
56
  ## Command groups
@@ -62,7 +62,7 @@ One exception: `standards schema diff` exits 0 when equal, 1 when there are diff
62
62
  | `files` | upload, list, download and delete uploaded files | `records` |
63
63
  | `meetings` | read meetings and their transcripts | `records` |
64
64
  | `schema` | inspect objects; `pull` and `diff` deployed source vs runtime | `schema-and-bundles` |
65
- | `bundles` | sync, add and update bundles | `schema-and-bundles` |
65
+ | `bundles` | sync, add, update and remove bundles | `schema-and-bundles` |
66
66
  | `skills` | pull, push, search, add workspace skills | `schema-and-bundles` |
67
67
  | `mcp` | remote MCP servers mounted into agents | `schema-and-bundles` |
68
68
  | `connectors` | mail and calendar connections | `schema-and-bundles` |
@@ -26,22 +26,33 @@ standards bundles status # current, update or repair, and wh
26
26
  standards bundles available --locale fr # bundles the workspace may add as copies it owns
27
27
  ```
28
28
 
29
+ A sync holds its removals (an attribute, option, view or object a bundle no longer declares) and prints `<name>: <n> removals held, <r> records`. Confirm them with the dry run's fingerprint and `totalRecords`, one pair per bundle:
30
+
31
+ ```bash
32
+ standards bundles sync --dry-run # per bundle: pieces, removals with record counts, totalRecords, fingerprint
33
+ standards bundles sync --fingerprint crm=<fp> --confirm-records crm=<n> # applies crm's removals: values and records go
34
+ ```
35
+
29
36
  Add and update in two steps: plan with `--dry-run`, then apply with the plan's `fingerprint`.
30
37
 
31
38
  ```bash
32
39
  standards bundles add crm --with-samples --dry-run # plan: new and colliding pieces + fingerprint
33
40
  standards bundles add crm --with-samples --fingerprint <fp> \
34
41
  --decision <pieceId>=bind --decision <pieceId>=rename:opportunities --decision <pieceId>=skip
35
- standards bundles update crm --dry-run # pieces by group + fingerprint
42
+ standards bundles update crm --dry-run # pieces by group, removals, totalRecords + fingerprint
36
43
  standards bundles update crm --fingerprint <fp> --take deals --skip-missing notes --decision companies=bind
37
- standards bundles remove <name> --yes
44
+ standards bundles update crm --fingerprint <fp> --confirm-records crm=<n> # when the plan removes pieces
45
+ standards bundles remove <name> --dry-run # objects with their records, totalRecords + fingerprint
46
+ standards bundles remove <name> --fingerprint <fp> --confirm-records <name>=<n>
38
47
  ```
39
48
 
40
49
  - Without `--dry-run`, `--fingerprint` is required. Pass the same names and flags as the plan.
41
50
  - The server refuses a stale fingerprint: the workspace changed since the plan. Plan again.
42
51
  - `--decision` settles one collision: `bind`, `skip` or `rename:<name>`.
43
- - `update` keeps a piece you changed unless `--take` names it, restores a missing piece unless `--skip-missing` names it, and never removes a piece.
44
- - `remove` demotes a synced bundle's objects to workspace-owned and deletes nothing. An added bundle is uninstalled from the app's Bundles window.
52
+ - `--fingerprint` is `<bundle>=<fp>` on `sync` (it pushes several bundles, repeatable) and a plain value on `add`, `update` and `remove`. `--confirm-records` is always `<bundle>=<n>`.
53
+ - `update` keeps a piece you changed unless `--take` names it, restores a missing piece unless `--skip-missing` names it, and removes a piece the new version dropped only with `--confirm-records`.
54
+ - `remove` deletes the bundle's objects with every record (private and trashed included) and its views, synced or added alike. It needs `architect:delete`.
55
+ - A count other than the server's own recount, or a workspace changed since the dry run, is refused with `The workspace changed since the preview; preview again` and the total to confirm now. Nothing is deleted.
45
56
  - `sync` prints `<name> applied|unchanged <hash7>` per bundle. A 409 names the owner of the conflicting piece.
46
57
 
47
58
  ## Skills
@@ -87,8 +98,11 @@ Mail and calendar connections. The account itself is connected from the app (Con
87
98
  ```bash
88
99
  standards connectors providers # providers this deployment can connect
89
100
  standards connectors list [--scope tenant|actor]
90
- standards connectors connect --provider gmail [--capabilities modify,send] [--yes] # checks, then points to the app
91
- standards connectors disconnect <id> --yes [--remove-imported]
101
+ standards connectors connect --provider gmail [--yes] # checks, then points to the app
102
+ standards connectors connect --provider google-calendar [--capabilities modify] [--yes] # a calendar may also ask to write
103
+ standards connectors retry <id> # a failing connection syncs again now
104
+ standards connectors disconnect <id> # stops syncing, keeps everything
105
+ standards connectors delete <id> --yes [--remove-imported]
92
106
  ```
93
107
 
94
- `disconnect` keeps what the connection imported unless `--remove-imported` is passed.
108
+ `retry` lifts a failing connection's backoff and schedules one sync pass; a connection that does not sync is left as it is. `disconnect` stops a connection syncing and keeps everything it imported. `delete` deletes it: deleting a mailbox's connection deletes its mail, every email no other connected mailbox holds, removed in the background once the command answers. A workspace mailbox is deleted from the app only (an API key never holds `workspace-mailboxes:delete`). A calendar keeps the meetings it imported unless `--remove-imported` is passed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@standards/cli",
3
- "version": "1.0.0-alpha.442",
3
+ "version": "1.0.0-alpha.444",
4
4
  "description": "CLI tool to interact with Standards API",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,9 +24,9 @@
24
24
  "node": ">=22"
25
25
  },
26
26
  "dependencies": {
27
- "@standards/client": "1.0.0-alpha.442",
28
- "@standards/constants": "1.0.0-alpha.442",
29
- "@standards/schema": "1.0.0-alpha.442",
27
+ "@standards/client": "1.0.0-alpha.444",
28
+ "@standards/constants": "1.0.0-alpha.444",
29
+ "@standards/schema": "1.0.0-alpha.444",
30
30
  "cli-table3": "^0.6.5",
31
31
  "commander": "^13.1.0",
32
32
  "jiti": "2.7.0",