@stndrds/cli 1.0.0-alpha.292 → 1.0.0-alpha.294

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 (3) hide show
  1. package/README.md +34 -0
  2. package/dist/bin.mjs +982 -153
  3. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # Standards CLI
2
+
3
+ The Standards CLI connects a terminal to a Standards instance. See the public
4
+ [CLI guide](../../content/docs/07-cli.mdx) for installation and the full command surface.
5
+
6
+ ## Run this computer as a paired device
7
+
8
+ Remote device execution is an optional server capability. Once an operator has enabled it,
9
+ create a named profile, pair the current computer, and keep the foreground worker running:
10
+
11
+ ```bash
12
+ standards login --name production --url https://standards.example/v1 --key "$STANDARDS_API_KEY"
13
+ standards device pair --name "Build Mac"
14
+ standards device serve
15
+ ```
16
+
17
+ The worker supports macOS and Linux and makes outbound HTTPS requests only. It executes each
18
+ approved command in the foreground using the user's local account and permissions. There is no
19
+ privilege escalation, background service installation, PTY, file transfer, or GUI control.
20
+
21
+ Commands time out after 10 minutes by default and may not exceed 60 minutes. Standard output and
22
+ standard error are each limited to 30,000 characters. Stop the worker with `Ctrl+C`; inspect it
23
+ with `standards device status`; revoke its credential with `standards device revoke`.
24
+
25
+ Interactive harnesses are outside the V0 contract. Invoke installed tools in their
26
+ non-interactive mode, for example:
27
+
28
+ ```bash
29
+ codex exec --json "Run the repository checks"
30
+ claude -p "Summarize the failing tests"
31
+ ```
32
+
33
+ Every agent command targeting a device requires explicit user approval. Calls to `exec_command`
34
+ without a `deviceId` continue to use the agent sandbox.
package/dist/bin.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/program.ts
4
- import chalk9 from "chalk";
4
+ import chalk10 from "chalk";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/client.ts
@@ -129,6 +129,13 @@ function getClientFromCommand(cmd) {
129
129
  }
130
130
  return createClient({ apiUrl: root.apiUrl, apiKey: root.apiKey, tenantId: root.tenant });
131
131
  }
132
+ function requireTenant(cmd, message) {
133
+ const { tenant } = getGlobalOptions(cmd);
134
+ if (!tenant) {
135
+ throw new Error(message);
136
+ }
137
+ return tenant;
138
+ }
132
139
  function messageOf(error) {
133
140
  return error instanceof Error ? error.message : String(error);
134
141
  }
@@ -301,6 +308,833 @@ function registerConnectorsCommand(program) {
301
308
  });
302
309
  }
303
310
 
