exponential-sdk 1.28.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
@@ -519,6 +519,52 @@ var WorkspacesApi = class {
519
519
  }
520
520
  };
521
521
 
522
+ // src/positions.ts
523
+ var PositionsApi = class {
524
+ constructor(client) {
525
+ this.client = client;
526
+ }
527
+ /** Every Position with its holders, plus each member's coverage. */
528
+ async list(workspaceId) {
529
+ return await this.client.position.list.query({ workspaceId });
530
+ }
531
+ /** One Position by id, from `list` (the router has no single-row read). */
532
+ async get(workspaceId, positionId) {
533
+ const { positions } = await this.list(workspaceId);
534
+ return positions.find((position) => position.id === positionId) ?? null;
535
+ }
536
+ /** How many members have a stated remit, and whether to offer an import. */
537
+ async coverage(workspaceId) {
538
+ return await this.client.position.coverage.query({ workspaceId });
539
+ }
540
+ async create(input) {
541
+ return await this.client.position.create.mutate(input);
542
+ }
543
+ async update(input) {
544
+ const { workspaceId, positionId, ...edits } = input;
545
+ if (Object.values(edits).every((value) => value === void 0)) {
546
+ throw new Error("Nothing to update: pass title, remit, notAccountableFor or holderUserIds");
547
+ }
548
+ return await this.client.position.update.mutate(input);
549
+ }
550
+ /** Holders cascade. */
551
+ async delete(workspaceId, positionId) {
552
+ return await this.client.position.delete.mutate({ workspaceId, positionId });
553
+ }
554
+ /** Replace the holder set; `[]` removes everyone. */
555
+ async setHolders(workspaceId, positionId, userIds) {
556
+ return await this.client.position.setHolders.mutate({ workspaceId, positionId, userIds });
557
+ }
558
+ /**
559
+ * Upsert many Positions by title (matched case-insensitively, never renamed).
560
+ * An update replaces the remit and only ADDS holders. Run with
561
+ * `dryRun: true` first to see the plan.
562
+ */
563
+ async importMany(input) {
564
+ return await this.client.position.importMany.mutate(input);
565
+ }
566
+ };
567
+
522
568
  // src/ceremonies.ts
523
569
  var CeremoniesApi = class {
524
570
  constructor(client) {
@@ -1279,6 +1325,238 @@ ${text}` : text;
1279
1325
  }
1280
1326
  };
1281
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
+
1282
1560
  // src/pages.ts
1283
1561
  var PagesApi = class {
1284
1562
  constructor(client) {
@@ -1561,6 +1839,22 @@ var SearchApi = class {
1561
1839
  }
1562
1840
  };
1563
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
+
1564
1858
  // src/time.ts
1565
1859
  function startOfLocalDay(date) {
1566
1860
  const d = typeof date === "string" && /^\d{4}-\d{2}-\d{2}$/.test(date) ? /* @__PURE__ */ new Date(`${date}T00:00:00`) : new Date(date);
@@ -1836,6 +2130,7 @@ var ExponentialClient = class {
1836
2130
  this.goals = new GoalsApi(this.client, this.keyResults);
1837
2131
  this.projects = new ProjectsApi(this.client);
1838
2132
  this.workspaces = new WorkspacesApi(this.client);
2133
+ this.positions = new PositionsApi(this.client);
1839
2134
  this.ceremonies = new CeremoniesApi(this.client);
1840
2135
  this.contacts = new ContactsApi(this.client);
1841
2136
  this.decisions = new DecisionsApi(this.client);
@@ -1849,12 +2144,15 @@ var ExponentialClient = class {
1849
2144
  this.labels = new LabelsApi(this.client);
1850
2145
  this.areas = new AreasApi(this.client);
1851
2146
  this.meetings = new MeetingsApi(this.client);
2147
+ this.calendar = new CalendarApi(this.client);
2148
+ this.cycles = new CyclesApi(this.client);
1852
2149
  this.pages = new PagesApi(this.client);
1853
2150
  this.databases = new DatabasesApi(this.client);
1854
2151
  this.resources = new ResourcesApi(this.client);
1855
2152
  this.requirements = new RequirementsApi(this.client);
1856
2153
  this.scopes = new ScopesApi(this.client);
1857
2154
  this.search = new SearchApi(this.client);
2155
+ this.users = new UsersApi(this.client);
1858
2156
  this.time = new TimeApi(this.client);
1859
2157
  this.agentRuns = new AgentRunsApi(this.client);
1860
2158
  }
@@ -1869,6 +2167,8 @@ var ExponentialClient = class {
1869
2167
  keyResults;
1870
2168
  projects;
1871
2169
  workspaces;
2170
+ /** Positions — a workspace's roles & responsibilities (ADR-0068). */
2171
+ positions;
1872
2172
  /** Ceremonies and their occurrences (ADR-0059). */
1873
2173
  ceremonies;
1874
2174
  contacts;
@@ -1883,6 +2183,8 @@ var ExponentialClient = class {
1883
2183
  labels;
1884
2184
  areas;
1885
2185
  meetings;
2186
+ calendar;
2187
+ cycles;
1886
2188
  pages;
1887
2189
  /** Databases — typed tables inside pages (ADR-0078). */
1888
2190
  databases;
@@ -1890,6 +2192,7 @@ var ExponentialClient = class {
1890
2192
  requirements;
1891
2193
  scopes;
1892
2194
  search;
2195
+ users;
1893
2196
  /** Time entries — the Daily worklog's write path (ADR-0061). */
1894
2197
  time;
1895
2198
  /** Agent runs — the local runner's claim/heartbeat/append/finish (ADR-0067); agent-key only. */
@@ -1897,9 +2200,12 @@ var ExponentialClient = class {
1897
2200
  };
1898
2201
  export {
1899
2202
  AgentRunsApi,
2203
+ CALENDAR_MAX_RESULTS,
2204
+ CALENDAR_MAX_SELECTED,
1900
2205
  CeremoniesApi,
1901
2206
  DEFAULT_GAP_MINUTES,
1902
2207
  ExponentialClient,
2208
+ PositionsApi,
1903
2209
  ROUND_MINUTES,
1904
2210
  TRPCClientError,
1905
2211
  TimeApi,