exponential-sdk 1.29.0 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1325,6 +1325,238 @@ ${text}` : text;
1325
1325
  }
1326
1326
  };
1327
1327
 
1328
+ // src/calendar.ts
1329
+ import { randomUUID } from "crypto";
1330
+ var CALENDAR_MAX_RESULTS = 100;
1331
+ var CALENDAR_MAX_SELECTED = 10;
1332
+ function hostTimeZone() {
1333
+ try {
1334
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
1335
+ } catch {
1336
+ return void 0;
1337
+ }
1338
+ }
1339
+ var CalendarApi = class {
1340
+ constructor(client) {
1341
+ this.client = client;
1342
+ }
1343
+ /** Merged, start-time-sorted events from every connected account. */
1344
+ async listEvents(options = {}) {
1345
+ return await this.client.calendar.getEventsMultiCalendar.query({
1346
+ timeMin: options.from,
1347
+ timeMax: options.to,
1348
+ maxResults: options.maxResults
1349
+ });
1350
+ }
1351
+ /**
1352
+ * Everything on today's date, in the host's local timezone — including
1353
+ * all-day events and meetings that started before now.
1354
+ */
1355
+ async today(options = {}) {
1356
+ const from = /* @__PURE__ */ new Date();
1357
+ from.setHours(0, 0, 0, 0);
1358
+ const to = new Date(from);
1359
+ to.setHours(23, 59, 59, 999);
1360
+ return this.listEvents({ from, to, maxResults: options.maxResults });
1361
+ }
1362
+ /** Events from now until `days` days ahead (default 7). */
1363
+ async upcoming(options = {}) {
1364
+ const from = /* @__PURE__ */ new Date();
1365
+ const to = new Date(from.getTime() + (options.days ?? 7) * 24 * 60 * 60 * 1e3);
1366
+ return this.listEvents({ from, to, maxResults: options.maxResults });
1367
+ }
1368
+ /** Connected accounts, each with its calendars and current selection. */
1369
+ async accounts() {
1370
+ const result = await this.client.calendar.getCalendarAccounts.query();
1371
+ return result.accounts;
1372
+ }
1373
+ /** Per-provider connection state, including accounts needing re-auth. */
1374
+ async connectionStatuses() {
1375
+ return await this.client.calendar.getAllConnectionStatuses.query();
1376
+ }
1377
+ /** Every calendar on one account, straight from the provider (uncached). */
1378
+ async listCalendars(selector = {}) {
1379
+ return await this.client.calendar.listCalendars.query(selector);
1380
+ }
1381
+ /** One account's selected calendars plus its cached calendar list. */
1382
+ async getPreferences(selector = {}) {
1383
+ return await this.client.calendar.getCalendarPreferences.query(
1384
+ selector
1385
+ );
1386
+ }
1387
+ /**
1388
+ * Replace which calendars on an account feed the merged event list.
1389
+ * This is a replace, not a merge — pass the full set you want.
1390
+ */
1391
+ async selectCalendars(input) {
1392
+ return await this.client.calendar.updateSelectedCalendars.mutate(input);
1393
+ }
1394
+ /** Re-fetch an account's calendar list from the provider and re-cache it. */
1395
+ async syncCalendars(selector = {}) {
1396
+ return await this.client.calendar.syncCalendarList.mutate(selector);
1397
+ }
1398
+ async createEvent(input) {
1399
+ const timeZone = input.timeZone ?? hostTimeZone();
1400
+ return await this.client.calendar.createEvent.mutate({
1401
+ summary: input.summary,
1402
+ description: input.description,
1403
+ // RFC3339 UTC — unambiguous regardless of host offset. `timeZone` only
1404
+ // tells the provider which zone to render the event in.
1405
+ start: { dateTime: input.start.toISOString(), timeZone },
1406
+ end: { dateTime: input.end.toISOString(), timeZone },
1407
+ attendees: input.attendees?.map((email) => ({ email })),
1408
+ conferenceData: input.meet ? {
1409
+ createRequest: {
1410
+ requestId: randomUUID(),
1411
+ conferenceSolutionKey: { type: "hangoutsMeet" }
1412
+ }
1413
+ } : void 0,
1414
+ calendarId: input.calendarId,
1415
+ provider: input.provider
1416
+ });
1417
+ }
1418
+ /**
1419
+ * Remove a calendar connection. Deletes the stored tokens and calendar
1420
+ * preferences; it never touches the user's ability to sign in with that
1421
+ * account. Reconnecting means redoing the OAuth flow in the web app.
1422
+ */
1423
+ async disconnect(selector = {}) {
1424
+ return await this.client.calendar.disconnect.mutate(selector);
1425
+ }
1426
+ };
1427
+
1428
+ // src/cycles.ts
1429
+ function describeCycle(c) {
1430
+ return c.productId ? `"${c.name}" (slug ${c.slug}, product ${c.productId})` : `"${c.name}" (slug ${c.slug}, workspace-shared)`;
1431
+ }
1432
+ var CyclesApi = class {
1433
+ constructor(client) {
1434
+ this.client = client;
1435
+ }
1436
+ /**
1437
+ * List cycles in a workspace, newest start date first. Pass `productId` to
1438
+ * scope to one product's timeline (plus legacy workspace-shared cycles).
1439
+ *
1440
+ * Note this is not a pure read: the server reconciles cycle statuses against
1441
+ * today's date and (unless `autoCreate: false`) provisions upcoming cycles —
1442
+ * scoped to `productId` when given.
1443
+ */
1444
+ async list(options) {
1445
+ return await this.client.product.cycle.list.query({
1446
+ workspaceId: options.workspaceId,
1447
+ productId: options.productId,
1448
+ status: options.status,
1449
+ autoCreate: options.autoCreate
1450
+ });
1451
+ }
1452
+ async get(id) {
1453
+ return await this.client.product.cycle.getById.query({ id });
1454
+ }
1455
+ /**
1456
+ * The current (`ACTIVE`) cycle, or `null` when none is running (e.g. a gap
1457
+ * between cycles).
1458
+ *
1459
+ * Cycles are product-scoped, so a workspace can have one ACTIVE cycle per
1460
+ * product. Pass `product` (a product CUID or slug) to get that product's
1461
+ * current cycle — a cycle scoped to the product wins over a legacy
1462
+ * workspace-shared one.
1463
+ *
1464
+ * Without `product`, this stays backward compatible for workspaces with a
1465
+ * single timeline: if every ACTIVE cycle belongs to the same product (or all
1466
+ * are workspace-shared), the most recently started wins. If ACTIVE cycles
1467
+ * from different products coexist, it throws rather than silently picking
1468
+ * an arbitrary product's cycle.
1469
+ */
1470
+ async current(workspaceId, product) {
1471
+ if (product) {
1472
+ const productId = await this.resolveProductId(workspaceId, product);
1473
+ const active2 = await this.list({ workspaceId, productId, status: "ACTIVE", autoCreate: false });
1474
+ return active2.find((c) => c.productId === productId) ?? active2[0] ?? null;
1475
+ }
1476
+ const active = await this.list({ workspaceId, status: "ACTIVE", autoCreate: false });
1477
+ if (active.length <= 1) return active[0] ?? null;
1478
+ const timelines = new Set(active.map((c) => c.productId ?? null));
1479
+ if (timelines.size === 1) return active[0] ?? null;
1480
+ throw new Error(
1481
+ `Multiple cycles are ACTIVE in this workspace \u2014 one per product \u2014 so "current" is ambiguous. Pass a product id or slug to pick one. Active: ${active.map(describeCycle).join(", ")}.`
1482
+ );
1483
+ }
1484
+ /**
1485
+ * Resolve what a user typed to a cycle.
1486
+ *
1487
+ * Accepts, in order of precedence:
1488
+ * - `"current"` / `"active"` → the ACTIVE cycle (see `current()`)
1489
+ * - a cycle CUID
1490
+ * - a cycle slug
1491
+ * - a cycle name (case-insensitive)
1492
+ *
1493
+ * Pass `product` (a product CUID or slug) to scope the lookup to that
1494
+ * product's timeline. Cycle auto-numbering is per-product, so two products
1495
+ * can both have a cycle named "Cycle 9" in one workspace: with `product`
1496
+ * set, a cycle scoped to that product beats a legacy workspace-shared one
1497
+ * of the same name; without it, an ambiguous name throws rather than
1498
+ * silently picking one. Ids and slugs are unique per workspace and never
1499
+ * ambiguous.
1500
+ *
1501
+ * Throws rather than returning null so callers never silently file work
1502
+ * against the wrong cycle — or against none at all.
1503
+ */
1504
+ async resolve(workspaceId, slugOrIdOrName, product) {
1505
+ const candidate = slugOrIdOrName.trim();
1506
+ const lower = candidate.toLowerCase();
1507
+ if (lower === "current" || lower === "active") {
1508
+ const cycle = await this.current(workspaceId, product);
1509
+ if (!cycle) {
1510
+ throw new Error(
1511
+ product ? `No cycle is currently active for product "${product}". Pass an explicit cycle slug or id, or create a cycle first.` : "No cycle is currently active in this workspace. Pass an explicit cycle slug or id, or create a cycle first."
1512
+ );
1513
+ }
1514
+ return cycle;
1515
+ }
1516
+ const productId = product ? await this.resolveProductId(workspaceId, product) : void 0;
1517
+ const cycles = await this.list({ workspaceId, productId, autoCreate: false });
1518
+ const exact = cycles.find((c) => c.id === candidate) ?? cycles.find((c) => c.slug === candidate);
1519
+ if (exact) return exact;
1520
+ const byName = cycles.filter((c) => c.name.toLowerCase() === lower);
1521
+ if (byName.length === 1) return byName[0];
1522
+ if (byName.length > 1) {
1523
+ if (productId) {
1524
+ const scoped = byName.filter((c) => c.productId === productId);
1525
+ if (scoped.length === 1) return scoped[0];
1526
+ }
1527
+ throw new Error(
1528
+ `Cycle name "${candidate}" is ambiguous: ${byName.map(describeCycle).join(", ")}. Use the slug or id instead${productId ? "" : ", or pass a product to scope the lookup"}.`
1529
+ );
1530
+ }
1531
+ const known = cycles.map((c) => c.slug).join(", ");
1532
+ throw new Error(
1533
+ `Cycle "${candidate}" not found. Available: ${known || "(none)"}`
1534
+ );
1535
+ }
1536
+ async create(input) {
1537
+ return await this.client.product.cycle.create.mutate(input);
1538
+ }
1539
+ async update(input) {
1540
+ return await this.client.product.cycle.update.mutate(input);
1541
+ }
1542
+ async delete(id) {
1543
+ return await this.client.product.cycle.delete.mutate({ id });
1544
+ }
1545
+ /**
1546
+ * Turn a product reference (CUID or slug) into the CUID the cycle endpoints
1547
+ * expect. CUIDs pass through without a lookup — the server validates access.
1548
+ */
1549
+ async resolveProductId(workspaceId, product) {
1550
+ const ref = product.trim();
1551
+ if (/^c[a-z0-9]{20,}$/.test(ref)) return ref;
1552
+ const match = await this.client.product.product.getBySlug.query({
1553
+ workspaceId,
1554
+ slug: ref
1555
+ });
1556
+ return match.id;
1557
+ }
1558
+ };
1559
+
1328
1560
  // src/pages.ts
1329
1561
  var PagesApi = class {
1330
1562
  constructor(client) {
@@ -1607,6 +1839,22 @@ var SearchApi = class {
1607
1839
  }
1608
1840
  };
1609
1841
 
1842
+ // src/users.ts
1843
+ var UsersApi = class {
1844
+ constructor(client) {
1845
+ this.client = client;
1846
+ }
1847
+ /**
1848
+ * The account this client's token signs in as. For an External agent's key
1849
+ * the result is the agent's shadow user: `isAgent` is true and `agent`
1850
+ * carries the agent's name and owner — the identity to grant access to when
1851
+ * a command comes back FORBIDDEN.
1852
+ */
1853
+ async me() {
1854
+ return await this.client.user.getCurrentUser.query();
1855
+ }
1856
+ };
1857
+
1610
1858
  // src/time.ts
1611
1859
  function startOfLocalDay(date) {
1612
1860
  const d = typeof date === "string" && /^\d{4}-\d{2}-\d{2}$/.test(date) ? /* @__PURE__ */ new Date(`${date}T00:00:00`) : new Date(date);
@@ -1896,12 +2144,15 @@ var ExponentialClient = class {
1896
2144
  this.labels = new LabelsApi(this.client);
1897
2145
  this.areas = new AreasApi(this.client);
1898
2146
  this.meetings = new MeetingsApi(this.client);
2147
+ this.calendar = new CalendarApi(this.client);
2148
+ this.cycles = new CyclesApi(this.client);
1899
2149
  this.pages = new PagesApi(this.client);
1900
2150
  this.databases = new DatabasesApi(this.client);
1901
2151
  this.resources = new ResourcesApi(this.client);
1902
2152
  this.requirements = new RequirementsApi(this.client);
1903
2153
  this.scopes = new ScopesApi(this.client);
1904
2154
  this.search = new SearchApi(this.client);
2155
+ this.users = new UsersApi(this.client);
1905
2156
  this.time = new TimeApi(this.client);
1906
2157
  this.agentRuns = new AgentRunsApi(this.client);
1907
2158
  }
@@ -1932,6 +2183,8 @@ var ExponentialClient = class {
1932
2183
  labels;
1933
2184
  areas;
1934
2185
  meetings;
2186
+ calendar;
2187
+ cycles;
1935
2188
  pages;
1936
2189
  /** Databases — typed tables inside pages (ADR-0078). */
1937
2190
  databases;
@@ -1939,6 +2192,7 @@ var ExponentialClient = class {
1939
2192
  requirements;
1940
2193
  scopes;
1941
2194
  search;
2195
+ users;
1942
2196
  /** Time entries — the Daily worklog's write path (ADR-0061). */
1943
2197
  time;
1944
2198
  /** Agent runs — the local runner's claim/heartbeat/append/finish (ADR-0067); agent-key only. */
@@ -1946,6 +2200,8 @@ var ExponentialClient = class {
1946
2200
  };
1947
2201
  export {
1948
2202
  AgentRunsApi,
2203
+ CALENDAR_MAX_RESULTS,
2204
+ CALENDAR_MAX_SELECTED,
1949
2205
  CeremoniesApi,
1950
2206
  DEFAULT_GAP_MINUTES,
1951
2207
  ExponentialClient,