@stndrds/cli 1.0.0-alpha.3 → 1.0.0-alpha.302

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 (4) hide show
  1. package/LICENSE +111 -0
  2. package/README.md +38 -0
  3. package/dist/bin.mjs +1611 -260
  4. package/package.json +9 -5
package/dist/bin.mjs CHANGED
@@ -1,124 +1,26 @@
1
1
  #!/usr/bin/env node
2
+ /*! © 2025-2026 AND YOU CREATE SAS (RCS Paris 940 190 614). Proprietary — see LICENSE (Standards SDK License 1.0). */
2
3
 
3
4
  // src/program.ts
4
- import chalk5 from "chalk";
5
+ import chalk10 from "chalk";
5
6
  import { Command } from "commander";
6
7
 
7
8
  // src/client.ts
8
- var ApiClientError = class extends Error {
9
- constructor(statusCode, message) {
10
- super(message);
11
- this.statusCode = statusCode;
12
- this.name = "ApiClientError";
13
- }
14
- };
15
- function buildUrl(base, path, params) {
16
- const normalizedBase = base.endsWith("/") ? base : `${base}/`;
17
- const normalizedPath = path.replace(/^\/+/, "");
18
- const url = new URL(normalizedPath, normalizedBase);
19
- if (params) {
20
- for (const [key, value] of Object.entries(params)) {
21
- if (value !== void 0 && value !== null) {
22
- url.searchParams.set(key, String(value));
23
- }
24
- }
25
- }
26
- return url.toString();
27
- }
9
+ import { StandardsRequestError, createTransport } from "@stndrds/client";
10
+ var ApiClientError = StandardsRequestError;
28
11
  function createClient(config) {
29
- const { apiUrl, apiKey } = config;
30
- const headers = {
31
- Authorization: `Bearer ${apiKey}`,
32
- "Content-Type": "application/json"
33
- };
34
- async function request(method, path, options) {
35
- const url = buildUrl(apiUrl, path, options?.params);
36
- let response;
37
- try {
38
- response = await fetch(url, {
39
- method,
40
- headers,
41
- body: options?.body ? JSON.stringify(options.body) : void 0,
42
- signal: AbortSignal.timeout(3e4)
43
- });
44
- } catch (error) {
45
- if (error instanceof TypeError || error instanceof DOMException && error.name === "TimeoutError") {
46
- throw new ApiClientError(0, `Could not connect to ${apiUrl}. Is the server running?`);
47
- }
48
- throw error;
49
- }
50
- if (response.status === 204) {
51
- return null;
52
- }
53
- const data = await response.json();
54
- if (!response.ok) {
55
- const message = data.message ?? `Request failed with status ${response.status}`;
56
- throw new ApiClientError(response.status, message);
57
- }
58
- return data;
59
- }
60
- return {
61
- get(path, params) {
62
- return request("GET", path, { params });
63
- },
64
- post(path, body) {
65
- return request("POST", path, { body });
66
- },
67
- patch(path, body) {
68
- return request("PATCH", path, { body });
69
- },
70
- put(path, body) {
71
- return request("PUT", path, { body });
72
- },
73
- delete(path) {
74
- return request("DELETE", path);
75
- }
76
- };
77
- }
78
-
79
- // src/commands/auth.ts
80
- import chalk from "chalk";
81
-
82
- // src/commands/common.ts
83
- function getGlobalOptions(cmd) {
84
- return cmd.optsWithGlobals();
85
- }
86
- function getFormat(cmd) {
87
- return getGlobalOptions(cmd).format;
88
- }
89
- function getClientFromCommand(cmd) {
90
- const root = getGlobalOptions(cmd);
91
- if (!(root.apiUrl && root.apiKey)) {
92
- throw new Error('No Standards instance configured. Run "standards login" or pass --api-key.');
93
- }
94
- return createClient({ apiUrl: root.apiUrl, apiKey: root.apiKey });
95
- }
96
-
97
- // src/commands/auth.ts
98
- function registerAuthCommand(program) {
99
- const auth = program.command("auth").description("Inspect CLI authentication");
100
- auth.command("whoami").description("Show current configuration and validate API key").action(async (_opts, cmd) => {
101
- const root = getGlobalOptions(cmd);
102
- const client = getClientFromCommand(cmd);
103
- const write = (msg) => process.stdout.write(`${msg}
104
- `);
105
- write(chalk.bold("Standards CLI Configuration"));
106
- write(` API URL: ${root.apiUrl}`);
107
- write(` API Key: ${root.apiKey ? `${root.apiKey.slice(0, 8)}...` : chalk.red("not set")}`);
108
- write("");
109
- const keys = await client.get("/api-keys");
110
- write(chalk.green("\u2713 API key is valid"));
111
- if (Array.isArray(keys) && keys.length > 0) {
112
- const activeKeys = keys.filter(
113
- (key) => typeof key === "object" && key !== null && !("revokedAt" in key && key.revokedAt)
114
- );
115
- write(` Active keys: ${activeKeys.length}`);
116
- }
12
+ return createTransport({
13
+ baseUrl: config.apiUrl,
14
+ apiKey: config.apiKey,
15
+ tenantId: config.tenantId
117
16
  });
118
17
  }
119
18
 
19
+ // src/commands/ai.ts
20
+ import { ValidationError } from "@stndrds/schema";
21
+
120
22
  // src/output.ts
121
- import chalk2 from "chalk";
23
+ import chalk from "chalk";
122
24
  import Table from "cli-table3";
123
25
  var MAX_CELL_WIDTH = 50;
124
26
  function truncate(value, maxLength) {
@@ -147,7 +49,7 @@ function formatTable(items) {
147
49
  if (items.length === 0) return "No results.";
148
50
  const headers = Object.keys(items[0]);
149
51
  const table = new Table({
150
- head: headers.map((h) => chalk2.bold(h)),
52
+ head: headers.map((h) => chalk.bold(h)),
151
53
  style: { head: [], border: [] }
152
54
  });
153
55
  for (const item of items) {
@@ -177,165 +79,186 @@ function formatOutput(data, format) {
177
79
  `);
178
80
  }
179
81
 
180
- // src/commands/documents.ts
181
- function registerDocumentsCommand(program) {
182
- const documents = program.command("documents").description("Manage documents");
183
- documents.command("list").description("List documents").option("--status <status>", "filter by processing status").option("--limit <n>", "max documents to return").option("--offset <n>", "number of documents to skip").action(async (opts, cmd) => {
184
- const client = getClientFromCommand(cmd);
185
- const params = {};
186
- if (opts.status) params.processingStatus = opts.status;
187
- if (opts.limit) params.limit = opts.limit;
188
- if (opts.offset) params.offset = opts.offset;
189
- const result = await client.get("/documents", params);
190
- formatOutput(result, getFormat(cmd));
191
- });
192
- documents.command("get").description("Get a document by ID").argument("<id>", "document ID").action(async (id, _opts, cmd) => {
193
- const client = getClientFromCommand(cmd);
194
- const result = await client.get(`/documents/${id}`);
195
- formatOutput(result, getFormat(cmd));
196
- });
197
- documents.command("create").description("Create a new document").requiredOption("--title <title>", "document title").option("--values <json>", "document values as JSON string").action(async (opts, cmd) => {
82
+ // src/commands/common.ts
83
+ function getGlobalOptions(cmd) {
84
+ return cmd.optsWithGlobals();
85
+ }
86
+ function getFormat(cmd) {
87
+ return getGlobalOptions(cmd).format;
88
+ }
89
+ function parseCsvList(value) {
90
+ return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
91
+ }
92
+ function getClientFromCommand(cmd) {
93
+ const root = getGlobalOptions(cmd);
94
+ if (!(root.apiUrl && root.apiKey)) {
95
+ throw new Error('No Standards instance configured. Run "standards login" or pass --api-key.');
96
+ }
97
+ return createClient({ apiUrl: root.apiUrl, apiKey: root.apiKey, tenantId: root.tenant });
98
+ }
99
+ function requireTenant(cmd, message) {
100
+ const { tenant } = getGlobalOptions(cmd);
101
+ if (!tenant) {
102
+ throw new Error(message);
103
+ }
104
+ return tenant;
105
+ }
106
+ function messageOf(error) {
107
+ return error instanceof Error ? error.message : String(error);
108
+ }
109
+
110
+ // src/commands/ai.ts
111
+ var SCOPES = ["me", "workspace"];
112
+ var ISO_DAY = /^\d{4}-\d{2}-\d{2}$/u;
113
+ function parseDay(flag, value) {
114
+ if (!ISO_DAY.test(value)) {
115
+ throw new ValidationError(`${flag} must be a YYYY-MM-DD date`, []);
116
+ }
117
+ return value;
118
+ }
119
+ function parseScope(scope) {
120
+ if (SCOPES.includes(scope)) return scope;
121
+ throw new ValidationError(`--scope must be one of: ${SCOPES.join(", ")}`, []);
122
+ }
123
+ function registerAiCommand(program) {
124
+ const ai = program.command("ai").description("Inspect AI usage and credit state");
125
+ ai.command("usage").description("Aggregate the AI journal over an inclusive UTC day window").requiredOption("--from <day>", "first day of the window (YYYY-MM-DD)").requiredOption("--to <day>", "last day of the window, inclusive (YYYY-MM-DD)").option("--scope <scope>", "me or workspace", "me").action(async (opts, cmd) => {
126
+ const params = {
127
+ from: parseDay("--from", opts.from),
128
+ to: parseDay("--to", opts.to),
129
+ scope: parseScope(opts.scope ?? "me")
130
+ };
198
131
  const client = getClientFromCommand(cmd);
199
- const body = { title: opts.title };
200
- if (opts.values) body.values = JSON.parse(opts.values);
201
- const result = await client.post("/documents", body);
132
+ const result = await client.get("/ai/usage", params);
202
133
  formatOutput(result, getFormat(cmd));
203
134
  });
204
- documents.command("update").description("Update a document").argument("<id>", "document ID").option("--title <title>", "new title").option("--values <json>", "new values as JSON string").action(async (id, opts, cmd) => {
135
+ ai.command("wallet").description("Show the tenant's billing mode and AI credit state").action(async (_opts, cmd) => {
205
136
  const client = getClientFromCommand(cmd);
206
- const body = {};
207
- if (opts.title) body.title = opts.title;
208
- if (opts.values) body.values = JSON.parse(opts.values);
209
- const result = await client.patch(`/documents/${id}`, body);
137
+ const result = await client.get("/ai/wallet");
210
138
  formatOutput(result, getFormat(cmd));
211
139
  });
212
- documents.command("delete").description("Delete a document").argument("<id>", "document ID").action(async (id, _opts, cmd) => {
140
+ }
141
+
142
+ // src/commands/auth.ts
143
+ import chalk2 from "chalk";
144
+ function registerAuthCommand(program) {
145
+ const auth = program.command("auth").description("Inspect CLI authentication");
146
+ auth.command("whoami").description("Show current configuration and validate API key").action(async (_opts, cmd) => {
147
+ const root = getGlobalOptions(cmd);
213
148
  const client = getClientFromCommand(cmd);
214
- await client.delete(`/documents/${id}`);
215
- process.stdout.write(`Document ${id} deleted.
149
+ const write = (msg) => process.stdout.write(`${msg}
216
150
  `);
151
+ write(chalk2.bold("Standards CLI Configuration"));
152
+ write(` API URL: ${root.apiUrl}`);
153
+ write(` API Key: ${root.apiKey ? `${root.apiKey.slice(0, 8)}...` : chalk2.red("not set")}`);
154
+ write("");
155
+ const keys = await client.get("/api-keys");
156
+ write(chalk2.green("\u2713 API key is valid"));
157
+ if (Array.isArray(keys) && keys.length > 0) {
158
+ const activeKeys = keys.filter(
159
+ (key) => typeof key === "object" && key !== null && !("revokedAt" in key && key.revokedAt)
160
+ );
161
+ write(` Active keys: ${activeKeys.length}`);
162
+ }
217
163
  });
218
- documents.command("preview").description("Get preview file for a document").argument("<id>", "document ID").action(async (id, _opts, cmd) => {
219
- const client = getClientFromCommand(cmd);
220
- const result = await client.get(`/documents/${id}/preview`);
221
- formatOutput(result, getFormat(cmd));
222
- });
223
- documents.command("slots").description("List slots for a document").argument("<id>", "document ID").action(async (id, _opts, cmd) => {
164
+ }
165
+
166
+ // src/commands/bundles.ts
167
+ function registerBundlesCommand(program) {
168
+ const bundles = program.command("bundles").description("Manage system bundles assigned to the current tenant (admin)");
169
+ bundles.command("list").description("List the bundles actively assigned to the current tenant").action(async (_opts, cmd) => {
224
170
  const client = getClientFromCommand(cmd);
225
- const result = await client.get(`/documents/${id}/slots`);
171
+ const result = await client.get("/bundles/assigned");
226
172
  formatOutput(result, getFormat(cmd));
227
173
  });
228
- documents.command("add-slot").description("Add a file slot to a document").argument("<id>", "document ID").requiredOption("--slot-name <name>", "slot name").requiredOption("--file-id <fileId>", "file ID").action(async (id, opts, cmd) => {
174
+ bundles.command("updatable").description("List assigned bundles that have a newer version available").action(async (_opts, cmd) => {
229
175
  const client = getClientFromCommand(cmd);
230
- const result = await client.post(`/documents/${id}/slots`, {
231
- slotName: opts.slotName,
232
- fileId: opts.fileId
233
- });
176
+ const result = await client.get("/bundles/updatable");
234
177
  formatOutput(result, getFormat(cmd));
235
178
  });
236
- documents.command("remove-slot").description("Remove a slot from a document").argument("<id>", "document ID").argument("<slotId>", "slot ID").action(async (id, slotId, _opts, cmd) => {
237
- const client = getClientFromCommand(cmd);
238
- await client.delete(`/documents/${id}/slots/${slotId}`);
239
- process.stdout.write(`Slot ${slotId} removed.
240
- `);
241
- });
242
- }
243
-
244
- // src/commands/keys.ts
245
- import chalk3 from "chalk";
246
- function registerKeysCommand(program) {
247
- const keys = program.command("keys").description("Manage Standards API keys");
248
- keys.command("list").description("List API keys").action(async (_opts, cmd) => {
179
+ bundles.command("assign").description("Assign (install) a bundle to the current tenant").argument("<bundleId>", "bundle ID to assign").action(async (bundleId, _opts, cmd) => {
249
180
  const client = getClientFromCommand(cmd);
250
- const result = await client.get("/api-keys");
181
+ const result = await client.post("/bundles/assign", { bundleId });
251
182
  formatOutput(result, getFormat(cmd));
252
183
  });
253
- keys.command("create").description("Create a new API key").requiredOption("--name <name>", "name for the API key").option("--expires <date>", "expiration date (ISO 8601)").option("--yes", "create without confirmation").action(async (opts, cmd) => {
184
+ bundles.command("uninstall").description("Uninstall a bundle: deactivate it and demote its now-orphaned objects").argument("<bundleId>", "bundle ID to uninstall").option("--yes", "uninstall without confirmation").action(async (bundleId, opts, cmd) => {
254
185
  if (!opts.yes) {
255
- throw new Error('Creating API keys is explicit. Re-run with "--yes" to confirm.');
186
+ throw new Error('Uninstalling a bundle is explicit. Re-run with "--yes" to confirm.');
256
187
  }
257
188
  const client = getClientFromCommand(cmd);
258
- const body = {
259
- name: opts.name,
260
- permissions: []
261
- };
262
- if (opts.expires) body.expiresAt = opts.expires;
263
- const result = await client.post("/api-keys", body);
189
+ const result = await client.delete(`/bundles/${bundleId}`);
264
190
  formatOutput(result, getFormat(cmd));
265
191
  });
266
- keys.command("revoke").description("Revoke an API key").argument("<id>", "API key ID").option("--yes", "revoke without confirmation").action(async (id, opts, cmd) => {
267
- if (!opts.yes) {
268
- throw new Error('Revoking API keys is explicit. Re-run with "--yes" to confirm.');
269
- }
192
+ bundles.command("update").description("Apply assigned bundles \u2014 picks up newer bundle versions (additive sync)").action(async (_opts, cmd) => {
270
193
  const client = getClientFromCommand(cmd);
271
- await client.delete(`/api-keys/${id}`);
272
- process.stdout.write(`${chalk3.green("\u2713")} API key ${id} revoked.
273
- `);
194
+ const result = await client.post("/bundles/update");
195
+ formatOutput(result, getFormat(cmd));
274
196
  });
275
197
  }
276
198
 
277
- // src/commands/records.ts
278
- function parseSortFlag(sort) {
279
- const [attribute, direction = "asc"] = sort.split(":");
280
- return JSON.stringify([{ attribute, direction }]);
199
+ // src/commands/connectors.ts
200
+ import { ValidationError as ValidationError2 } from "@stndrds/schema";
201
+ import chalk3 from "chalk";
202
+ var PROVIDER_IDS = {
203
+ gmail: true,
204
+ outlook: true,
205
+ "google-calendar": true,
206
+ "outlook-calendar": true
207
+ };
208
+ var PROVIDERS = Object.keys(PROVIDER_IDS);
209
+ function resolveScope(scope) {
210
+ return scope === "actor" ? "actor" : "tenant";
281
211
  }
282
- function registerRecordsCommand(program) {
283
- const records = program.command("records").description("Manage records (CRUD + search)");
284
- records.command("list").description("List records for an object").argument("<object>", "object name (e.g. contacts)").option("--limit <n>", "max records to return").option("--offset <n>", "number of records to skip").option("--sort <sort>", 'sort rule as "attribute:direction"').option("--filter <json>", "filter state as JSON string").action(async (objectName, opts, cmd) => {
285
- const client = getClientFromCommand(cmd);
286
- const params = {};
287
- if (opts.limit) params.limit = opts.limit;
288
- if (opts.offset) params.offset = opts.offset;
289
- if (opts.sort) params.sorts = parseSortFlag(opts.sort);
290
- if (opts.filter) params.filters = opts.filter;
291
- const result = await client.get(`/records/${objectName}`, params);
292
- formatOutput(result, getFormat(cmd));
293
- });
294
- records.command("get").description("Get a record by ID").argument("<object>", "object name").argument("<id>", "record ID").action(async (objectName, id, _opts, cmd) => {
212
+ function registerConnectorsCommand(program) {
213
+ const connectors = program.command("connectors").description("Manage email and calendar connectors (OAuth connections)");
214
+ connectors.command("providers").description("List the connector providers this deployment holds credentials for").action(async (_opts, cmd) => {
295
215
  const client = getClientFromCommand(cmd);
296
- const result = await client.get(`/records/${objectName}/${id}`);
216
+ const result = await client.get("/connectors/providers");
297
217
  formatOutput(result, getFormat(cmd));
298
218
  });
299
- records.command("create").description("Create a new record").argument("<object>", "object name").option("--data <json>", "record data as JSON string").action(async (objectName, opts, cmd) => {
300
- const data = opts.data ? JSON.parse(opts.data) : {};
219
+ connectors.command("list").description("List connector connections").option("--scope <scope>", "connection scope (tenant or actor)", "tenant").action(async (opts, cmd) => {
301
220
  const client = getClientFromCommand(cmd);
302
- const result = await client.post(`/records/${objectName}`, { data });
221
+ const result = await client.get("/connectors/connections", {
222
+ scope: resolveScope(opts.scope)
223
+ });
303
224
  formatOutput(result, getFormat(cmd));
304
225
  });
305
- records.command("update").description("Update a record").argument("<object>", "object name").argument("<id>", "record ID").option("--data <json>", "fields to update as JSON string").action(async (objectName, id, opts, cmd) => {
306
- const data = opts.data ? JSON.parse(opts.data) : {};
226
+ connectors.command("connect").description("Start an OAuth flow and return the authorization URL to open").requiredOption(
227
+ "--provider <provider>",
228
+ `connector provider (${PROVIDERS.join(", ")}); run "connectors providers" for the ones this deployment configured`
229
+ ).option("--scope <scope>", "connection scope (tenant or actor)", "tenant").action(async (opts, cmd) => {
230
+ if (!PROVIDERS.includes(opts.provider)) {
231
+ throw new ValidationError2(`--provider must be one of: ${PROVIDERS.join(", ")}`, []);
232
+ }
307
233
  const client = getClientFromCommand(cmd);
308
- const result = await client.put(`/records/${objectName}/${id}`, data);
234
+ const result = await client.post("/connectors/auth/start", {
235
+ provider: opts.provider,
236
+ scope: resolveScope(opts.scope)
237
+ });
309
238
  formatOutput(result, getFormat(cmd));
310
239
  });
311
- records.command("delete").description("Delete a record").argument("<object>", "object name").argument("<id>", "record ID").action(async (objectName, id, _opts, cmd) => {
240
+ connectors.command("disconnect").description("Disconnect (delete) a connector connection").argument("<id>", "connection ID").option("--yes", "disconnect without confirmation").action(async (id, opts, cmd) => {
241
+ if (!opts.yes) {
242
+ throw new Error('Disconnecting a connector is explicit. Re-run with "--yes" to confirm.');
243
+ }
312
244
  const client = getClientFromCommand(cmd);
313
- await client.delete(`/records/${objectName}/${id}`);
314
- process.stdout.write(`Record ${id} deleted.
245
+ await client.delete(`/connectors/connections/${id}`);
246
+ process.stdout.write(`${chalk3.green("\u2713")} Connector connection ${id} disconnected.
315
247
  `);
316
248
  });
317
- records.command("search").description("Full-text search records").argument("<object>", "object name").requiredOption("--query <q>", "search query string").option("--limit <n>", "max records to return").option("--offset <n>", "number of records to skip").option("--sort <sort>", 'sort rule as "attribute:direction"').option("--filter <json>", "filter state as JSON string").action(async (objectName, opts, cmd) => {
318
- const client = getClientFromCommand(cmd);
319
- const params = { q: opts.query };
320
- if (opts.limit) params.limit = opts.limit;
321
- if (opts.offset) params.offset = opts.offset;
322
- if (opts.sort) params.sorts = parseSortFlag(opts.sort);
323
- if (opts.filter) params.filters = opts.filter;
324
- const result = await client.get(`/records/${objectName}/search`, params);
325
- formatOutput(result, getFormat(cmd));
326
- });
327
249
  }
328
250
 
329
- // src/commands/root.ts
330
- import { stdin as input, stdout as output } from "process";
331
- import { createInterface } from "readline/promises";
332
- import chalk4 from "chalk";
251
+ // src/commands/device.ts
252
+ import { hostname } from "os";
253
+ import { SchemaError as SchemaError2, SchemaErrorCode as SchemaErrorCode2 } from "@stndrds/schema";
333
254
 
334
255
  // src/config.ts
335
- import { mkdir, readFile, rm, writeFile } from "fs/promises";
256
+ import { randomUUID } from "crypto";
257
+ import { chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
336
258
  import { homedir } from "os";
337
259
  import { dirname, join } from "path";
338
260
  var DEFAULT_API_URL = "http://localhost:4100/v1";
261
+ var ENV_PROFILE_NAME = "sandbox";
339
262
  function getDefaultApiUrl() {
340
263
  return DEFAULT_API_URL;
341
264
  }
@@ -348,6 +271,7 @@ async function readConfig() {
348
271
  const raw = await readFile(getConfigPath(), "utf8");
349
272
  const parsed = JSON.parse(raw);
350
273
  return {
274
+ ...parsed.installationId ? { installationId: parsed.installationId } : {},
351
275
  currentProfile: parsed.currentProfile,
352
276
  profiles: parsed.profiles ?? {}
353
277
  };
@@ -360,19 +284,60 @@ async function readConfig() {
360
284
  }
361
285
  async function writeConfig(config) {
362
286
  const path = getConfigPath();
363
- await mkdir(dirname(path), { recursive: true, mode: 448 });
287
+ const directory = dirname(path);
288
+ await mkdir(directory, { recursive: true, mode: 448 });
289
+ await chmod(directory, 448);
364
290
  await writeFile(path, `${JSON.stringify(config, null, 2)}
365
291
  `, { mode: 384 });
292
+ await chmod(path, 384);
293
+ }
294
+ function normalizeApiOrigin(apiUrl) {
295
+ try {
296
+ return new URL(apiUrl).origin;
297
+ } catch {
298
+ return apiUrl.replace(/\/+$/, "");
299
+ }
366
300
  }
367
301
  async function upsertProfile(input2) {
368
302
  const config = await readConfig();
303
+ const existingProfile = config.profiles[input2.name];
304
+ const device = existingProfile?.device && normalizeApiOrigin(existingProfile.apiUrl) === normalizeApiOrigin(input2.apiUrl) ? existingProfile.device : void 0;
369
305
  config.profiles[input2.name] = {
370
306
  apiUrl: input2.apiUrl,
371
- apiKey: input2.apiKey
307
+ apiKey: input2.apiKey,
308
+ ...device ? { device } : {}
372
309
  };
373
310
  config.currentProfile = input2.name;
374
311
  await writeConfig(config);
375
312
  }
313
+ async function getOrCreateInstallationId() {
314
+ const config = await readConfig();
315
+ if (config.installationId) return config.installationId;
316
+ const installationId = randomUUID();
317
+ config.installationId = installationId;
318
+ await writeConfig(config);
319
+ return installationId;
320
+ }
321
+ async function getStoredDeviceCredential(profileName) {
322
+ const config = await readConfig();
323
+ return config.profiles[profileName]?.device ?? null;
324
+ }
325
+ async function storeDeviceCredential(profileName, credential) {
326
+ const config = await readConfig();
327
+ const profile = config.profiles[profileName];
328
+ if (!profile) return false;
329
+ profile.device = credential;
330
+ await writeConfig(config);
331
+ return true;
332
+ }
333
+ async function removeDeviceCredential(profileName) {
334
+ const config = await readConfig();
335
+ const profile = config.profiles[profileName];
336
+ if (!profile?.device) return false;
337
+ profile.device = void 0;
338
+ await writeConfig(config);
339
+ return true;
340
+ }
376
341
  async function setCurrentProfile(name) {
377
342
  const config = await readConfig();
378
343
  if (!config.profiles[name]) {
@@ -393,19 +358,39 @@ async function removeProfile(name) {
393
358
  }
394
359
  async function listProfiles() {
395
360
  const config = await readConfig();
396
- return Object.entries(config.profiles).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({
361
+ const profiles = Object.entries(config.profiles).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({
397
362
  name,
398
363
  apiUrl: profile.apiUrl,
399
364
  current: config.currentProfile === name
400
365
  }));
366
+ const envProfile = getEnvProfile();
367
+ if (envProfile) {
368
+ profiles.push({
369
+ name: ENV_PROFILE_NAME,
370
+ apiUrl: envProfile.apiUrl,
371
+ current: !config.currentProfile,
372
+ source: "env"
373
+ });
374
+ }
375
+ return profiles;
401
376
  }
402
377
  async function getActiveProfile() {
403
378
  const config = await readConfig();
404
- if (!config.currentProfile) return null;
379
+ if (!config.currentProfile) {
380
+ const envProfile = getEnvProfile();
381
+ return envProfile ? { name: ENV_PROFILE_NAME, ...envProfile } : null;
382
+ }
405
383
  const profile = config.profiles[config.currentProfile];
406
384
  if (!profile) return null;
407
385
  return { name: config.currentProfile, ...profile };
408
386
  }
387
+ function getEnvProfile() {
388
+ if (!process.env.STANDARDS_API_KEY) return null;
389
+ return {
390
+ apiUrl: process.env.STANDARDS_API_URL ?? DEFAULT_API_URL,
391
+ apiKey: process.env.STANDARDS_API_KEY
392
+ };
393
+ }
409
394
  async function resolveCliConfig(options) {
410
395
  if (options.apiUrl || options.apiKey) {
411
396
  return {
@@ -430,37 +415,1347 @@ async function resolveCliConfig(options) {
430
415
  };
431
416
  }
432
417
 
433
- // src/commands/root.ts
434
- async function promptForMissingValue(label) {
435
- const rl = createInterface({ input, output });
418
+ // src/device/worker-client.ts
419
+ import { SchemaError, SchemaErrorCode } from "@stndrds/schema";
420
+ var REQUEST_TIMEOUT_MS = 3e4;
421
+ var DeviceWorkerRequestError = class extends SchemaError {
422
+ constructor(statusCode, message, code = SchemaErrorCode.REPOSITORY_QUERY_FAILED) {
423
+ super(message, code, { statusCode });
424
+ this.statusCode = statusCode;
425
+ this.name = "DeviceWorkerRequestError";
426
+ }
427
+ };
428
+ var DeviceCredentialRejectedError = class extends DeviceWorkerRequestError {
429
+ constructor() {
430
+ super(
431
+ 401,
432
+ "Device credential rejected; run standards device pair again",
433
+ SchemaErrorCode.ACCESS_DENIED
434
+ );
435
+ this.name = "DeviceCredentialRejectedError";
436
+ }
437
+ };
438
+ function isRecord(value) {
439
+ return value !== null && typeof value === "object" && !Array.isArray(value);
440
+ }
441
+ async function readJson(response) {
442
+ const text = await response.text();
443
+ if (!text) return void 0;
436
444
  try {
437
- const value = await rl.question(`${label}: `);
438
- return value.trim();
439
- } finally {
440
- rl.close();
445
+ return JSON.parse(text);
446
+ } catch {
447
+ return void 0;
441
448
  }
442
449
  }
443
- function registerRootCommands(program) {
444
- program.command("login").description("Connect the CLI to a Standards instance").option("--url <url>", "Standards API URL", getDefaultApiUrl()).option("--key <key>", "API key to store for this instance").option("--name <name>", "local instance name", "local").action(async (opts) => {
445
- const apiKey = opts.key ?? await promptForMissingValue("API key");
446
- if (!apiKey) throw new Error("API key is required");
447
- const client = createClient({ apiUrl: opts.url, apiKey });
448
- await client.get("/api-keys");
449
- await upsertProfile({ name: opts.name, apiUrl: opts.url, apiKey });
450
- process.stdout.write(
451
- `${chalk4.green("\u2713")} Standards instance "${opts.name}" saved and selected.
452
- `
453
- );
454
- process.stdout.write(` API URL: ${opts.url}
455
- `);
456
- process.stdout.write(` API Key: ${apiKey.slice(0, 8)}...
457
- `);
458
- process.stdout.write(` Use it with: standards --instance ${opts.name} <command>
459
- `);
450
+ function messageFrom(value, fallback) {
451
+ return isRecord(value) && typeof value.message === "string" ? value.message : fallback;
452
+ }
453
+ function parseLease(value) {
454
+ if (!isRecord(value)) throw new DeviceWorkerRequestError(200, "Invalid lease response");
455
+ const leaseToken = value.leaseToken;
456
+ if (!isRecord(value.command)) {
457
+ throw new DeviceWorkerRequestError(200, "Invalid lease response");
458
+ }
459
+ const leasedCommand = value.command;
460
+ const { id, intent, command, cwd, timeoutMs } = leasedCommand;
461
+ 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)) {
462
+ throw new DeviceWorkerRequestError(200, "Invalid lease response");
463
+ }
464
+ return {
465
+ id,
466
+ leaseToken,
467
+ intent,
468
+ command,
469
+ timeoutMs,
470
+ ...typeof cwd === "string" ? { cwd } : {}
471
+ };
472
+ }
473
+ function parseHeartbeat(value) {
474
+ if (!isRecord(value)) {
475
+ throw new DeviceWorkerRequestError(200, "Invalid heartbeat response");
476
+ }
477
+ const { cancel } = value;
478
+ if (typeof cancel !== "boolean") {
479
+ throw new DeviceWorkerRequestError(200, "Invalid heartbeat response");
480
+ }
481
+ const reason = value.reason;
482
+ if (reason !== void 0 && reason !== "cancelled" && reason !== "revoked") {
483
+ throw new DeviceWorkerRequestError(200, "Invalid heartbeat response");
484
+ }
485
+ return { cancel, ...reason === void 0 ? {} : { reason } };
486
+ }
487
+ function isAbortError(error) {
488
+ return error instanceof DOMException && error.name === "AbortError";
489
+ }
490
+ var DeviceWorkerClient = class {
491
+ constructor(options) {
492
+ this.options = options;
493
+ this.fetch = options.fetch ?? fetch;
494
+ this.apiUrl = options.apiUrl.replace(/\/+$/, "");
495
+ }
496
+ async request(path, input2 = {}) {
497
+ const timeoutSignal = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
498
+ const signal = input2.signal ? AbortSignal.any([input2.signal, timeoutSignal]) : timeoutSignal;
499
+ let response;
500
+ try {
501
+ response = await this.fetch(`${this.apiUrl}/device-worker${path}`, {
502
+ method: "POST",
503
+ signal,
504
+ headers: {
505
+ authorization: `Device ${this.options.token}`,
506
+ "content-type": "application/json",
507
+ ...input2.leaseToken ? { "x-device-lease-token": input2.leaseToken } : {}
508
+ },
509
+ ...input2.body === void 0 ? {} : { body: JSON.stringify(input2.body) }
510
+ });
511
+ } catch (error) {
512
+ if (input2.signal?.aborted) throw new DOMException("Aborted", "AbortError");
513
+ if (isAbortError(error)) throw error;
514
+ throw new DeviceWorkerRequestError(0, "Device worker request failed");
515
+ }
516
+ if (response.status === 401) throw new DeviceCredentialRejectedError();
517
+ if (!response.ok) {
518
+ const body = await readJson(response);
519
+ throw new DeviceWorkerRequestError(
520
+ response.status,
521
+ messageFrom(body, `Device worker request failed with status ${response.status}`),
522
+ response.status === 409 ? SchemaErrorCode.CONFLICT : SchemaErrorCode.REPOSITORY_QUERY_FAILED
523
+ );
524
+ }
525
+ return response;
526
+ }
527
+ async lease(signal) {
528
+ const response = await this.request("/commands/lease", {
529
+ body: { version: "1", capabilities: ["exec"] },
530
+ signal
531
+ });
532
+ if (response.status === 204) return null;
533
+ return parseLease(await readJson(response));
534
+ }
535
+ async markRunning(commandId, leaseToken) {
536
+ await this.request(`/commands/${commandId}/running`, { leaseToken });
537
+ }
538
+ async heartbeat(commandId, leaseToken, signal) {
539
+ const response = await this.request(`/commands/${commandId}/heartbeat`, {
540
+ leaseToken,
541
+ signal
542
+ });
543
+ return parseHeartbeat(await readJson(response));
544
+ }
545
+ async output(commandId, leaseToken, stdout, stderr) {
546
+ await this.request(`/commands/${commandId}/output`, {
547
+ leaseToken,
548
+ body: { stdout, stderr }
549
+ });
550
+ }
551
+ async complete(commandId, leaseToken, result) {
552
+ await this.request(`/commands/${commandId}/complete`, {
553
+ leaseToken,
554
+ body: {
555
+ exitCode: result.exitCode,
556
+ success: result.success,
557
+ ...result.errorCode ? { errorCode: result.errorCode } : {}
558
+ }
559
+ });
560
+ }
561
+ };
562
+
563
+ // src/device/process-runner.ts
564
+ import { spawn } from "child_process";
565
+ import { constants } from "fs";
566
+ import { access, stat } from "fs/promises";
567
+ import { isAbsolute } from "path";
568
+ import { StringDecoder } from "string_decoder";
569
+ var OUTPUT_LIMIT = 3e4;
570
+ var TERMINATION_GRACE_MS = 5e3;
571
+ var TERMINATION_POLL_MS = 50;
572
+ var BoundedTextBuffer = class {
573
+ constructor() {
574
+ this.decoder = new StringDecoder("utf8");
575
+ this.decoderFlushed = false;
576
+ this.value = "";
577
+ this.truncated = false;
578
+ }
579
+ append(chunk) {
580
+ if (this.truncated) return;
581
+ const text = typeof chunk === "string" ? chunk : this.decoder.write(Buffer.from(chunk));
582
+ this.appendText(text);
583
+ }
584
+ appendText(text) {
585
+ const remaining = OUTPUT_LIMIT - this.value.length;
586
+ if (text.length <= remaining) {
587
+ this.value += text;
588
+ return;
589
+ }
590
+ this.value += text.slice(0, Math.max(remaining, 0));
591
+ this.truncated = true;
592
+ }
593
+ toString() {
594
+ if (!this.decoderFlushed) {
595
+ this.decoderFlushed = true;
596
+ this.appendText(this.decoder.end());
597
+ }
598
+ return this.truncated ? `${this.value}
599
+ ... output truncated to ${OUTPUT_LIMIT} chars` : this.value;
600
+ }
601
+ };
602
+ function isMissingProcess(error) {
603
+ return error instanceof Error && "code" in error && error.code === "ESRCH";
604
+ }
605
+ function signalProcessGroup(pid, signal, kill) {
606
+ try {
607
+ kill(-pid, signal);
608
+ return true;
609
+ } catch (error) {
610
+ if (isMissingProcess(error)) return false;
611
+ throw error;
612
+ }
613
+ }
614
+ function beginProcessGroupTermination(pid, deps = {}) {
615
+ const kill = deps.kill ?? process.kill.bind(process);
616
+ const setTimer = deps.setTimer ?? setTimeout;
617
+ const clearTimer = deps.clearTimer ?? clearTimeout;
618
+ return new Promise((resolve, reject) => {
619
+ let settled = false;
620
+ let escalationTimer;
621
+ let pollTimer;
622
+ function clearTimers() {
623
+ if (escalationTimer) clearTimer(escalationTimer);
624
+ if (pollTimer) clearTimer(pollTimer);
625
+ }
626
+ function finish() {
627
+ if (settled) return;
628
+ settled = true;
629
+ clearTimers();
630
+ resolve();
631
+ }
632
+ function fail(error) {
633
+ if (settled) return;
634
+ settled = true;
635
+ clearTimers();
636
+ reject(error);
637
+ }
638
+ function poll() {
639
+ if (settled) return;
640
+ try {
641
+ if (!signalProcessGroup(pid, 0, kill)) {
642
+ finish();
643
+ return;
644
+ }
645
+ pollTimer = setTimer(poll, TERMINATION_POLL_MS);
646
+ } catch (error) {
647
+ fail(error);
648
+ }
649
+ }
650
+ try {
651
+ if (!signalProcessGroup(pid, "SIGTERM", kill)) {
652
+ finish();
653
+ return;
654
+ }
655
+ escalationTimer = setTimer(() => {
656
+ try {
657
+ if (!signalProcessGroup(pid, 0, kill)) {
658
+ finish();
659
+ return;
660
+ }
661
+ signalProcessGroup(pid, "SIGKILL", kill);
662
+ } catch (error) {
663
+ fail(error);
664
+ }
665
+ }, TERMINATION_GRACE_MS);
666
+ pollTimer = setTimer(poll, TERMINATION_POLL_MS);
667
+ } catch (error) {
668
+ fail(error);
669
+ }
670
+ });
671
+ }
672
+ async function resolveShell(shell = process.env.SHELL) {
673
+ if (!(shell && isAbsolute(shell))) return "/bin/sh";
674
+ try {
675
+ const metadata = await stat(shell);
676
+ if (!metadata.isFile()) return "/bin/sh";
677
+ await access(shell, constants.X_OK);
678
+ return shell;
679
+ } catch {
680
+ return "/bin/sh";
681
+ }
682
+ }
683
+ function terminalResult(reason, stdout, stderr) {
684
+ return {
685
+ stdout: stdout.toString(),
686
+ stderr: stderr.toString(),
687
+ exitCode: reason === "timed_out" ? 124 : 130,
688
+ success: false,
689
+ errorCode: reason
690
+ };
691
+ }
692
+ async function runDeviceProcess(input2) {
693
+ if (input2.signal?.aborted) {
694
+ return { stdout: "", stderr: "", exitCode: 130, success: false, errorCode: "cancelled" };
695
+ }
696
+ const shell = await resolveShell();
697
+ if (input2.signal?.aborted) {
698
+ return { stdout: "", stderr: "", exitCode: 130, success: false, errorCode: "cancelled" };
699
+ }
700
+ const stdout = new BoundedTextBuffer();
701
+ const stderr = new BoundedTextBuffer();
702
+ return new Promise((resolve) => {
703
+ let settled = false;
704
+ let terminationReason;
705
+ let termination;
706
+ let child;
707
+ function finish(result) {
708
+ if (settled) return;
709
+ settled = true;
710
+ clearTimeout(timeout);
711
+ input2.signal?.removeEventListener("abort", cancel);
712
+ resolve(result);
713
+ }
714
+ function finishTerminationFailure(error) {
715
+ finish({
716
+ stdout: stdout.toString(),
717
+ stderr: error instanceof Error ? error.message : String(error),
718
+ exitCode: 1,
719
+ success: false,
720
+ errorCode: "execution_failed"
721
+ });
722
+ }
723
+ function terminate(reason) {
724
+ if (settled || terminationReason || !child?.pid) return;
725
+ terminationReason = reason;
726
+ termination = beginProcessGroupTermination(child.pid);
727
+ termination.catch(finishTerminationFailure);
728
+ }
729
+ function cancel() {
730
+ terminate("cancelled");
731
+ }
732
+ const timeout = setTimeout(() => terminate("timed_out"), input2.timeoutMs);
733
+ try {
734
+ const spawned2 = spawn(shell, ["-lc", input2.command], {
735
+ cwd: input2.cwd,
736
+ env: process.env,
737
+ detached: true,
738
+ stdio: ["ignore", "pipe", "pipe"]
739
+ });
740
+ child = spawned2;
741
+ } catch (error) {
742
+ finish({
743
+ stdout: "",
744
+ stderr: error instanceof Error ? error.message : String(error),
745
+ exitCode: 1,
746
+ success: false,
747
+ errorCode: "execution_failed"
748
+ });
749
+ return;
750
+ }
751
+ input2.signal?.addEventListener("abort", cancel, { once: true });
752
+ if (input2.signal?.aborted) cancel();
753
+ const spawned = child;
754
+ spawned.stdout.on("data", (chunk) => stdout.append(chunk));
755
+ spawned.stderr.on("data", (chunk) => stderr.append(chunk));
756
+ spawned.once("error", (error) => {
757
+ finish({
758
+ stdout: stdout.toString(),
759
+ stderr: error.message,
760
+ exitCode: 1,
761
+ success: false,
762
+ errorCode: "execution_failed"
763
+ });
764
+ });
765
+ spawned.once("close", (code) => {
766
+ if (terminationReason) {
767
+ const reason = terminationReason;
768
+ (termination ?? Promise.resolve()).then(
769
+ () => finish(terminalResult(reason, stdout, stderr)),
770
+ finishTerminationFailure
771
+ );
772
+ return;
773
+ }
774
+ const exitCode = code ?? 1;
775
+ finish({
776
+ stdout: stdout.toString(),
777
+ stderr: stderr.toString(),
778
+ exitCode,
779
+ success: exitCode === 0,
780
+ ...code === null ? { errorCode: "execution_failed" } : {}
781
+ });
782
+ });
783
+ });
784
+ }
785
+
786
+ // src/device/worker-loop.ts
787
+ var HEARTBEAT_INTERVAL_MS = 2e3;
788
+ var INITIAL_RETRY_MS = 250;
789
+ var MAX_RETRY_MS = 1e4;
790
+ var RETRY_JITTER_MS = 250;
791
+ function computeRetryDelay(attempt, random = Math.random) {
792
+ const exponential = Math.min(INITIAL_RETRY_MS * 2 ** attempt, MAX_RETRY_MS);
793
+ return exponential + Math.floor(random() * RETRY_JITTER_MS);
794
+ }
795
+ function sleepWithAbort(milliseconds, signal) {
796
+ if (signal?.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
797
+ return new Promise((resolve, reject) => {
798
+ function abort() {
799
+ clearTimeout(timer);
800
+ reject(new DOMException("Aborted", "AbortError"));
801
+ }
802
+ const timer = setTimeout(() => {
803
+ signal?.removeEventListener("abort", abort);
804
+ resolve();
805
+ }, milliseconds);
806
+ signal?.addEventListener("abort", abort, { once: true });
807
+ });
808
+ }
809
+ function isAbortError2(error) {
810
+ return error instanceof DOMException && error.name === "AbortError";
811
+ }
812
+ function isRetryableIdleError(error) {
813
+ return error instanceof DeviceWorkerRequestError && (error.statusCode === 0 || error.statusCode >= 500);
814
+ }
815
+ function isLeaseConflict(error) {
816
+ return error instanceof DeviceWorkerRequestError && error.statusCode === 409;
817
+ }
818
+ async function executeLease(command, input2, runProcess, sleep) {
819
+ try {
820
+ await input2.client.markRunning(command.id, command.leaseToken);
821
+ } catch (error) {
822
+ if (error instanceof DeviceCredentialRejectedError) throw error;
823
+ return;
824
+ }
825
+ input2.onEvent?.({ type: "running", commandId: command.id, intent: command.intent });
826
+ const commandController = new AbortController();
827
+ const heartbeatController = new AbortController();
828
+ let completionAllowed = true;
829
+ let heartbeatFailure;
830
+ function abortCommand() {
831
+ commandController.abort();
832
+ }
833
+ input2.signal.addEventListener("abort", abortCommand, { once: true });
834
+ const processPromise = runProcess({
835
+ command: command.command,
836
+ cwd: command.cwd,
837
+ timeoutMs: command.timeoutMs,
838
+ signal: commandController.signal
839
+ });
840
+ const heartbeatPromise = (async () => {
841
+ while (!heartbeatController.signal.aborted) {
842
+ try {
843
+ await sleep(HEARTBEAT_INTERVAL_MS, heartbeatController.signal);
844
+ const heartbeat = await input2.client.heartbeat(
845
+ command.id,
846
+ command.leaseToken,
847
+ heartbeatController.signal
848
+ );
849
+ if (heartbeat.cancel) {
850
+ if (heartbeat.reason === "revoked") completionAllowed = false;
851
+ commandController.abort();
852
+ return;
853
+ }
854
+ } catch (error) {
855
+ if (heartbeatController.signal.aborted && isAbortError2(error)) return;
856
+ heartbeatFailure = error;
857
+ completionAllowed = false;
858
+ commandController.abort();
859
+ return;
860
+ }
861
+ }
862
+ })();
863
+ let result;
864
+ try {
865
+ result = await processPromise;
866
+ } catch (error) {
867
+ result = {
868
+ stdout: "",
869
+ stderr: error instanceof Error ? error.message : String(error),
870
+ exitCode: 1,
871
+ success: false,
872
+ errorCode: "execution_failed"
873
+ };
874
+ } finally {
875
+ heartbeatController.abort();
876
+ await heartbeatPromise;
877
+ input2.signal.removeEventListener("abort", abortCommand);
878
+ }
879
+ if (heartbeatFailure instanceof DeviceCredentialRejectedError) throw heartbeatFailure;
880
+ if (!completionAllowed || isLeaseConflict(heartbeatFailure)) return;
881
+ try {
882
+ await input2.client.output(command.id, command.leaseToken, result.stdout, result.stderr);
883
+ await input2.client.complete(command.id, command.leaseToken, result);
884
+ } catch (error) {
885
+ if (error instanceof DeviceCredentialRejectedError) throw error;
886
+ return;
887
+ }
888
+ input2.onEvent?.({ type: "completed", commandId: command.id, exitCode: result.exitCode });
889
+ }
890
+ async function runDeviceWorker(input2) {
891
+ const runProcess = input2.runProcess ?? runDeviceProcess;
892
+ const sleep = input2.sleep ?? sleepWithAbort;
893
+ const random = input2.random ?? Math.random;
894
+ let retryAttempt = 0;
895
+ while (!input2.signal.aborted) {
896
+ let command;
897
+ try {
898
+ command = await input2.client.lease(input2.signal);
899
+ retryAttempt = 0;
900
+ } catch (error) {
901
+ if (input2.signal.aborted) return;
902
+ if (error instanceof DeviceCredentialRejectedError) throw error;
903
+ if (!isRetryableIdleError(error)) throw error;
904
+ try {
905
+ await sleep(computeRetryDelay(retryAttempt, random), input2.signal);
906
+ } catch (sleepError) {
907
+ if (input2.signal.aborted && isAbortError2(sleepError)) return;
908
+ throw sleepError;
909
+ }
910
+ retryAttempt += 1;
911
+ continue;
912
+ }
913
+ if (!command) continue;
914
+ await executeLease(command, input2, runProcess, sleep);
915
+ }
916
+ }
917
+
918
+ // src/commands/device.ts
919
+ var NAMED_PROFILE_REQUIRED = "Device pairing requires a named Standards profile";
920
+ var SUPPORTED_PLATFORMS_REQUIRED = "Device workers support macOS and Linux only";
921
+ var DeviceCommandError = class extends SchemaError2 {
922
+ constructor(message) {
923
+ super(message, SchemaErrorCode2.VALIDATION_FAILED);
924
+ this.name = "DeviceCommandError";
925
+ }
926
+ };
927
+ function requireSupportedPlatform(platform) {
928
+ if (platform === "darwin" || platform === "linux") return platform;
929
+ throw new DeviceCommandError(SUPPORTED_PLATFORMS_REQUIRED);
930
+ }
931
+ function requireNamedProfile(command) {
932
+ const options = getGlobalOptions(command);
933
+ if (!(options.profileName && options.apiUrl && options.apiKey)) {
934
+ throw new DeviceCommandError(NAMED_PROFILE_REQUIRED);
935
+ }
936
+ return {
937
+ apiUrl: options.apiUrl,
938
+ apiKey: options.apiKey,
939
+ profileName: options.profileName,
940
+ tenantId: options.tenant
941
+ };
942
+ }
943
+ function isRecord2(value) {
944
+ return value !== null && typeof value === "object" && !Array.isArray(value);
945
+ }
946
+ function parseDeviceView(value) {
947
+ if (!isRecord2(value)) throw new DeviceCommandError("Invalid device response");
948
+ const { id, name, platform, arch, hostname: deviceHostname } = value;
949
+ const { status } = value;
950
+ if (typeof id !== "string" || typeof name !== "string" || platform !== "darwin" && platform !== "linux" || typeof arch !== "string" || typeof deviceHostname !== "string" || status !== "online" && status !== "offline" && status !== "revoked") {
951
+ throw new DeviceCommandError("Invalid device response");
952
+ }
953
+ return { id, name, platform, arch, hostname: deviceHostname, status };
954
+ }
955
+ function parsePairDeviceResponse(value) {
956
+ if (!isRecord2(value) || typeof value.token !== "string") {
957
+ throw new DeviceCommandError("Invalid device pairing response");
958
+ }
959
+ return { device: parseDeviceView(value.device), token: value.token };
960
+ }
961
+ function parseDeviceList(value) {
962
+ if (!Array.isArray(value)) throw new DeviceCommandError("Invalid device list response");
963
+ return value.map(parseDeviceView);
964
+ }
965
+ async function pairDevice(name, command) {
966
+ const platform = requireSupportedPlatform(process.platform);
967
+ const profile = requireNamedProfile(command);
968
+ const installationId = await getOrCreateInstallationId();
969
+ const client = createClient({
970
+ apiUrl: profile.apiUrl,
971
+ apiKey: profile.apiKey,
972
+ tenantId: profile.tenantId
973
+ });
974
+ const response = parsePairDeviceResponse(
975
+ await client.post("/devices/pair", {
976
+ installationId,
977
+ name,
978
+ platform,
979
+ arch: process.arch,
980
+ hostname: hostname()
981
+ })
982
+ );
983
+ const stored = await storeDeviceCredential(profile.profileName, {
984
+ id: response.device.id,
985
+ name: response.device.name,
986
+ token: response.token
987
+ });
988
+ if (!stored) throw new DeviceCommandError(NAMED_PROFILE_REQUIRED);
989
+ process.stdout.write(`Paired ${response.device.name} (${response.device.id})
990
+ `);
991
+ }
992
+ async function showDeviceStatus(command) {
993
+ const profile = requireNamedProfile(command);
994
+ const credential = await getStoredDeviceCredential(profile.profileName);
995
+ if (!credential) {
996
+ throw new DeviceCommandError("No paired device; run standards device pair first");
997
+ }
998
+ const client = createClient({
999
+ apiUrl: profile.apiUrl,
1000
+ apiKey: profile.apiKey,
1001
+ tenantId: profile.tenantId
1002
+ });
1003
+ const devices = parseDeviceList(await client.get("/devices"));
1004
+ const pairedDevice = devices.find((device) => device.id === credential.id);
1005
+ formatOutput(
1006
+ pairedDevice ? [pairedDevice] : [{ id: credential.id, name: credential.name, status: "revoked" }],
1007
+ getFormat(command)
1008
+ );
1009
+ }
1010
+ async function revokeDevice(command) {
1011
+ const profile = requireNamedProfile(command);
1012
+ const credential = await getStoredDeviceCredential(profile.profileName);
1013
+ if (!credential) {
1014
+ throw new DeviceCommandError("No paired device; run standards device pair first");
1015
+ }
1016
+ const client = createClient({
1017
+ apiUrl: profile.apiUrl,
1018
+ apiKey: profile.apiKey,
1019
+ tenantId: profile.tenantId
1020
+ });
1021
+ await client.delete(`/devices/${credential.id}`);
1022
+ await removeDeviceCredential(profile.profileName);
1023
+ }
1024
+ async function serveDevice(command) {
1025
+ const platform = requireSupportedPlatform(process.platform);
1026
+ const profile = requireNamedProfile(command);
1027
+ const credential = await getStoredDeviceCredential(profile.profileName);
1028
+ if (!credential) {
1029
+ throw new DeviceCommandError("No paired device; run standards device pair first");
1030
+ }
1031
+ const userClient = createClient({
1032
+ apiUrl: profile.apiUrl,
1033
+ apiKey: profile.apiKey,
1034
+ tenantId: profile.tenantId
1035
+ });
1036
+ const devices = parseDeviceList(await userClient.get("/devices"));
1037
+ const device = devices.find((candidate) => candidate.id === credential.id);
1038
+ if (!device) throw new DeviceCommandError("Device is unavailable");
1039
+ if (device.platform !== platform) {
1040
+ throw new DeviceCommandError("Device platform does not match paired device");
1041
+ }
1042
+ const controller = new AbortController();
1043
+ const stop = () => controller.abort();
1044
+ process.once("SIGINT", stop);
1045
+ process.once("SIGTERM", stop);
1046
+ process.stdout.write(`Connected as ${credential.name}
1047
+ `);
1048
+ process.stdout.write("Waiting for commands \u2014 press Ctrl+C to disconnect\n");
1049
+ try {
1050
+ await runDeviceWorker({
1051
+ client: new DeviceWorkerClient({ apiUrl: profile.apiUrl, token: credential.token }),
1052
+ signal: controller.signal,
1053
+ onEvent: (event) => {
1054
+ if (event.type === "running") {
1055
+ process.stdout.write(`Running ${event.commandId}: ${event.intent}
1056
+ `);
1057
+ return;
1058
+ }
1059
+ process.stdout.write(`Completed ${event.commandId} with exit code ${event.exitCode}
1060
+ `);
1061
+ }
1062
+ });
1063
+ } finally {
1064
+ process.removeListener("SIGINT", stop);
1065
+ process.removeListener("SIGTERM", stop);
1066
+ }
1067
+ }
1068
+ function registerDeviceCommand(program) {
1069
+ const device = program.command("device").description("Pair and run this computer as a device");
1070
+ device.command("pair").description("Pair this computer with Standards").requiredOption("--name <name>", "device name").action(
1071
+ async (options, command) => pairDevice(options.name, command)
1072
+ );
1073
+ device.command("status").description("Show the paired device status").action(async (_options, command) => showDeviceStatus(command));
1074
+ device.command("revoke").description("Revoke this computer's device credential").action(async (_options, command) => revokeDevice(command));
1075
+ device.command("serve").description("Run the foreground device worker").action(async (_options, command) => serveDevice(command));
1076
+ }
1077
+
1078
+ // src/commands/documents.ts
1079
+ import { formatByteSize } from "@stndrds/schema";
1080
+ function toFileRow(file) {
1081
+ return {
1082
+ id: file.id,
1083
+ position: file.position,
1084
+ type: file.mimeType,
1085
+ size: formatByteSize(file.size),
1086
+ ocrStatus: file.ocrStatus
1087
+ };
1088
+ }
1089
+ function registerDocumentsCommand(program) {
1090
+ const documents = program.command("documents").description("Manage documents");
1091
+ documents.command("list").description("List documents").option("--limit <n>", "max documents to return").option("--offset <n>", "number of documents to skip").action(async (opts, cmd) => {
1092
+ const client = getClientFromCommand(cmd);
1093
+ const params = {};
1094
+ if (opts.limit) params.limit = opts.limit;
1095
+ if (opts.offset) params.offset = opts.offset;
1096
+ const result = await client.get("/documents", params);
1097
+ formatOutput(result, getFormat(cmd));
1098
+ });
1099
+ documents.command("get").description("Get a document by ID").argument("<id>", "document ID").action(async (id, _opts, cmd) => {
1100
+ const client = getClientFromCommand(cmd);
1101
+ const result = await client.get(`/documents/${id}`);
1102
+ formatOutput(result, getFormat(cmd));
1103
+ });
1104
+ documents.command("create").description("Create a new document").requiredOption("--title <title>", "document title").option("--values <json>", "document values as JSON string").action(async (opts, cmd) => {
1105
+ const client = getClientFromCommand(cmd);
1106
+ const body = { title: opts.title };
1107
+ if (opts.values) body.values = JSON.parse(opts.values);
1108
+ const result = await client.post("/documents", body);
1109
+ formatOutput(result, getFormat(cmd));
1110
+ });
1111
+ documents.command("update").description("Update a document").argument("<id>", "document ID").option("--title <title>", "new title").option("--values <json>", "new values as JSON string").action(async (id, opts, cmd) => {
1112
+ const client = getClientFromCommand(cmd);
1113
+ const body = {};
1114
+ if (opts.title) body.title = opts.title;
1115
+ if (opts.values) body.values = JSON.parse(opts.values);
1116
+ const result = await client.patch(`/documents/${id}`, body);
1117
+ formatOutput(result, getFormat(cmd));
1118
+ });
1119
+ documents.command("delete").description("Delete a document").argument("<id>", "document ID").action(async (id, _opts, cmd) => {
1120
+ const client = getClientFromCommand(cmd);
1121
+ await client.delete(`/documents/${id}`);
1122
+ process.stdout.write(`Document ${id} deleted.
1123
+ `);
1124
+ });
1125
+ documents.command("preview").description("Get preview file for a document").argument("<id>", "document ID").action(async (id, _opts, cmd) => {
1126
+ const client = getClientFromCommand(cmd);
1127
+ const result = await client.get(`/documents/${id}/preview`);
1128
+ formatOutput(result, getFormat(cmd));
1129
+ });
1130
+ documents.command("files").description("List files in a document's pack (position-ordered)").argument("<id>", "document ID").action(async (id, _opts, cmd) => {
1131
+ const client = getClientFromCommand(cmd);
1132
+ const files = await client.get(`/documents/${id}/files`);
1133
+ const sorted = [...files].sort((a, b) => a.position - b.position);
1134
+ const format = getFormat(cmd);
1135
+ formatOutput(format === "table" ? sorted.map(toFileRow) : sorted, format);
1136
+ });
1137
+ documents.command("add-file").description("Append a file to a document's pack").argument("<id>", "document ID").requiredOption("--file-id <fileId>", "file ID").action(async (id, opts, cmd) => {
1138
+ const client = getClientFromCommand(cmd);
1139
+ const result = await client.post(`/documents/${id}/files`, { fileId: opts.fileId });
1140
+ formatOutput(result, getFormat(cmd));
1141
+ });
1142
+ documents.command("remove-file").description("Remove a file from a document's pack").argument("<id>", "document ID").argument("<fileId>", "file ID").action(async (id, fileId, _opts, cmd) => {
1143
+ const client = getClientFromCommand(cmd);
1144
+ await client.delete(`/documents/${id}/files/${fileId}`);
1145
+ process.stdout.write(`File ${fileId} removed.
1146
+ `);
1147
+ });
1148
+ documents.command("content").description("Print a document's aggregated OCR text (every pack file, position order)").argument("<id>", "document ID").action(async (id, _opts, cmd) => {
1149
+ const client = getClientFromCommand(cmd);
1150
+ const text = await client.getText(`/documents/${id}/content`);
1151
+ process.stdout.write(text.endsWith("\n") ? text : `${text}
1152
+ `);
1153
+ });
1154
+ }
1155
+
1156
+ // src/commands/embeddings.ts
1157
+ import chalk4 from "chalk";
1158
+ function registerEmbeddingsCommand(program) {
1159
+ const embeddings = program.command("embeddings").description("Maintain record embeddings");
1160
+ embeddings.command("drain").description("Embed every pending memory and skill record of the tenant").action(async (_opts, cmd) => {
1161
+ const tenant = requireTenant(
1162
+ cmd,
1163
+ 'An embedding drain names its tenant explicitly. Pass "--tenant <id>".'
1164
+ );
1165
+ const client = getClientFromCommand(cmd);
1166
+ await client.post("/admin/embeddings/drain", { tenantId: tenant });
1167
+ process.stdout.write(`${chalk4.green("\u2713")} Embedding drain accepted for tenant ${tenant}.
1168
+ `);
1169
+ });
1170
+ }
1171
+
1172
+ // src/commands/folders.ts
1173
+ import { readFile as readFile2 } from "fs/promises";
1174
+ function registerFoldersCommand(program) {
1175
+ const folders = program.command("folders").description("Manage record drive folders");
1176
+ 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(
1177
+ "--path <path>",
1178
+ "folder path to create (repeatable)",
1179
+ (val, acc) => {
1180
+ acc.push(val);
1181
+ return acc;
1182
+ },
1183
+ []
1184
+ ).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) => {
1185
+ const paths = [...opts.path];
1186
+ if (opts.pathsFile) {
1187
+ const content = await readFile2(opts.pathsFile, "utf-8");
1188
+ const filePaths = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
1189
+ paths.push(...filePaths);
1190
+ }
1191
+ if (paths.length === 0) {
1192
+ throw new Error("No paths provided. Pass at least one --path or a non-empty --paths-file.");
1193
+ }
1194
+ const client = getClientFromCommand(cmd);
1195
+ const result = await client.post(`/folders/record-drive/${recordId}/reconcile-paths`, {
1196
+ objectName,
1197
+ paths,
1198
+ mode: opts.mode
1199
+ });
1200
+ formatOutput(result, getFormat(cmd));
1201
+ });
1202
+ }
1203
+
1204
+ // src/commands/keys.ts
1205
+ import chalk5 from "chalk";
1206
+ function registerKeysCommand(program) {
1207
+ const keys = program.command("keys").description("Manage Standards API keys");
1208
+ keys.command("list").description("List API keys").action(async (_opts, cmd) => {
1209
+ const client = getClientFromCommand(cmd);
1210
+ const result = await client.get("/api-keys");
1211
+ formatOutput(result, getFormat(cmd));
1212
+ });
1213
+ keys.command("create").description("Create a new API key").requiredOption("--name <name>", "name for the API key").option("--expires <date>", "expiration date (ISO 8601)").option("--yes", "create without confirmation").action(async (opts, cmd) => {
1214
+ if (!opts.yes) {
1215
+ throw new Error('Creating API keys is explicit. Re-run with "--yes" to confirm.');
1216
+ }
1217
+ const client = getClientFromCommand(cmd);
1218
+ const body = {
1219
+ name: opts.name,
1220
+ permissions: []
1221
+ };
1222
+ if (opts.expires) body.expiresAt = opts.expires;
1223
+ const result = await client.post("/api-keys", body);
1224
+ formatOutput(result, getFormat(cmd));
1225
+ });
1226
+ keys.command("revoke").description("Revoke an API key").argument("<id>", "API key ID").option("--yes", "revoke without confirmation").action(async (id, opts, cmd) => {
1227
+ if (!opts.yes) {
1228
+ throw new Error('Revoking API keys is explicit. Re-run with "--yes" to confirm.');
1229
+ }
1230
+ const client = getClientFromCommand(cmd);
1231
+ await client.delete(`/api-keys/${id}`);
1232
+ process.stdout.write(`${chalk5.green("\u2713")} API key ${id} revoked.
1233
+ `);
1234
+ });
1235
+ }
1236
+
1237
+ // src/commands/mcp.ts
1238
+ import { RESOURCE_VISIBILITIES, ValidationError as ValidationError3 } from "@stndrds/schema";
1239
+ import chalk6 from "chalk";
1240
+ var AUTH_TYPES = ["none", "header"];
1241
+ function buildAuth(opts) {
1242
+ const type = opts.authType ?? "none";
1243
+ if (!AUTH_TYPES.includes(type)) {
1244
+ throw new ValidationError3(`--auth-type must be one of: ${AUTH_TYPES.join(", ")}`, []);
1245
+ }
1246
+ if (type === "none") {
1247
+ return { type: "none" };
1248
+ }
1249
+ if (!(opts.headerName && opts.secret)) {
1250
+ throw new ValidationError3('--auth-type "header" requires both --header-name and --secret.', []);
1251
+ }
1252
+ return { type: "header", headerName: opts.headerName, secret: opts.secret };
1253
+ }
1254
+ function resolveVisibility(visibility) {
1255
+ if (visibility === void 0) {
1256
+ return "workspace";
1257
+ }
1258
+ if (!RESOURCE_VISIBILITIES.includes(visibility)) {
1259
+ throw new ValidationError3(
1260
+ `--visibility must be one of: ${RESOURCE_VISIBILITIES.join(", ")}`,
1261
+ []
1262
+ );
1263
+ }
1264
+ return visibility;
1265
+ }
1266
+ function registerMcpCommand(program) {
1267
+ const mcp = program.command("mcp").description("Manage MCP servers mounted into this workspace's agents");
1268
+ mcp.command("list").description("List connected MCP servers").action(async (_opts, cmd) => {
1269
+ const client = getClientFromCommand(cmd);
1270
+ const result = await client.get("/mcp-servers");
1271
+ formatOutput(result, getFormat(cmd));
1272
+ });
1273
+ mcp.command("catalog").description("List the vendored MCP catalog entries available to connect").action(async (_opts, cmd) => {
1274
+ const client = getClientFromCommand(cmd);
1275
+ const result = await client.get("/mcp-servers/catalog");
1276
+ formatOutput(result, getFormat(cmd));
1277
+ });
1278
+ mcp.command("connect").description("Connect a remote MCP server and mount its tools").requiredOption("--slug <slug>", "unique slug for the server").requiredOption("--name <name>", "display name").requiredOption("--url <url>", "remote MCP endpoint URL").option("--visibility <visibility>", "workspace or private", "workspace").option("--catalog-entry <id>", "catalog entry ID this server instantiates").option("--auth-type <type>", "none or header", "none").option("--header-name <name>", 'header name when --auth-type is "header"').option("--secret <secret>", 'header value when --auth-type is "header"').action(async (opts, cmd) => {
1279
+ const auth = buildAuth(opts);
1280
+ const visibility = resolveVisibility(opts.visibility);
1281
+ const client = getClientFromCommand(cmd);
1282
+ const result = await client.post("/mcp-servers", {
1283
+ slug: opts.slug,
1284
+ name: opts.name,
1285
+ url: opts.url,
1286
+ visibility,
1287
+ catalogEntryId: opts.catalogEntry ?? null,
1288
+ auth
1289
+ });
1290
+ formatOutput(result, getFormat(cmd));
1291
+ });
1292
+ mcp.command("refresh").description("Re-discover a server's tools and refresh its status").argument("<id>", "MCP server ID").action(async (id, _opts, cmd) => {
1293
+ const client = getClientFromCommand(cmd);
1294
+ const result = await client.post(`/mcp-servers/${id}/refresh`);
1295
+ formatOutput(result, getFormat(cmd));
1296
+ });
1297
+ mcp.command("trust").description("Mark a server trusted so its tools run without per-call approval").argument("<id>", "MCP server ID").option("--revoke", "revoke trust instead of granting it").action(async (id, opts, cmd) => {
1298
+ const trusted = !opts.revoke;
1299
+ const client = getClientFromCommand(cmd);
1300
+ await client.post(`/mcp-servers/${id}/trust`, { trusted });
1301
+ process.stdout.write(
1302
+ `${chalk6.green("\u2713")} MCP server ${id} ${trusted ? "trusted" : "untrusted"}.
1303
+ `
1304
+ );
1305
+ });
1306
+ mcp.command("disconnect").description("Disconnect (delete) an MCP server").argument("<id>", "MCP server ID").option("--yes", "disconnect without confirmation").action(async (id, opts, cmd) => {
1307
+ if (!opts.yes) {
1308
+ throw new ValidationError3(
1309
+ 'Disconnecting an MCP server is explicit. Re-run with "--yes" to confirm.',
1310
+ []
1311
+ );
1312
+ }
1313
+ const client = getClientFromCommand(cmd);
1314
+ await client.delete(`/mcp-servers/${id}`);
1315
+ process.stdout.write(`${chalk6.green("\u2713")} MCP server ${id} disconnected.
1316
+ `);
1317
+ });
1318
+ }
1319
+
1320
+ // src/commands/meetings.ts
1321
+ import { createHmac } from "crypto";
1322
+ import {
1323
+ FAKE_MEETING_BOT_SIGNATURE_HEADER,
1324
+ RECORDING_OVERRIDES,
1325
+ ValidationError as ValidationError4
1326
+ } from "@stndrds/schema";
1327
+ var ORDERS = ["asc", "desc"];
1328
+ function parseOrder(order) {
1329
+ if (ORDERS.includes(order)) return order;
1330
+ throw new ValidationError4(`--order must be one of: ${ORDERS.join(", ")}`, []);
1331
+ }
1332
+ function parseCount(flag, value) {
1333
+ const parsed = Number(value);
1334
+ if (!Number.isInteger(parsed) || parsed < 0) {
1335
+ throw new ValidationError4(`${flag} must be a non-negative integer`, []);
1336
+ }
1337
+ return parsed;
1338
+ }
1339
+ function buildQuery(opts) {
1340
+ const query = {};
1341
+ if (opts.search !== void 0) query.search = opts.search;
1342
+ if (opts.since !== void 0) query.since = opts.since;
1343
+ if (opts.before !== void 0) query.before = opts.before;
1344
+ if (opts.addresses !== void 0) query.addresses = parseCsvList(opts.addresses);
1345
+ if (opts.record !== void 0) query.recordId = opts.record;
1346
+ if (opts.order !== void 0) query.order = parseOrder(opts.order);
1347
+ if (opts.limit !== void 0) query.limit = parseCount("--limit", opts.limit);
1348
+ if (opts.offset !== void 0) query.offset = parseCount("--offset", opts.offset);
1349
+ return query;
1350
+ }
1351
+ function parseOverride(opts) {
1352
+ const chosen = RECORDING_OVERRIDES.filter(
1353
+ (override) => opts[override]
1354
+ );
1355
+ if (opts.auto) chosen.push(null);
1356
+ if (chosen.length !== 1) {
1357
+ throw new ValidationError4("record needs exactly one of --force, --skip or --auto", []);
1358
+ }
1359
+ return chosen[0] ?? null;
1360
+ }
1361
+ var SIMULATED_LINE_MS = 4e3;
1362
+ var DEFAULT_SIMULATED_LINES = 12;
1363
+ var SIMULATED_SENTENCES = [
1364
+ "Bonjour \xE0 tous, merci d'\xEAtre l\xE0, on commence par le point d'avancement.",
1365
+ "De mon c\xF4t\xE9 la maquette est valid\xE9e, il reste les retours du client.",
1366
+ "On garde le planning initial, la livraison est pr\xE9vue pour vendredi.",
1367
+ "Est-ce que quelqu'un a des questions sur le budget de la phase deux ?",
1368
+ "Je propose qu'on fasse un point rapide jeudi pour v\xE9rifier les tests.",
1369
+ "Tr\xE8s bien, je vous envoie le compte rendu dans la journ\xE9e, bonne journ\xE9e."
1370
+ ];
1371
+ function simulatedSpeakers(meeting) {
1372
+ const speakers = meeting.participants.map(
1373
+ (participant) => participant.name?.trim() || participant.address.split("@")[0] || participant.address
1374
+ );
1375
+ return speakers.length > 0 ? speakers : ["Speaker"];
1376
+ }
1377
+ function buildSimulatedSegments(speakers, lines) {
1378
+ return Array.from({ length: lines }, (_, index) => ({
1379
+ speaker: speakers[index % speakers.length] ?? null,
1380
+ startsAtMs: index * SIMULATED_LINE_MS,
1381
+ endsAtMs: (index + 1) * SIMULATED_LINE_MS,
1382
+ text: SIMULATED_SENTENCES[index % SIMULATED_SENTENCES.length] ?? ""
1383
+ }));
1384
+ }
1385
+ function signFakeWebhook(secret, body) {
1386
+ return createHmac("sha256", secret).update(body).digest("hex");
1387
+ }
1388
+ function withQueryOptions(command) {
1389
+ 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");
1390
+ }
1391
+ function registerMeetingsCommand(program) {
1392
+ const meetings = program.command("meetings").description("Read calendar meetings and the records each one concerns");
1393
+ withQueryOptions(
1394
+ meetings.command("list").description("List meetings, newest window first by default")
1395
+ ).action(async (opts, cmd) => {
1396
+ const client = getClientFromCommand(cmd);
1397
+ const result = await client.post("/meetings/search", buildQuery(opts));
1398
+ formatOutput(result, getFormat(cmd));
1399
+ });
1400
+ meetings.command("get").description("Get one meeting, with its participants and linked records").argument("<id>", "meeting ID").action(async (id, _opts, cmd) => {
1401
+ const client = getClientFromCommand(cmd);
1402
+ const result = await client.get(`/meetings/${id}`);
1403
+ formatOutput(result, getFormat(cmd));
1404
+ });
1405
+ withQueryOptions(
1406
+ meetings.command("for-record").description("List the meetings a record's email addresses appear in").argument("<recordId>", "record ID").option(
1407
+ "--email-attributes <ids>",
1408
+ "comma-separated email attribute IDs to match participants on"
1409
+ )
1410
+ ).action(
1411
+ async (recordId, opts, cmd) => {
1412
+ const client = getClientFromCommand(cmd);
1413
+ const result = await client.post(`/meetings/records/${recordId}`, {
1414
+ // The API defaults an absent list to none, which matches nothing —
1415
+ // send the key only when the caller named the attributes.
1416
+ ...opts.emailAttributes !== void 0 && {
1417
+ emailAttributeIds: parseCsvList(opts.emailAttributes)
1418
+ },
1419
+ query: buildQuery(opts)
1420
+ });
1421
+ formatOutput(result, getFormat(cmd));
1422
+ }
1423
+ );
1424
+ meetings.command("transcript").description("Read a meeting's completed transcript").argument("<id>", "meeting ID").action(async (id, _opts, cmd) => {
1425
+ const client = getClientFromCommand(cmd);
1426
+ const result = await client.get(`/meetings/${id}/transcript`);
1427
+ formatOutput(result, getFormat(cmd));
1428
+ });
1429
+ 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) => {
1430
+ const override = parseOverride(opts);
1431
+ const client = getClientFromCommand(cmd);
1432
+ const result = await client.put(`/meetings/${id}/recording`, { override });
1433
+ formatOutput(result, getFormat(cmd));
1434
+ });
1435
+ meetings.command("simulate-transcript").description("Complete a fake-provider bot by posting a generated transcript to its webhook").argument("<id>", "meeting ID").option(
1436
+ "--lines <n>",
1437
+ "number of transcript lines to generate",
1438
+ String(DEFAULT_SIMULATED_LINES)
1439
+ ).action(async (id, opts, cmd) => {
1440
+ const secret = process.env.MEETING_BOT_FAKE_SECRET;
1441
+ if (!secret) {
1442
+ throw new ValidationError4("MEETING_BOT_FAKE_SECRET is not set in this shell", []);
1443
+ }
1444
+ const lines = parseCount("--lines", opts.lines);
1445
+ if (lines < 1) throw new ValidationError4("--lines must be at least 1", []);
1446
+ const client = getClientFromCommand(cmd);
1447
+ const meeting = await client.get(`/meetings/${id}`);
1448
+ const { status, botId } = meeting.recording;
1449
+ if (status !== "scheduled" && status !== "recording" || botId === null) {
1450
+ throw new ValidationError4(`Meeting ${id} has no bot to complete (status: ${status})`, []);
1451
+ }
1452
+ const segments = buildSimulatedSegments(simulatedSpeakers(meeting), lines);
1453
+ const startedAt = meeting.startsAt ?? (/* @__PURE__ */ new Date()).toISOString();
1454
+ const endedAt = new Date(
1455
+ new Date(startedAt).getTime() + lines * SIMULATED_LINE_MS
1456
+ ).toISOString();
1457
+ const body = JSON.stringify({
1458
+ botId,
1459
+ transcript: {
1460
+ fullText: segments.map((segment) => segment.text).join("\n"),
1461
+ segments,
1462
+ language: "fr",
1463
+ startedAt,
1464
+ endedAt
1465
+ }
1466
+ });
1467
+ const response = await fetch(
1468
+ `${getGlobalOptions(cmd).apiUrl}/meetings/transcripts/webhook/fake`,
1469
+ {
1470
+ method: "POST",
1471
+ headers: {
1472
+ "content-type": "application/json",
1473
+ [FAKE_MEETING_BOT_SIGNATURE_HEADER]: signFakeWebhook(secret, body)
1474
+ },
1475
+ body
1476
+ }
1477
+ );
1478
+ if (!response.ok) {
1479
+ throw new ValidationError4(`webhook answered ${response.status}`, []);
1480
+ }
1481
+ formatOutput({ botId, segments: segments.length }, getFormat(cmd));
1482
+ });
1483
+ }
1484
+
1485
+ // src/commands/pull.ts
1486
+ import chalk7 from "chalk";
1487
+
1488
+ // src/drift/reconciliation-prompt.ts
1489
+ var PREAMBLE = [
1490
+ "# Standards schema reconciliation",
1491
+ "",
1492
+ "The deployed runtime has drifted from code. Walk each item **with the developer**,",
1493
+ "then edit the fluent builders. The pre-deploy gate (STANDARDS_SYNC_CHECK=1) applies",
1494
+ "your choices at deploy. Loop until the gate reports clean. Never make a destructive",
1495
+ "choice without the developer's explicit confirmation.",
1496
+ ""
1497
+ ].join("\n");
1498
+ function isViewDiverged(view) {
1499
+ return view.origin !== "code" && view.baselineHash !== null && view.dbHash !== view.baselineHash;
1500
+ }
1501
+ function hasReconcilableDrift(state) {
1502
+ return state.drift.customObjects.length > 0 || state.drift.systemObjectDrift.length > 0 || state.views.some(isViewDiverged) || state.views.some((v) => v.origin === "code" && v.workspaceOverlay !== null) || state.views.some((v) => v.origin === "runtime");
1503
+ }
1504
+ function renderAttribute(objectName, sealed, attr) {
1505
+ const action = attr.tolerated ? "Already tolerated \u2014 declare it in the builder to make it a system attribute, or leave tolerated." : `Choose: **declare** \`${attr.name}\` on the \`${objectName}\` builder (\u2192 becomes system on next sync), OR **tolerate** it \u2014 add \`.tolerate(["${attr.name}"])\`${sealed ? " (required: the object is sealed)" : ""}.`;
1506
+ return `- \`${objectName}.${attr.name}\` (${attr.type})
1507
+ ${action}`;
1508
+ }
1509
+ function renderView(view) {
1510
+ const d = view.delta;
1511
+ const lines = [`### view \`${view.object}:${view.name}:${view.type}\``];
1512
+ if (d) {
1513
+ if (d.tabsAdded.length) lines.push(`- tabs added at runtime: ${d.tabsAdded.join(", ")}`);
1514
+ if (d.tabsRemoved.length) lines.push(`- tabs removed at runtime: ${d.tabsRemoved.join(", ")}`);
1515
+ for (const t of d.tabsChanged)
1516
+ lines.push(`- tab "${t.name}" changed: ${t.changedKeys.join(", ")}`);
1517
+ if (d.topLevelChanged.length) lines.push(`- changed: ${d.topLevelChanged.join(", ")}`);
1518
+ }
1519
+ lines.push(
1520
+ "",
1521
+ "Runtime config to fold into the builder call (or record an explicit resolution to let code win):",
1522
+ "```json",
1523
+ JSON.stringify(view.config, null, 2),
1524
+ "```"
1525
+ );
1526
+ return lines.join("\n");
1527
+ }
1528
+ function renderWorkspaceOverlay(view) {
1529
+ const lines = [
1530
+ `### view \`${view.object}:${view.name}:${view.type}\``,
1531
+ "",
1532
+ "Workspace overlay to fold into the builder call (or leave as a runtime-only workspace customization):",
1533
+ "```json",
1534
+ JSON.stringify(view.workspaceOverlay, null, 2),
1535
+ "```"
1536
+ ];
1537
+ return lines.join("\n");
1538
+ }
1539
+ function renderAdoptionCandidate(view) {
1540
+ const lines = [
1541
+ `### view \`${view.object}:${view.name}:${view.type}\``,
1542
+ "",
1543
+ "Runtime-only view to adopt into a builder call (or leave as runtime-only):",
1544
+ "```json",
1545
+ JSON.stringify(view.config, null, 2),
1546
+ "```"
1547
+ ];
1548
+ return lines.join("\n");
1549
+ }
1550
+ function renderReconciliationPrompt(state) {
1551
+ if (!hasReconcilableDrift(state))
1552
+ return `${PREAMBLE}
1553
+ No drift to reconcile \u2014 the runtime matches code.
1554
+ `;
1555
+ const parts = [PREAMBLE];
1556
+ for (const obj of state.drift.customObjects) {
1557
+ parts.push(
1558
+ `## Custom object \`${obj.name}\` (runtime-only)
1559
+ Add an \`object("${obj.name}")\` builder, then its attributes:`
1560
+ );
1561
+ for (const a of obj.attributes) parts.push(renderAttribute(obj.name, false, a));
1562
+ }
1563
+ for (const entry of state.drift.systemObjectDrift) {
1564
+ parts.push(`## \`${entry.objectName}\`${entry.sealed ? " (sealed)" : ""} \u2014 custom attributes`);
1565
+ for (const a of entry.customAttributes)
1566
+ parts.push(renderAttribute(entry.objectName, entry.sealed, a));
1567
+ }
1568
+ const overlaidViews = state.views.filter(
1569
+ (v) => v.origin === "code" && v.workspaceOverlay !== null
1570
+ );
1571
+ if (overlaidViews.length) {
1572
+ parts.push("## Workspace customizations to fold into code");
1573
+ for (const v of overlaidViews) parts.push(renderWorkspaceOverlay(v));
1574
+ }
1575
+ const runtimeViews = state.views.filter((v) => v.origin === "runtime");
1576
+ if (runtimeViews.length) {
1577
+ parts.push("## Runtime views to adopt into code");
1578
+ for (const v of runtimeViews) parts.push(renderAdoptionCandidate(v));
1579
+ }
1580
+ const divergedViews = state.views.filter(isViewDiverged);
1581
+ if (divergedViews.length) {
1582
+ parts.push("## Drifted views");
1583
+ for (const v of divergedViews) parts.push(renderView(v));
1584
+ }
1585
+ return `${parts.join("\n\n")}
1586
+ `;
1587
+ }
1588
+
1589
+ // src/commands/pull.ts
1590
+ async function runPullCommand(client) {
1591
+ let state;
1592
+ try {
1593
+ state = await client.get("/schema/state");
1594
+ } catch (error) {
1595
+ return { exitCode: 2, errorMessage: messageOf(error) };
1596
+ }
1597
+ return { exitCode: 0, prompt: renderReconciliationPrompt(state) };
1598
+ }
1599
+ function registerPullCommand(program) {
1600
+ program.command("pull").description("Emit a self-contained reconciliation prompt for the current runtime drift").option("--json", "Emit the raw /schema/state JSON instead of the prompt").action(async (opts, cmd) => {
1601
+ let client;
1602
+ try {
1603
+ client = getClientFromCommand(cmd);
1604
+ } catch (error) {
1605
+ console.error(chalk7.red(`\u2717 ${messageOf(error)}`));
1606
+ process.exit(2);
1607
+ return;
1608
+ }
1609
+ if (opts.json) {
1610
+ try {
1611
+ const state = await client.get("/schema/state");
1612
+ console.info(JSON.stringify(state, null, 2));
1613
+ process.exit(0);
1614
+ } catch (error) {
1615
+ console.error(chalk7.red(`\u2717 ${messageOf(error)}`));
1616
+ process.exit(2);
1617
+ }
1618
+ return;
1619
+ }
1620
+ const result = await runPullCommand(client);
1621
+ if (result.exitCode === 2) {
1622
+ console.error(chalk7.red(`\u2717 ${result.errorMessage}`));
1623
+ process.exit(2);
1624
+ return;
1625
+ }
1626
+ console.info(result.prompt);
1627
+ process.exit(0);
1628
+ });
1629
+ }
1630
+
1631
+ // src/commands/quotas.ts
1632
+ function registerQuotasCommand(program) {
1633
+ const quotas = program.command("quotas").description("Inspect the tenant's remaining capacity");
1634
+ quotas.command("list").description("List one state per quota key (limit is null when nothing is enforced)").action(async (_opts, cmd) => {
1635
+ const client = getClientFromCommand(cmd);
1636
+ const result = await client.get("/quotas");
1637
+ formatOutput(result, getFormat(cmd));
1638
+ });
1639
+ }
1640
+
1641
+ // src/commands/records.ts
1642
+ import { readFile as readFile3 } from "fs/promises";
1643
+ import { basename } from "path";
1644
+ function parseSortFlag(sort) {
1645
+ const [attribute, direction = "asc"] = sort.split(":");
1646
+ return [{ attribute, direction }];
1647
+ }
1648
+ var COUNT_MODES = ["none", "estimated", "exact"];
1649
+ function parseCountFlag(count) {
1650
+ if (COUNT_MODES.includes(count)) return count;
1651
+ throw new Error(`Invalid --count value "${count}". Expected one of: ${COUNT_MODES.join(", ")}.`);
1652
+ }
1653
+ function buildQueryBody(opts) {
1654
+ const body = {};
1655
+ if (opts.limit) body.limit = Number(opts.limit);
1656
+ if (opts.offset) body.offset = Number(opts.offset);
1657
+ if (opts.sort) body.sorts = parseSortFlag(opts.sort);
1658
+ if (opts.filter) body.filters = JSON.parse(opts.filter);
1659
+ if (opts.fields !== void 0) body.fields = parseCsvList(opts.fields);
1660
+ if (opts.count) body.countMode = parseCountFlag(opts.count);
1661
+ return body;
1662
+ }
1663
+ var COUNT_OPTION_DESCRIPTION = "total count mode (none, estimated, exact); omit for no total in page.total";
1664
+ function registerRecordsCommand(program) {
1665
+ const records = program.command("records").description("Manage records (CRUD + search)");
1666
+ records.command("list").description("List records for an object").argument("<object>", "object name (e.g. contacts)").option("--limit <n>", "max records to return").option("--offset <n>", "number of records to skip").option("--sort <sort>", 'sort rule as "attribute:direction"').option("--filter <json>", "filter state as JSON string").option(
1667
+ "--fields <names>",
1668
+ "comma-separated reference attributes to hydrate (empty for none, omit for all)"
1669
+ ).option("--count <mode>", COUNT_OPTION_DESCRIPTION).action(async (objectName, opts, cmd) => {
1670
+ const client = getClientFromCommand(cmd);
1671
+ const body = buildQueryBody(opts);
1672
+ const result = await client.post(`/records/${objectName}/list`, body);
1673
+ formatOutput(result, getFormat(cmd));
1674
+ });
1675
+ records.command("get").description("Get a record by ID").argument("<object>", "object name").argument("<id>", "record ID").action(async (objectName, id, _opts, cmd) => {
1676
+ const client = getClientFromCommand(cmd);
1677
+ const result = await client.get(`/records/${objectName}/${id}`);
1678
+ formatOutput(result, getFormat(cmd));
1679
+ });
1680
+ records.command("create").description("Create a new record").argument("<object>", "object name").option("--data <json>", "record data as JSON string").action(async (objectName, opts, cmd) => {
1681
+ const data = opts.data ? JSON.parse(opts.data) : {};
1682
+ const client = getClientFromCommand(cmd);
1683
+ const result = await client.post(`/records/${objectName}`, { data });
1684
+ formatOutput(result, getFormat(cmd));
1685
+ });
1686
+ records.command("update").description("Update a record").argument("<object>", "object name").argument("<id>", "record ID").option("--data <json>", "fields to update as JSON string").action(async (objectName, id, opts, cmd) => {
1687
+ const data = opts.data ? JSON.parse(opts.data) : {};
1688
+ const client = getClientFromCommand(cmd);
1689
+ const result = await client.put(`/records/${objectName}/${id}`, data);
1690
+ formatOutput(result, getFormat(cmd));
1691
+ });
1692
+ records.command("delete").description("Delete a record").argument("<object>", "object name").argument("<id>", "record ID").action(async (objectName, id, _opts, cmd) => {
1693
+ const client = getClientFromCommand(cmd);
1694
+ await client.delete(`/records/${objectName}/${id}`);
1695
+ process.stdout.write(`Record ${id} deleted.
1696
+ `);
1697
+ });
1698
+ records.command("search").description("Full-text search records").argument("<object>", "object name").requiredOption("--query <q>", "search query string").option("--limit <n>", "max records to return").option("--offset <n>", "number of records to skip").option("--sort <sort>", 'sort rule as "attribute:direction"').option("--filter <json>", "filter state as JSON string").option(
1699
+ "--fields <names>",
1700
+ "comma-separated reference attributes to hydrate (empty for none, omit for all)"
1701
+ ).option("--count <mode>", COUNT_OPTION_DESCRIPTION).action(async (objectName, opts, cmd) => {
1702
+ const client = getClientFromCommand(cmd);
1703
+ const body = { q: opts.query, ...buildQueryBody(opts) };
1704
+ const result = await client.post(`/records/${objectName}/search`, body);
1705
+ formatOutput(result, getFormat(cmd));
1706
+ });
1707
+ 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) => {
1708
+ const fileContent = await readFile3(opts.file);
1709
+ const fileName = basename(opts.file);
1710
+ const title = opts.title ?? fileName;
1711
+ const form = new FormData();
1712
+ form.append("files", new Blob([fileContent]), fileName);
1713
+ form.append("title", title);
1714
+ if (opts.attribute) form.append("attributeName", opts.attribute);
1715
+ if (opts.parentId) form.append("parentId", opts.parentId);
1716
+ const client = getClientFromCommand(cmd);
1717
+ const result = await client.postMultipart(
1718
+ `/records/${objectName}/${recordId}/documents/attach`,
1719
+ form
1720
+ );
1721
+ formatOutput(result, getFormat(cmd));
1722
+ });
1723
+ }
1724
+
1725
+ // src/commands/root.ts
1726
+ import { stdin as input, stdout as output } from "process";
1727
+ import { createInterface } from "readline/promises";
1728
+ import chalk8 from "chalk";
1729
+ async function promptForMissingValue(label) {
1730
+ const rl = createInterface({ input, output });
1731
+ try {
1732
+ const value = await rl.question(`${label}: `);
1733
+ return value.trim();
1734
+ } finally {
1735
+ rl.close();
1736
+ }
1737
+ }
1738
+ function registerRootCommands(program) {
1739
+ program.command("login").description("Connect the CLI to a Standards instance").option("--url <url>", "Standards API URL", getDefaultApiUrl()).option("--key <key>", "API key to store for this instance").option("--name <name>", "local instance name", "local").action(async (opts) => {
1740
+ const apiKey = opts.key ?? await promptForMissingValue("API key");
1741
+ if (!apiKey) throw new Error("API key is required");
1742
+ const client = createClient({ apiUrl: opts.url, apiKey });
1743
+ await client.get("/api-keys");
1744
+ await upsertProfile({ name: opts.name, apiUrl: opts.url, apiKey });
1745
+ process.stdout.write(
1746
+ `${chalk8.green("\u2713")} Standards instance "${opts.name}" saved and selected.
1747
+ `
1748
+ );
1749
+ process.stdout.write(` API URL: ${opts.url}
1750
+ `);
1751
+ process.stdout.write(` API Key: ${apiKey.slice(0, 8)}...
1752
+ `);
1753
+ process.stdout.write(` Use it with: standards --instance ${opts.name} <command>
1754
+ `);
460
1755
  });
461
1756
  program.command("use").description("Select the active Standards instance").argument("<name>", "instance name").action(async (name) => {
462
1757
  await setCurrentProfile(name);
463
- process.stdout.write(`${chalk4.green("\u2713")} Standards instance "${name}" selected.
1758
+ process.stdout.write(`${chalk8.green("\u2713")} Standards instance "${name}" selected.
464
1759
  `);
465
1760
  });
466
1761
  program.command("instances").description("List configured Standards instances").action(async (_opts, cmd) => {
@@ -479,7 +1774,7 @@ function registerRootCommands(program) {
479
1774
  });
480
1775
  program.command("logout").description("Remove a Standards instance from local CLI config").argument("[name]", "instance name, defaults to current").action(async (name) => {
481
1776
  await removeProfile(name);
482
- process.stdout.write(`${chalk4.green("\u2713")} Standards instance removed.
1777
+ process.stdout.write(`${chalk8.green("\u2713")} Standards instance removed.
483
1778
  `);
484
1779
  });
485
1780
  }
@@ -499,6 +1794,49 @@ function registerSchemaCommand(program) {
499
1794
  });
500
1795
  }
501
1796
 
1797
+ // src/commands/search.ts
1798
+ import chalk9 from "chalk";
1799
+ function registerSearchCommand(program) {
1800
+ const search = program.command("search").description("Maintain the Standards search index");
1801
+ search.command("reindex").description("Clear the tenant search index and re-inject every record").option("--yes", "reindex without confirmation").action(async (opts, cmd) => {
1802
+ if (!opts.yes) {
1803
+ throw new Error(
1804
+ 'A full reindex clears the index before refilling it. Re-run with "--yes" to confirm.'
1805
+ );
1806
+ }
1807
+ const tenant = requireTenant(
1808
+ cmd,
1809
+ 'A full reindex names its tenant explicitly. Pass "--tenant <id>" with the tenant you are authenticated in.'
1810
+ );
1811
+ const client = getClientFromCommand(cmd);
1812
+ await client.post("/admin/search/full-reindex", { tenantId: tenant });
1813
+ process.stdout.write(
1814
+ `${chalk9.green("\u2713")} Full reindex accepted for tenant ${tenant}. It runs in the background \u2014 follow the server logs for progress.
1815
+ `
1816
+ );
1817
+ });
1818
+ }
1819
+
1820
+ // src/commands/sources.ts
1821
+ function registerSourcesCommand(program) {
1822
+ const sources = program.command("sources").description("Inspect and remove remote schema sources on the current tenant");
1823
+ sources.command("list").description("List remote schema sources: id, hash, last apply, object names").action(async (_opts, cmd) => {
1824
+ const client = getClientFromCommand(cmd);
1825
+ const result = await client.get("/schema/sources");
1826
+ formatOutput(result, getFormat(cmd));
1827
+ });
1828
+ sources.command("remove").description(
1829
+ "Remove a remote schema source: its objects are demoted to custom, records are kept"
1830
+ ).argument("<sourceId>", "source id declared in code").option("--yes", "remove without confirmation").action(async (sourceId, opts, cmd) => {
1831
+ if (!opts.yes) {
1832
+ throw new Error('Removing a schema source is explicit. Re-run with "--yes" to confirm.');
1833
+ }
1834
+ const client = getClientFromCommand(cmd);
1835
+ await client.delete(`/schema/sources/${sourceId}`);
1836
+ formatOutput({ removed: sourceId }, getFormat(cmd));
1837
+ });
1838
+ }
1839
+
502
1840
  // src/program.ts
503
1841
  var PUBLIC_COMMANDS = /* @__PURE__ */ new Set(["login", "use", "instances", "current", "logout", "help"]);
504
1842
  function isPublicCommand(actionCommand) {
@@ -507,13 +1845,25 @@ function isPublicCommand(actionCommand) {
507
1845
  }
508
1846
  function createProgram() {
509
1847
  const program = new Command();
510
- program.name("standards").description("CLI to interact with Standards API").version("1.0.0-alpha.3").option("--format <format>", "output format (json, table, csv)", "json").option("--api-url <url>", "API base URL").option("--api-key <key>", "API key for authentication").option("--instance <name>", "Standards instance profile to use");
1848
+ program.name("standards").description("CLI to interact with Standards API").version("1.0.0-alpha.139").option("--format <format>", "output format (json, table, csv)", "json").option("--api-url <url>", "API base URL").option("--api-key <key>", "API key for authentication").option("--instance <name>", "Standards instance profile to use").option("--tenant <id>", "Tenant ID (multi-tenant setups, defaults to primary tenant)");
511
1849
  registerRootCommands(program);
512
1850
  registerRecordsCommand(program);
513
1851
  registerSchemaCommand(program);
1852
+ registerPullCommand(program);
514
1853
  registerDocumentsCommand(program);
1854
+ registerDeviceCommand(program);
1855
+ registerFoldersCommand(program);
515
1856
  registerKeysCommand(program);
516
1857
  registerAuthCommand(program);
1858
+ registerBundlesCommand(program);
1859
+ registerSourcesCommand(program);
1860
+ registerConnectorsCommand(program);
1861
+ registerMcpCommand(program);
1862
+ registerMeetingsCommand(program);
1863
+ registerSearchCommand(program);
1864
+ registerEmbeddingsCommand(program);
1865
+ registerAiCommand(program);
1866
+ registerQuotasCommand(program);
517
1867
  program.hook("preAction", async (_thisCommand, actionCommand) => {
518
1868
  const raw = program.opts();
519
1869
  const resolved = await resolveCliConfig({
@@ -524,13 +1874,14 @@ function createProgram() {
524
1874
  program.setOptionValue("apiUrl", resolved.apiUrl);
525
1875
  program.setOptionValue("apiKey", resolved.apiKey);
526
1876
  program.setOptionValue("profileName", resolved.profileName);
1877
+ program.setOptionValue("tenant", raw.tenant);
527
1878
  if (!(resolved.apiKey || isPublicCommand(actionCommand))) {
528
1879
  console.error(
529
- chalk5.red(
1880
+ chalk10.red(
530
1881
  `\u2717 Error: No Standards instance configured. Run "standards login" or pass --api-key.`
531
1882
  )
532
1883
  );
533
- process.exit(1);
1884
+ process.exit(2);
534
1885
  }
535
1886
  });
536
1887
  return program;
@@ -539,13 +1890,13 @@ async function runProgram(argv = process.argv) {
539
1890
  const program = createProgram();
540
1891
  await program.parseAsync(argv).catch((error) => {
541
1892
  if (error instanceof ApiClientError) {
542
- if (error.statusCode > 0) {
543
- console.error(chalk5.red(`\u2717 Error (${error.statusCode}): ${error.message}`));
1893
+ if (error.status > 0) {
1894
+ console.error(chalk10.red(`\u2717 Error (${error.status}): ${error.message}`));
544
1895
  } else {
545
- console.error(chalk5.red(`\u2717 Error: ${error.message}`));
1896
+ console.error(chalk10.red(`\u2717 Error: ${error.message}`));
546
1897
  }
547
1898
  } else if (error instanceof Error) {
548
- console.error(chalk5.red(`\u2717 Error: ${error.message}`));
1899
+ console.error(chalk10.red(`\u2717 Error: ${error.message}`));
549
1900
  }
550
1901
  process.exit(1);
551
1902
  });