311
+ // src/commands/device.ts
312
+ import { hostname } from "os";
313
+ import { SchemaError as SchemaError2, SchemaErrorCode as SchemaErrorCode2 } from "@stndrds/schema";
314
+
315
+ // src/config.ts
316
+ import { randomUUID } from "crypto";
317
+ import { chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
318
+ import { homedir } from "os";
319
+ import { dirname, join } from "path";
320
+ var DEFAULT_API_URL = "http://localhost:4100/v1";
321
+ var ENV_PROFILE_NAME = "sandbox";
322
+ function getDefaultApiUrl() {
323
+ return DEFAULT_API_URL;
324
+ }
325
+ function getConfigPath() {
326
+ const configDir = process.env.STANDARDS_CONFIG_DIR ?? join(homedir(), ".standards");
327
+ return join(configDir, "config.json");
328
+ }
329
+ async function readConfig() {
330
+ try {
331
+ const raw = await readFile(getConfigPath(), "utf8");
332
+ const parsed = JSON.parse(raw);
333
+ return {
334
+ ...parsed.installationId ? { installationId: parsed.installationId } : {},
335
+ currentProfile: parsed.currentProfile,
336
+ profiles: parsed.profiles ?? {}
337
+ };
338
+ } catch (error) {
339
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
340
+ return { profiles: {} };
341
+ }
342
+ throw error;
343
+ }
344
+ }
345
+ async function writeConfig(config) {
346
+ const path = getConfigPath();
347
+ const directory = dirname(path);
348
+ await mkdir(directory, { recursive: true, mode: 448 });
349
+ await chmod(directory, 448);
350
+ await writeFile(path, `${JSON.stringify(config, null, 2)}
351
+ `, { mode: 384 });
352
+ await chmod(path, 384);
353
+ }
354
+ function normalizeApiOrigin(apiUrl) {
355
+ try {
356
+ return new URL(apiUrl).origin;
357
+ } catch {
358
+ return apiUrl.replace(/\/+$/, "");
359
+ }
360
+ }
361
+ async function upsertProfile(input2) {
362
+ const config = await readConfig();
363
+ const existingProfile = config.profiles[input2.name];
364
+ const device = existingProfile?.device && normalizeApiOrigin(existingProfile.apiUrl) === normalizeApiOrigin(input2.apiUrl) ? existingProfile.device : void 0;
365
+ config.profiles[input2.name] = {
366
+ apiUrl: input2.apiUrl,
367
+ apiKey: input2.apiKey,
368
+ ...device ? { device } : {}
369
+ };
370
+ config.currentProfile = input2.name;
371
+ await writeConfig(config);
372
+ }
373
+ async function getOrCreateInstallationId() {
374
+ const config = await readConfig();
375
+ if (config.installationId) return config.installationId;
376
+ const installationId = randomUUID();
377
+ config.installationId = installationId;
378
+ await writeConfig(config);
379
+ return installationId;
380
+ }
381
+ async function getStoredDeviceCredential(profileName) {
382
+ const config = await readConfig();
383
+ return config.profiles[profileName]?.device ?? null;
384
+ }
385
+ async function storeDeviceCredential(profileName, credential) {
386
+ const config = await readConfig();
387
+ const profile = config.profiles[profileName];
388
+ if (!profile) return false;
389
+ profile.device = credential;
390
+ await writeConfig(config);
391
+ return true;
392
+ }
393
+ async function removeDeviceCredential(profileName) {
394
+ const config = await readConfig();
395
+ const profile = config.profiles[profileName];
396
+ if (!profile?.device) return false;
397
+ profile.device = void 0;
398
+ await writeConfig(config);
399
+ return true;
400
+ }
401
+ async function setCurrentProfile(name) {
402
+ const config = await readConfig();
403
+ if (!config.profiles[name]) {
404
+ throw new Error(`Unknown Standards instance "${name}"`);
405
+ }
406
+ config.currentProfile = name;
407
+ await writeConfig(config);
408
+ }
409
+ async function removeProfile(name) {
410
+ const config = await readConfig();
411
+ const profileName = name ?? config.currentProfile;
412
+ if (!profileName) return;
413
+ delete config.profiles[profileName];
414
+ if (config.currentProfile === profileName) {
415
+ config.currentProfile = void 0;
416
+ }
417
+ await writeConfig(config);
418
+ }
419
+ async function listProfiles() {
420
+ const config = await readConfig();
421
+ const profiles = Object.entries(config.profiles).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({
422
+ name,
423
+ apiUrl: profile.apiUrl,
424
+ current: config.currentProfile === name
425
+ }));
426
+ const envProfile = getEnvProfile();
427
+ if (envProfile) {
428
+ profiles.push({
429
+ name: ENV_PROFILE_NAME,
430
+ apiUrl: envProfile.apiUrl,
431
+ current: !config.currentProfile,
432
+ source: "env"
433
+ });
434
+ }
435
+ return profiles;
436
+ }
437
+ async function getActiveProfile() {
438
+ const config = await readConfig();
439
+ if (!config.currentProfile) {
440
+ const envProfile = getEnvProfile();
441
+ return envProfile ? { name: ENV_PROFILE_NAME, ...envProfile } : null;
442
+ }
443
+ const profile = config.profiles[config.currentProfile];
444
+ if (!profile) return null;
445
+ return { name: config.currentProfile, ...profile };
446
+ }
447
+ function getEnvProfile() {
448
+ if (!process.env.STANDARDS_API_KEY) return null;
449
+ return {
450
+ apiUrl: process.env.STANDARDS_API_URL ?? DEFAULT_API_URL,
451
+ apiKey: process.env.STANDARDS_API_KEY
452
+ };
453
+ }
454
+ async function resolveCliConfig(options) {
455
+ if (options.apiUrl || options.apiKey) {
456
+ return {
457
+ apiUrl: options.apiUrl ?? process.env.STANDARDS_API_URL ?? DEFAULT_API_URL,
458
+ apiKey: options.apiKey ?? process.env.STANDARDS_API_KEY
459
+ };
460
+ }
461
+ const config = await readConfig();
462
+ const profileName = options.instance ?? config.currentProfile;
463
+ if (profileName) {
464
+ const profile = config.profiles[profileName];
465
+ if (!profile) throw new Error(`Unknown Standards instance "${profileName}"`);
466
+ return {
467
+ apiUrl: profile.apiUrl,
468
+ apiKey: profile.apiKey,
469
+ profileName
470
+ };
471
+ }
472
+ return {
473
+ apiUrl: process.env.STANDARDS_API_URL ?? DEFAULT_API_URL,
474
+ apiKey: process.env.STANDARDS_API_KEY
475
+ };
476
+ }
477
+
478
+ // src/device/worker-client.ts
479
+ import { SchemaError, SchemaErrorCode } from "@stndrds/schema";
480
+ var REQUEST_TIMEOUT_MS = 3e4;
481
+ var DeviceWorkerRequestError = class extends SchemaError {
482
+ constructor(statusCode, message, code = SchemaErrorCode.REPOSITORY_QUERY_FAILED) {
483
+ super(message, code, { statusCode });
484
+ this.statusCode = statusCode;
485
+ this.name = "DeviceWorkerRequestError";
486
+ }
487
+ };
488
+ var DeviceCredentialRejectedError = class extends DeviceWorkerRequestError {
489
+ constructor() {
490
+ super(
491
+ 401,
492
+ "Device credential rejected; run standards device pair again",
493
+ SchemaErrorCode.ACCESS_DENIED
494
+ );
495
+ this.name = "DeviceCredentialRejectedError";
496
+ }
497
+ };
498
+ function isRecord(value) {
499
+ return value !== null && typeof value === "object" && !Array.isArray(value);
500
+ }
501
+ async function readJson(response) {
502
+ const text = await response.text();
503
+ if (!text) return void 0;
504
+ try {
505
+ return JSON.parse(text);
506
+ } catch {
507
+ return void 0;
508
+ }
509
+ }
510
+ function messageFrom(value, fallback) {
511
+ return isRecord(value) && typeof value.message === "string" ? value.message : fallback;
512
+ }
513
+ function parseLease(value) {
514
+ if (!isRecord(value)) throw new DeviceWorkerRequestError(200, "Invalid lease response");
515
+ const leaseToken = value.leaseToken;
516
+ if (!isRecord(value.command)) {
517
+ throw new DeviceWorkerRequestError(200, "Invalid lease response");
518
+ }
519
+ const leasedCommand = value.command;
520
+ const { id, intent, command, cwd, timeoutMs } = leasedCommand;
521
+ if (typeof id !== "string" || typeof leaseToken !== "string" || typeof intent !== "string" || typeof command !== "string" || cwd !== void 0 && cwd !== null && typeof cwd !== "string" || typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs)) {
522
+ throw new DeviceWorkerRequestError(200, "Invalid lease response");
523
+ }
524
+ return {
525
+ id,
526
+ leaseToken,
527
+ intent,
528
+ command,
529
+ timeoutMs,
530
+ ...typeof cwd === "string" ? { cwd } : {}
531
+ };
532
+ }
533
+ function parseHeartbeat(value) {
534
+ if (!isRecord(value)) {
535
+ throw new DeviceWorkerRequestError(200, "Invalid heartbeat response");
536
+ }
537
+ const { cancel } = value;
538
+ if (typeof cancel !== "boolean") {
539
+ throw new DeviceWorkerRequestError(200, "Invalid heartbeat response");
540
+ }
541
+ const reason = value.reason;
542
+ if (reason !== void 0 && reason !== "cancelled" && reason !== "revoked") {
543
+ throw new DeviceWorkerRequestError(200, "Invalid heartbeat response");
544
+ }
545
+ return { cancel, ...reason === void 0 ? {} : { reason } };
546
+ }
547
+ function isAbortError(error) {
548
+ return error instanceof DOMException && error.name === "AbortError";
549
+ }
550
+ var DeviceWorkerClient = class {
551
+ constructor(options) {
552
+ this.options = options;
553
+ this.fetch = options.fetch ?? fetch;
554
+ this.apiUrl = options.apiUrl.replace(/\/+$/, "");
555
+ }
556
+ async request(path, input2 = {}) {
557
+ const timeoutSignal = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
558
+ const signal = input2.signal ? AbortSignal.any([input2.signal, timeoutSignal]) : timeoutSignal;
559
+ let response;
560
+ try {
561
+ response = await this.fetch(`${this.apiUrl}/device-worker${path}`, {
562
+ method: "POST",
563
+ signal,
564
+ headers: {
565
+ authorization: `Device ${this.options.token}`,
566
+ "content-type": "application/json",
567
+ ...input2.leaseToken ? { "x-device-lease-token": input2.leaseToken } : {}
568
+ },
569
+ ...input2.body === void 0 ? {} : { body: JSON.stringify(input2.body) }
570
+ });
571
+ } catch (error) {
572
+ if (input2.signal?.aborted) throw new DOMException("Aborted", "AbortError");
573
+ if (isAbortError(error)) throw error;
574
+ throw new DeviceWorkerRequestError(0, "Device worker request failed");
575
+ }
576
+ if (response.status === 401) throw new DeviceCredentialRejectedError();
577
+ if (!response.ok) {
578
+ const body = await readJson(response);
579
+ throw new DeviceWorkerRequestError(
580
+ response.status,
581
+ messageFrom(body, `Device worker request failed with status ${response.status}`),
582
+ response.status === 409 ? SchemaErrorCode.CONFLICT : SchemaErrorCode.REPOSITORY_QUERY_FAILED
583
+ );
584
+ }
585
+ return response;
586
+ }
587
+ async lease(signal) {
588
+ const response = await this.request("/commands/lease", {
589
+ body: { version: "1", capabilities: ["exec"] },
590
+ signal
591
+ });
592
+ if (response.status === 204) return null;
593
+ return parseLease(await readJson(response));
594
+ }
595
+ async markRunning(commandId, leaseToken) {
596
+ await this.request(`/commands/${commandId}/running`, { leaseToken });
597
+ }
598
+ async heartbeat(commandId, leaseToken, signal) {
599
+ const response = await this.request(`/commands/${commandId}/heartbeat`, {
600
+ leaseToken,
601
+ signal
602
+ });
603
+ return parseHeartbeat(await readJson(response));
604
+ }
605
+ async output(commandId, leaseToken, stdout, stderr) {
606
+ await this.request(`/commands/${commandId}/output`, {
607
+ leaseToken,
608
+ body: { stdout, stderr }
609
+ });
610
+ }
611
+ async complete(commandId, leaseToken, result) {
612
+ await this.request(`/commands/${commandId}/complete`, {
613
+ leaseToken,
614
+ body: {
615
+ exitCode: result.exitCode,
616
+ success: result.success,
617
+ ...result.errorCode ? { errorCode: result.errorCode } : {}
618
+ }
619
+ });
620
+ }
621
+ };
622
+
623
+ // src/device/process-runner.ts
624
+ import { spawn } from "child_process";
625
+ import { constants } from "fs";
626
+ import { access, stat } from "fs/promises";
627
+ import { isAbsolute } from "path";
628
+ import { StringDecoder } from "string_decoder";
629
+ var OUTPUT_LIMIT = 3e4;
630
+ var TERMINATION_GRACE_MS = 5e3;
631
+ var TERMINATION_POLL_MS = 50;
632
+ var BoundedTextBuffer = class {
633
+ constructor() {
634
+ this.decoder = new StringDecoder("utf8");
635
+ this.decoderFlushed = false;
636
+ this.value = "";
637
+ this.truncated = false;
638
+ }
639
+ append(chunk) {
640
+ if (this.truncated) return;
641
+ const text = typeof chunk === "string" ? chunk : this.decoder.write(Buffer.from(chunk));
642
+ this.appendText(text);
643
+ }
644
+ appendText(text) {
645
+ const remaining = OUTPUT_LIMIT - this.value.length;
646
+ if (text.length <= remaining) {
647
+ this.value += text;
648
+ return;
649
+ }
650
+ this.value += text.slice(0, Math.max(remaining, 0));
651
+ this.truncated = true;
652
+ }
653
+ toString() {
654
+ if (!this.decoderFlushed) {
655
+ this.decoderFlushed = true;
656
+ this.appendText(this.decoder.end());
657
+ }
658
+ return this.truncated ? `${this.value}
659
+ ... output truncated to ${OUTPUT_LIMIT} chars` : this.value;
660
+ }
661
+ };
662
+ function isMissingProcess(error) {
663
+ return error instanceof Error && "code" in error && error.code === "ESRCH";
664
+ }
665
+ function signalProcessGroup(pid, signal, kill) {
666
+ try {
667
+ kill(-pid, signal);
668
+ return true;
669
+ } catch (error) {
670
+ if (isMissingProcess(error)) return false;
671
+ throw error;
672
+ }
673
+ }
674
+ function beginProcessGroupTermination(pid, deps = {}) {
675
+ const kill = deps.kill ?? process.kill.bind(process);
676
+ const setTimer = deps.setTimer ?? setTimeout;
677
+ const clearTimer = deps.clearTimer ?? clearTimeout;
678
+ return new Promise((resolve, reject) => {
679
+ let settled = false;
680
+ let escalationTimer;
681
+ let pollTimer;
682
+ function clearTimers() {
683
+ if (escalationTimer) clearTimer(escalationTimer);
684
+ if (pollTimer) clearTimer(pollTimer);
685
+ }
686
+ function finish() {
687
+ if (settled) return;
688
+ settled = true;
689
+ clearTimers();
690
+ resolve();
691
+ }
692
+ function fail(error) {
693
+ if (settled) return;
694
+ settled = true;
695
+ clearTimers();
696
+ reject(error);
697
+ }
698
+ function poll() {
699
+ if (settled) return;
700
+ try {
701
+ if (!signalProcessGroup(pid, 0, kill)) {
702
+ finish();
703
+ return;
704
+ }
705
+ pollTimer = setTimer(poll, TERMINATION_POLL_MS);
706
+ } catch (error) {
707
+ fail(error);
708
+ }
709
+ }
710
+ try {
711
+ if (!signalProcessGroup(pid, "SIGTERM", kill)) {
712
+ finish();
713
+ return;
714
+ }
715
+ escalationTimer = setTimer(() => {
716
+ try {
717
+ if (!signalProcessGroup(pid, 0, kill)) {
718
+ finish();
719
+ return;
720
+ }
721
+ signalProcessGroup(pid, "SIGKILL", kill);
722
+ } catch (error) {
723
+ fail(error);
724
+ }
725
+ }, TERMINATION_GRACE_MS);
726
+ pollTimer = setTimer(poll, TERMINATION_POLL_MS);
727
+ } catch (error) {
728
+ fail(error);
729
+ }
730
+ });
731
+ }
732
+ async function resolveShell(shell = process.env.SHELL) {
733
+ if (!(shell && isAbsolute(shell))) return "/bin/sh";
734
+ try {
735
+ const metadata = await stat(shell);
736
+ if (!metadata.isFile()) return "/bin/sh";
737
+ await access(shell, constants.X_OK);
738
+ return shell;
739
+ } catch {
740
+ return "/bin/sh";
741
+ }
742
+ }
743
+ function terminalResult(reason, stdout, stderr) {
744
+ return {
745
+ stdout: stdout.toString(),
746
+ stderr: stderr.toString(),
747
+ exitCode: reason === "timed_out" ? 124 : 130,
748
+ success: false,
749
+ errorCode: reason
750
+ };
751
+ }
752
+ async function runDeviceProcess(input2) {
753
+ if (input2.signal?.aborted) {
754
+ return { stdout: "", stderr: "", exitCode: 130, success: false, errorCode: "cancelled" };
755
+ }
756
+ const shell = await resolveShell();
757
+ if (input2.signal?.aborted) {
758
+ return { stdout: "", stderr: "", exitCode: 130, success: false, errorCode: "cancelled" };
759
+ }
760
+ const stdout = new BoundedTextBuffer();
761
+ const stderr = new BoundedTextBuffer();
762
+ return new Promise((resolve) => {
763
+ let settled = false;
764
+ let terminationReason;
765
+ let termination;
766
+ let child;
767
+ function finish(result) {
768
+ if (settled) return;
769
+ settled = true;
770
+ clearTimeout(timeout);
771
+ input2.signal?.removeEventListener("abort", cancel);
772
+ resolve(result);
773
+ }
774
+ function finishTerminationFailure(error) {
775
+ finish({
776
+ stdout: stdout.toString(),
777
+ stderr: error instanceof Error ? error.message : String(error),
778
+ exitCode: 1,
779
+ success: false,
780
+ errorCode: "execution_failed"
781
+ });
782
+ }
783
+ function terminate(reason) {
784
+ if (settled || terminationReason || !child?.pid) return;
785
+ terminationReason = reason;
786
+ termination = beginProcessGroupTermination(child.pid);
787
+ termination.catch(finishTerminationFailure);
788
+ }
789
+ function cancel() {
790
+ terminate("cancelled");
791
+ }
792
+ const timeout = setTimeout(() => terminate("timed_out"), input2.timeoutMs);
793
+ try {
794
+ const spawned2 = spawn(shell, ["-lc", input2.command], {
795
+ cwd: input2.cwd,
796
+ env: process.env,
797
+ detached: true,
798
+ stdio: ["ignore", "pipe", "pipe"]
799
+ });
800
+ child = spawned2;
801
+ } catch (error) {
802
+ finish({
803
+ stdout: "",
804
+ stderr: error instanceof Error ? error.message : String(error),
805
+ exitCode: 1,
806
+ success: false,
807
+ errorCode: "execution_failed"
808
+ });
809
+ return;
810
+ }
811
+ input2.signal?.addEventListener("abort", cancel, { once: true });
812
+ if (input2.signal?.aborted) cancel();
813
+ const spawned = child;
814
+ spawned.stdout.on("data", (chunk) => stdout.append(chunk));
815
+ spawned.stderr.on("data", (chunk) => stderr.append(chunk));
816
+ spawned.once("error", (error) => {
817
+ finish({
818
+ stdout: stdout.toString(),
819
+ stderr: error.message,
820
+ exitCode: 1,
821
+ success: false,
822
+ errorCode: "execution_failed"
823
+ });
824
+ });
825
+ spawned.once("close", (code) => {
826
+ if (terminationReason) {
827
+ const reason = terminationReason;
828
+ (termination ?? Promise.resolve()).then(
829
+ () => finish(terminalResult(reason, stdout, stderr)),
830
+ finishTerminationFailure
831
+ );
832
+ return;
833
+ }
834
+ const exitCode = code ?? 1;
835
+ finish({
836
+ stdout: stdout.toString(),
837
+ stderr: stderr.toString(),
838
+ exitCode,
839
+ success: exitCode === 0,
840
+ ...code === null ? { errorCode: "execution_failed" } : {}
841
+ });
842
+ });
843
+ });
844
+ }
845
+
846
+ // src/device/worker-loop.ts
847
+ var HEARTBEAT_INTERVAL_MS = 2e3;
848
+ var INITIAL_RETRY_MS = 250;
849
+ var MAX_RETRY_MS = 1e4;
850
+ var RETRY_JITTER_MS = 250;
851
+ function computeRetryDelay(attempt, random = Math.random) {
852
+ const exponential = Math.min(INITIAL_RETRY_MS * 2 ** attempt, MAX_RETRY_MS);
853
+ return exponential + Math.floor(random() * RETRY_JITTER_MS);
854
+ }
855
+ function sleepWithAbort(milliseconds, signal) {
856
+ if (signal?.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
857
+ return new Promise((resolve, reject) => {
858
+ function abort() {
859
+ clearTimeout(timer);
860
+ reject(new DOMException("Aborted", "AbortError"));
861
+ }
862
+ const timer = setTimeout(() => {
863
+ signal?.removeEventListener("abort", abort);
864
+ resolve();
865
+ }, milliseconds);
866
+ signal?.addEventListener("abort", abort, { once: true });
867
+ });
868
+ }
869
+ function isAbortError2(error) {
870
+ return error instanceof DOMException && error.name === "AbortError";
871
+ }
872
+ function isRetryableIdleError(error) {
873
+ return error instanceof DeviceWorkerRequestError && (error.statusCode === 0 || error.statusCode >= 500);
874
+ }
875
+ function isLeaseConflict(error) {
876
+ return error instanceof DeviceWorkerRequestError && error.statusCode === 409;
877
+ }
878
+ async function executeLease(command, input2, runProcess, sleep) {
879
+ try {
880
+ await input2.client.markRunning(command.id, command.leaseToken);
881
+ } catch (error) {
882
+ if (error instanceof DeviceCredentialRejectedError) throw error;
883
+ return;
884
+ }
885
+ input2.onEvent?.({ type: "running", commandId: command.id, intent: command.intent });
886
+ const commandController = new AbortController();
887
+ const heartbeatController = new AbortController();
888
+ let completionAllowed = true;
889
+ let heartbeatFailure;
890
+ function abortCommand() {
891
+ commandController.abort();
892
+ }
893
+ input2.signal.addEventListener("abort", abortCommand, { once: true });
894
+ const processPromise = runProcess({
895
+ command: command.command,
896
+ cwd: command.cwd,
897
+ timeoutMs: command.timeoutMs,
898
+ signal: commandController.signal
899
+ });
900
+ const heartbeatPromise = (async () => {
901
+ while (!heartbeatController.signal.aborted) {
902
+ try {
903
+ await sleep(HEARTBEAT_INTERVAL_MS, heartbeatController.signal);
904
+ const heartbeat = await input2.client.heartbeat(
905
+ command.id,
906
+ command.leaseToken,
907
+ heartbeatController.signal
908
+ );
909
+ if (heartbeat.cancel) {
910
+ if (heartbeat.reason === "revoked") completionAllowed = false;
911
+ commandController.abort();
912
+ return;
913
+ }
914
+ } catch (error) {
915
+ if (heartbeatController.signal.aborted && isAbortError2(error)) return;
916
+ heartbeatFailure = error;
917
+ completionAllowed = false;
918
+ commandController.abort();
919
+ return;
920
+ }
921
+ }
922
+ })();
923
+ let result;
924
+ try {
925
+ result = await processPromise;
926
+ } catch (error) {
927
+ result = {
928
+ stdout: "",
929
+ stderr: error instanceof Error ? error.message : String(error),
930
+ exitCode: 1,
931
+ success: false,
932
+ errorCode: "execution_failed"
933
+ };
934
+ } finally {
935
+ heartbeatController.abort();
936
+ await heartbeatPromise;
937
+ input2.signal.removeEventListener("abort", abortCommand);
938
+ }
939
+ if (heartbeatFailure instanceof DeviceCredentialRejectedError) throw heartbeatFailure;
940
+ if (!completionAllowed || isLeaseConflict(heartbeatFailure)) return;
941
+ try {
942
+ await input2.client.output(command.id, command.leaseToken, result.stdout, result.stderr);
943
+ await input2.client.complete(command.id, command.leaseToken, result);
944
+ } catch (error) {
945
+ if (error instanceof DeviceCredentialRejectedError) throw error;
946
+ return;
947
+ }
948
+ input2.onEvent?.({ type: "completed", commandId: command.id, exitCode: result.exitCode });
949
+ }
950
+ async function runDeviceWorker(input2) {
951
+ const runProcess = input2.runProcess ?? runDeviceProcess;
952
+ const sleep = input2.sleep ?? sleepWithAbort;
953
+ const random = input2.random ?? Math.random;
954
+ let retryAttempt = 0;
955
+ while (!input2.signal.aborted) {
956
+ let command;
957
+ try {
958
+ command = await input2.client.lease(input2.signal);
959
+ retryAttempt = 0;
960
+ } catch (error) {
961
+ if (input2.signal.aborted) return;
962
+ if (error instanceof DeviceCredentialRejectedError) throw error;
963
+ if (!isRetryableIdleError(error)) throw error;
964
+ try {
965
+ await sleep(computeRetryDelay(retryAttempt, random), input2.signal);
966
+ } catch (sleepError) {
967
+ if (input2.signal.aborted && isAbortError2(sleepError)) return;
968
+ throw sleepError;
969
+ }
970
+ retryAttempt += 1;
971
+ continue;
972
+ }
973
+ if (!command) continue;
974
+ await executeLease(command, input2, runProcess, sleep);
975
+ }
976
+ }
977
+
978
+ // src/commands/device.ts
979
+ var NAMED_PROFILE_REQUIRED = "Device pairing requires a named Standards profile";
980
+ var SUPPORTED_PLATFORMS_REQUIRED = "Device workers support macOS and Linux only";
981
+ var DeviceCommandError = class extends SchemaError2 {
982
+ constructor(message) {
983
+ super(message, SchemaErrorCode2.VALIDATION_FAILED);
984
+ this.name = "DeviceCommandError";
985
+ }
986
+ };
987
+ function requireSupportedPlatform(platform) {
988
+ if (platform === "darwin" || platform === "linux") return platform;
989
+ throw new DeviceCommandError(SUPPORTED_PLATFORMS_REQUIRED);
990
+ }
991
+ function requireNamedProfile(command) {
992
+ const options = getGlobalOptions(command);
993
+ if (!(options.profileName && options.apiUrl && options.apiKey)) {
994
+ throw new DeviceCommandError(NAMED_PROFILE_REQUIRED);
995
+ }
996
+ return {
997
+ apiUrl: options.apiUrl,
998
+ apiKey: options.apiKey,
999
+ profileName: options.profileName,
1000
+ tenantId: options.tenant
1001
+ };
1002
+ }
1003
+ function isRecord2(value) {
1004
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1005
+ }
1006
+ function parseDeviceView(value) {
1007
+ if (!isRecord2(value)) throw new DeviceCommandError("Invalid device response");
1008
+ const { id, name, platform, arch, hostname: deviceHostname } = value;
1009
+ const { status } = value;
1010
+ if (typeof id !== "string" || typeof name !== "string" || platform !== "darwin" && platform !== "linux" || typeof arch !== "string" || typeof deviceHostname !== "string" || status !== "online" && status !== "offline" && status !== "revoked") {
1011
+ throw new DeviceCommandError("Invalid device response");
1012
+ }
1013
+ return { id, name, platform, arch, hostname: deviceHostname, status };
1014
+ }
1015
+ function parsePairDeviceResponse(value) {
1016
+ if (!isRecord2(value) || typeof value.token !== "string") {
1017
+ throw new DeviceCommandError("Invalid device pairing response");
1018
+ }
1019
+ return { device: parseDeviceView(value.device), token: value.token };
1020
+ }
1021
+ function parseDeviceList(value) {
1022
+ if (!Array.isArray(value)) throw new DeviceCommandError("Invalid device list response");
1023
+ return value.map(parseDeviceView);
1024
+ }
1025
+ async function pairDevice(name, command) {
1026
+ const platform = requireSupportedPlatform(process.platform);
1027
+ const profile = requireNamedProfile(command);
1028
+ const installationId = await getOrCreateInstallationId();
1029
+ const client = createClient({
1030
+ apiUrl: profile.apiUrl,
1031
+ apiKey: profile.apiKey,
1032
+ tenantId: profile.tenantId
1033
+ });
1034
+ const response = parsePairDeviceResponse(
1035
+ await client.post("/devices/pair", {
1036
+ installationId,
1037
+ name,
1038
+ platform,
1039
+ arch: process.arch,
1040
+ hostname: hostname()
1041
+ })
1042
+ );
1043
+ const stored = await storeDeviceCredential(profile.profileName, {
1044
+ id: response.device.id,
1045
+ name: response.device.name,
1046
+ token: response.token
1047
+ });
1048
+ if (!stored) throw new DeviceCommandError(NAMED_PROFILE_REQUIRED);
1049
+ process.stdout.write(`Paired ${response.device.name} (${response.device.id})
1050
+ `);
1051
+ }
1052
+ async function showDeviceStatus(command) {
1053
+ const profile = requireNamedProfile(command);
1054
+ const credential = await getStoredDeviceCredential(profile.profileName);
1055
+ if (!credential) {
1056
+ throw new DeviceCommandError("No paired device; run standards device pair first");
1057
+ }
1058
+ const client = createClient({
1059
+ apiUrl: profile.apiUrl,
1060
+ apiKey: profile.apiKey,
1061
+ tenantId: profile.tenantId
1062
+ });
1063
+ const devices = parseDeviceList(await client.get("/devices"));
1064
+ const pairedDevice = devices.find((device) => device.id === credential.id);
1065
+ formatOutput(
1066
+ pairedDevice ? [pairedDevice] : [{ id: credential.id, name: credential.name, status: "revoked" }],
1067
+ getFormat(command)
1068
+ );
1069
+ }
1070
+ async function revokeDevice(command) {
1071
+ const profile = requireNamedProfile(command);
1072
+ const credential = await getStoredDeviceCredential(profile.profileName);
1073
+ if (!credential) {
1074
+ throw new DeviceCommandError("No paired device; run standards device pair first");
1075
+ }
1076
+ const client = createClient({
1077
+ apiUrl: profile.apiUrl,
1078
+ apiKey: profile.apiKey,
1079
+ tenantId: profile.tenantId
1080
+ });
1081
+ await client.delete(`/devices/${credential.id}`);
1082
+ await removeDeviceCredential(profile.profileName);
1083
+ }
1084
+ async function serveDevice(command) {
1085
+ const platform = requireSupportedPlatform(process.platform);
1086
+ const profile = requireNamedProfile(command);
1087
+ const credential = await getStoredDeviceCredential(profile.profileName);
1088
+ if (!credential) {
1089
+ throw new DeviceCommandError("No paired device; run standards device pair first");
1090
+ }
1091
+ const userClient = createClient({
1092
+ apiUrl: profile.apiUrl,
1093
+ apiKey: profile.apiKey,
1094
+ tenantId: profile.tenantId
1095
+ });
1096
+ const devices = parseDeviceList(await userClient.get("/devices"));
1097
+ const device = devices.find((candidate) => candidate.id === credential.id);
1098
+ if (!device) throw new DeviceCommandError("Device is unavailable");
1099
+ if (device.platform !== platform) {
1100
+ throw new DeviceCommandError("Device platform does not match paired device");
1101
+ }
1102
+ const controller = new AbortController();
1103
+ const stop = () => controller.abort();
1104
+ process.once("SIGINT", stop);
1105
+ process.once("SIGTERM", stop);
1106
+ process.stdout.write(`Connected as ${credential.name}
1107
+ `);
1108
+ process.stdout.write("Waiting for commands \u2014 press Ctrl+C to disconnect\n");
1109
+ try {
1110
+ await runDeviceWorker({
1111
+ client: new DeviceWorkerClient({ apiUrl: profile.apiUrl, token: credential.token }),
1112
+ signal: controller.signal,
1113
+ onEvent: (event) => {
1114
+ if (event.type === "running") {
1115
+ process.stdout.write(`Running ${event.commandId}: ${event.intent}
1116
+ `);
1117
+ return;
1118
+ }
1119
+ process.stdout.write(`Completed ${event.commandId} with exit code ${event.exitCode}
1120
+ `);
1121
+ }
1122
+ });
1123
+ } finally {
1124
+ process.removeListener("SIGINT", stop);
1125
+ process.removeListener("SIGTERM", stop);
1126
+ }
1127
+ }
1128
+ function registerDeviceCommand(program) {
1129
+ const device = program.command("device").description("Pair and run this computer as a device");
1130
+ device.command("pair").description("Pair this computer with Standards").requiredOption("--name <name>", "device name").action(
1131
+ async (options, command) => pairDevice(options.name, command)
1132
+ );
1133
+ device.command("status").description("Show the paired device status").action(async (_options, command) => showDeviceStatus(command));
1134
+ device.command("revoke").description("Revoke this computer's device credential").action(async (_options, command) => revokeDevice(command));
1135
+ device.command("serve").description("Run the foreground device worker").action(async (_options, command) => serveDevice(command));
1136
+ }
1137
+
304
1138
  // src/commands/documents.ts
305
1139
  import { formatByteSize } from "@stndrds/schema";
306
1140
  function toFileRow(file) {
@@ -379,8 +1213,24 @@ function registerDocumentsCommand(program) {
379
1213
  });
380
1214
  }
