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/README.md +11 -0
- package/dist/index.d.ts +496 -1
- package/dist/index.js +256 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,
|