381
1215
 
1216
+ // src/commands/embeddings.ts
1217
+ import chalk4 from "chalk";
1218
+ function registerEmbeddingsCommand(program) {
1219
+ const embeddings = program.command("embeddings").description("Maintain record embeddings");
1220
+ embeddings.command("drain").description("Embed every pending memory and skill record of the tenant").action(async (_opts, cmd) => {
1221
+ const tenant = requireTenant(
1222
+ cmd,
1223
+ 'An embedding drain names its tenant explicitly. Pass "--tenant <id>".'
1224
+ );
1225
+ const client = getClientFromCommand(cmd);
1226
+ await client.post("/admin/embeddings/drain", { tenantId: tenant });
1227
+ process.stdout.write(`${chalk4.green("\u2713")} Embedding drain accepted for tenant ${tenant}.
1228
+ `);
1229
+ });
1230
+ }
1231
+
382
1232
  // src/commands/folders.ts
383
- import { readFile } from "fs/promises";
1233
+ import { readFile as readFile2 } from "fs/promises";
384
1234
  function registerFoldersCommand(program) {
385
1235
  const folders = program.command("folders").description("Manage record drive folders");
386
1236
  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(
@@ -394,7 +1244,7 @@ function registerFoldersCommand(program) {
394
1244
  ).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) => {
395
1245
  const paths = [...opts.path];
396
1246
  if (opts.pathsFile) {
397
- const content = await readFile(opts.pathsFile, "utf-8");
1247
+ const content = await readFile2(opts.pathsFile, "utf-8");
398
1248
  const filePaths = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
399
1249
  paths.push(...filePaths);
400
1250
  }
@@ -412,7 +1262,7 @@ function registerFoldersCommand(program) {
412
1262
  }
413
1263
 
414
1264
  // src/commands/keys.ts
415
- import chalk4 from "chalk";
1265
+ import chalk5 from "chalk";
416
1266
  function registerKeysCommand(program) {
417
1267
  const keys = program.command("keys").description("Manage Standards API keys");
418
1268
  keys.command("list").description("List API keys").action(async (_opts, cmd) => {
@@ -439,14 +1289,14 @@ function registerKeysCommand(program) {
439
1289
  }
440
1290
  const client = getClientFromCommand(cmd);
441
1291
  await client.delete(`/api-keys/${id}`);
442
- process.stdout.write(`${chalk4.green("\u2713")} API key ${id} revoked.
1292
+ process.stdout.write(`${chalk5.green("\u2713")} API key ${id} revoked.
443
1293
  `);
444
1294
  });
445
1295
  }
446
1296
 
447
1297
  // src/commands/mcp.ts
448
1298
  import { RESOURCE_VISIBILITIES, ValidationError as ValidationError2 } from "@stndrds/schema";
449
- import chalk5 from "chalk";
1299
+ import chalk6 from "chalk";
450
1300
  var AUTH_TYPES = ["none", "header"];
451
1301
  function buildAuth(opts) {
452
1302
  const type = opts.authType ?? "none";
@@ -509,7 +1359,7 @@ function registerMcpCommand(program) {
509
1359
  const client = getClientFromCommand(cmd);
510
1360
  await client.post(`/mcp-servers/${id}/trust`, { trusted });
511
1361
  process.stdout.write(
512
- `${chalk5.green("\u2713")} MCP server ${id} ${trusted ? "trusted" : "untrusted"}.
1362
+ `${chalk6.green("\u2713")} MCP server ${id} ${trusted ? "trusted" : "untrusted"}.
513
1363
  `
514
1364
  );
515
1365
  });
@@ -522,13 +1372,18 @@ function registerMcpCommand(program) {
522
1372
  }
523
1373
  const client = getClientFromCommand(cmd);
524
1374
  await client.delete(`/mcp-servers/${id}`);
525
- process.stdout.write(`${chalk5.green("\u2713")} MCP server ${id} disconnected.
1375
+ process.stdout.write(`${chalk6.green("\u2713")} MCP server ${id} disconnected.
526
1376
  `);
527
1377
  });
528
1378
  }
529
1379
 
530
1380
  // src/commands/meetings.ts
531
- import { ValidationError as ValidationError3 } from "@stndrds/schema";
1381
+ import { createHmac } from "crypto";
1382
+ import {
1383
+ FAKE_MEETING_BOT_SIGNATURE_HEADER,
1384
+ RECORDING_OVERRIDES,
1385
+ ValidationError as ValidationError3
1386
+ } from "@stndrds/schema";
532
1387
  var ORDERS = ["asc", "desc"];
533
1388
  function parseOrder(order) {
534
1389
  if (ORDERS.includes(order)) return order;
@@ -553,6 +1408,43 @@ function buildQuery(opts) {
553
1408
  if (opts.offset !== void 0) query.offset = parseCount("--offset", opts.offset);
554
1409
  return query;
555
1410
  }
1411
+ function parseOverride(opts) {
1412
+ const chosen = RECORDING_OVERRIDES.filter(
1413
+ (override) => opts[override]
1414
+ );
1415
+ if (opts.auto) chosen.push(null);
1416
+ if (chosen.length !== 1) {
1417
+ throw new ValidationError3("record needs exactly one of --force, --skip or --auto", []);
1418
+ }
1419
+ return chosen[0] ?? null;
1420
+ }
1421
+ var SIMULATED_LINE_MS = 4e3;
1422
+ var DEFAULT_SIMULATED_LINES = 12;
1423
+ var SIMULATED_SENTENCES = [
1424
+ "Bonjour \xE0 tous, merci d'\xEAtre l\xE0, on commence par le point d'avancement.",
1425
+ "De mon c\xF4t\xE9 la maquette est valid\xE9e, il reste les retours du client.",
1426
+ "On garde le planning initial, la livraison est pr\xE9vue pour vendredi.",
1427
+ "Est-ce que quelqu'un a des questions sur le budget de la phase deux ?",
1428
+ "Je propose qu'on fasse un point rapide jeudi pour v\xE9rifier les tests.",
1429
+ "Tr\xE8s bien, je vous envoie le compte rendu dans la journ\xE9e, bonne journ\xE9e."
1430
+ ];
1431
+ function simulatedSpeakers(meeting) {
1432
+ const speakers = meeting.participants.map(
1433
+ (participant) => participant.name?.trim() || participant.address.split("@")[0] || participant.address
1434
+ );
1435
+ return speakers.length > 0 ? speakers : ["Speaker"];
1436
+ }
1437
+ function buildSimulatedSegments(speakers, lines) {
1438
+ return Array.from({ length: lines }, (_, index) => ({
1439
+ speaker: speakers[index % speakers.length] ?? null,
1440
+ startsAtMs: index * SIMULATED_LINE_MS,
1441
+ endsAtMs: (index + 1) * SIMULATED_LINE_MS,
1442
+ text: SIMULATED_SENTENCES[index % SIMULATED_SENTENCES.length] ?? ""
1443
+ }));
1444
+ }
1445
+ function signFakeWebhook(secret, body) {
1446
+ return createHmac("sha256", secret).update(body).digest("hex");
1447
+ }
556
1448
  function withQueryOptions(command) {
557
1449
  return command.option("--search <text>", "match title, description and location").option("--since <iso>", "inclusive ISO lower bound on the start").option("--before <iso>", "exclusive ISO upper bound on the start").option("--addresses <emails>", "comma-separated participant addresses to restrict to").option("--record <id>", "only meetings linked to this record").option("--order <order>", "asc for what is coming next, desc to read history").option("--limit <n>", "max meetings to return").option("--offset <n>", "number of meetings to skip");
558
1450
  }
@@ -589,10 +1481,69 @@ function registerMeetingsCommand(program) {
589
1481
  formatOutput(result, getFormat(cmd));
590
1482
  }
591
1483
  );
1484
+ meetings.command("transcript").description("Read a meeting's completed transcript").argument("<id>", "meeting ID").action(async (id, _opts, cmd) => {
1485
+ const client = getClientFromCommand(cmd);
1486
+ const result = await client.get(`/meetings/${id}/transcript`);
1487
+ formatOutput(result, getFormat(cmd));
1488
+ });
1489
+ meetings.command("record").description("Force, skip, or hand back to the workspace policy the recording of a meeting").argument("<id>", "meeting ID").option("--force", "record this meeting whatever the workspace policy says").option("--skip", "never record this meeting").option("--auto", "clear the override and follow the workspace policy").action(async (id, opts, cmd) => {
1490
+ const override = parseOverride(opts);
1491
+ const client = getClientFromCommand(cmd);
1492
+ const result = await client.put(`/meetings/${id}/recording`, { override });
1493
+ formatOutput(result, getFormat(cmd));
1494
+ });
1495
+ meetings.command("simulate-transcript").description("Complete a fake-provider bot by posting a generated transcript to its webhook").argument("<id>", "meeting ID").option(
1496
+ "--lines <n>",
1497
+ "number of transcript lines to generate",
1498
+ String(DEFAULT_SIMULATED_LINES)
1499
+ ).action(async (id, opts, cmd) => {
1500
+ const secret = process.env.MEETING_BOT_FAKE_SECRET;
1501
+ if (!secret) {
1502
+ throw new ValidationError3("MEETING_BOT_FAKE_SECRET is not set in this shell", []);
1503
+ }
1504
+ const lines = parseCount("--lines", opts.lines);
1505
+ if (lines < 1) throw new ValidationError3("--lines must be at least 1", []);
1506
+ const client = getClientFromCommand(cmd);
1507
+ const meeting = await client.get(`/meetings/${id}`);
1508
+ const { status, botId } = meeting.recording;
1509
+ if (status !== "scheduled" && status !== "recording" || botId === null) {
1510
+ throw new ValidationError3(`Meeting ${id} has no bot to complete (status: ${status})`, []);
1511
+ }
1512
+ const segments = buildSimulatedSegments(simulatedSpeakers(meeting), lines);
1513
+ const startedAt = meeting.startsAt ?? (/* @__PURE__ */ new Date()).toISOString();
1514
+ const endedAt = new Date(
1515
+ new Date(startedAt).getTime() + lines * SIMULATED_LINE_MS
1516
+ ).toISOString();
1517
+ const body = JSON.stringify({
1518
+ botId,
1519
+ transcript: {
1520
+ fullText: segments.map((segment) => segment.text).join("\n"),
1521
+ segments,
1522
+ language: "fr",
1523
+ startedAt,
1524
+ endedAt
1525
+ }
1526
+ });
1527
+ const response = await fetch(
1528
+ `${getGlobalOptions(cmd).apiUrl}/meetings/transcripts/webhook/fake`,
1529
+ {
1530
+ method: "POST",
1531
+ headers: {
1532
+ "content-type": "application/json",
1533
+ [FAKE_MEETING_BOT_SIGNATURE_HEADER]: signFakeWebhook(secret, body)
1534
+ },
1535
+ body
1536
+ }
1537
+ );
1538
+ if (!response.ok) {
1539
+ throw new ValidationError3(`webhook answered ${response.status}`, []);
1540
+ }
1541
+ formatOutput({ botId, segments: segments.length }, getFormat(cmd));
1542
+ });
592
1543
  }
593
1544
 
594
1545
  // src/commands/pull.ts
595
- import chalk6 from "chalk";
1546
+ import chalk7 from "chalk";
596
1547
 
597
1548
  // src/drift/reconciliation-prompt.ts
598
1549
  var PREAMBLE = [
@@ -711,7 +1662,7 @@ function registerPullCommand(program) {
711
1662
  try {
712
1663
  client = getClientFromCommand(cmd);
713
1664
  } catch (error) {
714
- console.error(chalk6.red(`\u2717 ${messageOf(error)}`));
1665
+ console.error(chalk7.red(`\u2717 ${messageOf(error)}`));
715
1666
  process.exit(2);
716
1667
  return;
717
1668
  }
@@ -721,14 +1672,14 @@ function registerPullCommand(program) {
721
1672
  console.info(JSON.stringify(state, null, 2));
722
1673
  process.exit(0);
723
1674
  } catch (error) {
724
- console.error(chalk6.red(`\u2717 ${messageOf(error)}`));
1675
+ console.error(chalk7.red(`\u2717 ${messageOf(error)}`));
725
1676
  process.exit(2);
726
1677
  }
727
1678
  return;
728
1679
  }
729
1680
  const result = await runPullCommand(client);
730
1681
  if (result.exitCode === 2) {
731
- console.error(chalk6.red(`\u2717 ${result.errorMessage}`));
1682
+ console.error(chalk7.red(`\u2717 ${result.errorMessage}`));
732
1683
  process.exit(2);
733
1684
  return;
734
1685
  }
@@ -738,7 +1689,7 @@ function registerPullCommand(program) {
738
1689
  }
739
1690
 
740
1691
  // src/commands/records.ts
741
- import { readFile as readFile2 } from "fs/promises";
1692
+ import { readFile as readFile3 } from "fs/promises";
742
1693
  import { basename } from "path";
743
1694
  function parseSortFlag(sort) {
744
1695
  const [attribute, direction = "asc"] = sort.split(":");
@@ -804,7 +1755,7 @@ function registerRecordsCommand(program) {
804
1755
  formatOutput(result, getFormat(cmd));
805
1756
  });
806
1757
  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) => {
807
- const fileContent = await readFile2(opts.file);
1758
+ const fileContent = await readFile3(opts.file);
808
1759
  const fileName = basename(opts.file);
809
1760
  const title = opts.title ?? fileName;
810
1761
  const form = new FormData();
@@ -824,129 +1775,7 @@ function registerRecordsCommand(program) {
824
1775
  // src/commands/root.ts
825
1776
  import { stdin as input, stdout as output } from "process";
826
1777
  import { createInterface } from "readline/promises";
827
- import chalk7 from "chalk";
828
-
829
- // src/config.ts
830
- import { mkdir, readFile as readFile3, rm, writeFile } from "fs/promises";
831
- import { homedir } from "os";
832
- import { dirname, join } from "path";
833
- var DEFAULT_API_URL = "http://localhost:4100/v1";
834
- var ENV_PROFILE_NAME = "sandbox";
835
- function getDefaultApiUrl() {
836
- return DEFAULT_API_URL;
837
- }
838
- function getConfigPath() {
839
- const configDir = process.env.STANDARDS_CONFIG_DIR ?? join(homedir(), ".standards");
840
- return join(configDir, "config.json");
841
- }
842
- async function readConfig() {
843
- try {
844
- const raw = await readFile3(getConfigPath(), "utf8");
845
- const parsed = JSON.parse(raw);
846
- return {
847
- currentProfile: parsed.currentProfile,
848
- profiles: parsed.profiles ?? {}
849
- };
850
- } catch (error) {
851
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
852
- return { profiles: {} };
853
- }
854
- throw error;
855
- }
856
- }
857
- async function writeConfig(config) {
858
- const path = getConfigPath();
859
- await mkdir(dirname(path), { recursive: true, mode: 448 });
860
- await writeFile(path, `${JSON.stringify(config, null, 2)}
861
- `, { mode: 384 });
862
- }
863
- async function upsertProfile(input2) {
864
- const config = await readConfig();
865
- config.profiles[input2.name] = {
866
- apiUrl: input2.apiUrl,
867
- apiKey: input2.apiKey
868
- };
869
- config.currentProfile = input2.name;
870
- await writeConfig(config);
871
- }
872
- async function setCurrentProfile(name) {
873
- const config = await readConfig();
874
- if (!config.profiles[name]) {
875
- throw new Error(`Unknown Standards instance "${name}"`);
876
- }
877
- config.currentProfile = name;
878
- await writeConfig(config);
879
- }
880
- async function removeProfile(name) {
881
- const config = await readConfig();
882
- const profileName = name ?? config.currentProfile;
883
- if (!profileName) return;
884
- delete config.profiles[profileName];
885
- if (config.currentProfile === profileName) {
886
- config.currentProfile = void 0;
887
- }
888
- await writeConfig(config);
889
- }
890
- async function listProfiles() {
891
- const config = await readConfig();
892
- const profiles = Object.entries(config.profiles).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({
893
- name,
894
- apiUrl: profile.apiUrl,
895
- current: config.currentProfile === name
896
- }));
897
- const envProfile = getEnvProfile();
898
- if (envProfile) {
899
- profiles.push({
900
- name: ENV_PROFILE_NAME,
901
- apiUrl: envProfile.apiUrl,
902
- current: !config.currentProfile,
903
- source: "env"
904
- });
905
- }
906
- return profiles;
907
- }
908
- async function getActiveProfile() {
909
- const config = await readConfig();
910
- if (!config.currentProfile) {
911
- const envProfile = getEnvProfile();
912
- return envProfile ? { name: ENV_PROFILE_NAME, ...envProfile } : null;
913
- }
914
- const profile = config.profiles[config.currentProfile];
915
- if (!profile) return null;
916
- return { name: config.currentProfile, ...profile };
917
- }
918
- function getEnvProfile() {
919
- if (!process.env.STANDARDS_API_KEY) return null;
920
- return {
921
- apiUrl: process.env.STANDARDS_API_URL ?? DEFAULT_API_URL,
922
- apiKey: process.env.STANDARDS_API_KEY
923
- };
924
- }
925
- async function resolveCliConfig(options) {
926
- if (options.apiUrl || options.apiKey) {
927
- return {
928
- apiUrl: options.apiUrl ?? process.env.STANDARDS_API_URL ?? DEFAULT_API_URL,
929
- apiKey: options.apiKey ?? process.env.STANDARDS_API_KEY
930
- };
931
- }
932
- const config = await readConfig();
933
- const profileName = options.instance ?? config.currentProfile;
934
- if (profileName) {
935
- const profile = config.profiles[profileName];
936
- if (!profile) throw new Error(`Unknown Standards instance "${profileName}"`);
937
- return {
938
- apiUrl: profile.apiUrl,
939
- apiKey: profile.apiKey,
940
- profileName
941
- };
942
- }
943
- return {
944
- apiUrl: process.env.STANDARDS_API_URL ?? DEFAULT_API_URL,
945
- apiKey: process.env.STANDARDS_API_KEY
946
- };
947
- }
948
-
949
- // src/commands/root.ts
1778
+ import chalk8 from "chalk";
950
1779
  async function promptForMissingValue(label) {
951
1780
  const rl = createInterface({ input, output });
952
1781
  try {
@@ -964,7 +1793,7 @@ function registerRootCommands(program) {
964
1793
  await client.get("/api-keys");
965
1794
  await upsertProfile({ name: opts.name, apiUrl: opts.url, apiKey });
966
1795
  process.stdout.write(
967
- `${chalk7.green("\u2713")} Standards instance "${opts.name}" saved and selected.
1796
+ `${chalk8.green("\u2713")} Standards instance "${opts.name}" saved and selected.
968
1797
  `
969
1798
  );
970
1799
  process.stdout.write(` API URL: ${opts.url}
@@ -976,7 +1805,7 @@ function registerRootCommands(program) {
976
1805
  });
977
1806
  program.command("use").description("Select the active Standards instance").argument("<name>", "instance name").action(async (name) => {
978
1807
  await setCurrentProfile(name);
979
- process.stdout.write(`${chalk7.green("\u2713")} Standards instance "${name}" selected.
1808
+ process.stdout.write(`${chalk8.green("\u2713")} Standards instance "${name}" selected.
980
1809
  `);
981
1810
  });
982
1811
  program.command("instances").description("List configured Standards instances").action(async (_opts, cmd) => {
@@ -995,7 +1824,7 @@ function registerRootCommands(program) {
995
1824
  });
996
1825
  program.command("logout").description("Remove a Standards instance from local CLI config").argument("[name]", "instance name, defaults to current").action(async (name) => {
997
1826
  await removeProfile(name);
998
- process.stdout.write(`${chalk7.green("\u2713")} Standards instance removed.
1827
+ process.stdout.write(`${chalk8.green("\u2713")} Standards instance removed.
999
1828
  `);
1000
1829
  });
1001
1830
  }
@@ -1016,7 +1845,7 @@ function registerSchemaCommand(program) {
1016
1845
  }
1017
1846
 
1018
1847
  // src/commands/search.ts
1019
- import chalk8 from "chalk";
1848
+ import chalk9 from "chalk";
1020
1849
  function registerSearchCommand(program) {
1021
1850
  const search = program.command("search").description("Maintain the Standards search index");
1022
1851
  search.command("reindex").description("Clear the tenant search index and re-inject every record").option("--yes", "reindex without confirmation").action(async (opts, cmd) => {
@@ -1025,16 +1854,14 @@ function registerSearchCommand(program) {
1025
1854
  'A full reindex clears the index before refilling it. Re-run with "--yes" to confirm.'
1026
1855
  );
1027
1856
  }
1028
- const { tenant } = getGlobalOptions(cmd);
1029
- if (!tenant) {
1030
- throw new Error(
1031
- 'A full reindex names its tenant explicitly. Pass "--tenant <id>" with the tenant you are authenticated in.'
1032
- );
1033
- }
1857
+ const tenant = requireTenant(
1858
+ cmd,
1859
+ 'A full reindex names its tenant explicitly. Pass "--tenant <id>" with the tenant you are authenticated in.'
1860
+ );
1034
1861
  const client = getClientFromCommand(cmd);
1035
1862
  await client.post("/admin/search/full-reindex", { tenantId: tenant });
1036
1863
  process.stdout.write(
1037
- `${chalk8.green("\u2713")} Full reindex accepted for tenant ${tenant}. It runs in the background \u2014 follow the server logs for progress.
1864
+ `${chalk9.green("\u2713")} Full reindex accepted for tenant ${tenant}. It runs in the background \u2014 follow the server logs for progress.
1038
1865
  `
1039
1866
  );
1040
1867
  });
@@ -1054,6 +1881,7 @@ function createProgram() {
1054
1881
  registerSchemaCommand(program);
1055
1882
  registerPullCommand(program);
1056
1883
  registerDocumentsCommand(program);
1884
+ registerDeviceCommand(program);
1057
1885
  registerFoldersCommand(program);
1058
1886
  registerKeysCommand(program);
1059
1887
  registerAuthCommand(program);
@@ -1062,6 +1890,7 @@ function createProgram() {
1062
1890
  registerMcpCommand(program);
1063
1891
  registerMeetingsCommand(program);
1064
1892
  registerSearchCommand(program);
1893
+ registerEmbeddingsCommand(program);
1065
1894
  program.hook("preAction", async (_thisCommand, actionCommand) => {
1066
1895
  const raw = program.opts();
1067
1896
  const resolved = await resolveCliConfig({
@@ -1075,7 +1904,7 @@ function createProgram() {
1075
1904
  program.setOptionValue("tenant", raw.tenant);
1076
1905
  if (!(resolved.apiKey || isPublicCommand(actionCommand))) {
1077
1906
  console.error(
1078
- chalk9.red(
1907
+ chalk10.red(
1079
1908
  `\u2717 Error: No Standards instance configured. Run "standards login" or pass --api-key.`
1080
1909
  )
1081
1910
  );
@@ -1089,12 +1918,12 @@ async function runProgram(argv = process.argv) {
1089
1918
  await program.parseAsync(argv).catch((error) => {
1090
1919
  if (error instanceof ApiClientError) {
1091
1920
  if (error.statusCode > 0) {
1092
- console.error(chalk9.red(`\u2717 Error (${error.statusCode}): ${error.message}`));
1921
+ console.error(chalk10.red(`\u2717 Error (${error.statusCode}): ${error.message}`));
1093
1922
  } else {
1094
- console.error(chalk9.red(`\u2717 Error: ${error.message}`));
1923
+ console.error(chalk10.red(`\u2717 Error: ${error.message}`));
1095
1924
  }
1096
1925
  } else if (error instanceof Error) {
1097
- console.error(chalk9.red(`\u2717 Error: ${error.message}`));
1926
+ console.error(chalk10.red(`\u2717 Error: ${error.message}`));
1098
1927
  }
1099
1928
  process.exit(1);
1100
1929
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stndrds/cli",
3
- "version": "1.0.0-alpha.292",
3
+ "version": "1.0.0-alpha.294",
4
4
  "description": "CLI tool to interact with Standards API",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,7 @@
13
13
  "chalk": "^5.4.1",
14
14
  "cli-table3": "^0.6.5",
15
15
  "commander": "^13.1.0",
16
- "@stndrds/schema": "1.0.0-alpha.292"
16
+ "@stndrds/schema": "1.0.0-alpha.294"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "^25.6.0",