@hasna/skills 0.5.0 → 0.5.1

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/sdk/index.js CHANGED
@@ -25462,7 +25462,7 @@ class MissingSkillsFleetError extends Error {
25462
25462
  // package.json
25463
25463
  var package_default = {
25464
25464
  name: "@hasna/skills",
25465
- version: "0.5.0",
25465
+ version: "0.5.1",
25466
25466
  description: "Skills library for AI coding agents",
25467
25467
  type: "module",
25468
25468
  bin: {
@@ -52164,12 +52164,106 @@ function createOfflineGate(options) {
52164
52164
  }
52165
52165
  };
52166
52166
  }
52167
+ // src/lib/remote-workspace-selection.ts
52168
+ var record = (v2) => !!v2 && typeof v2 === "object" && !Array.isArray(v2);
52169
+ var uuid = (v2) => typeof v2 === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v2);
52170
+ var text = (v2, max = 1024) => typeof v2 === "string" && !!v2.trim() && v2.length <= max && !/[\p{Cc}\p{Cs}\u2028\u2029]/u.test(v2);
52171
+ var role = (v2) => typeof v2 === "string" && ["owner", "admin", "member", "viewer"].includes(v2);
52172
+ var invalidWorkspaceResult = "The server returned an invalid workspace selection result.";
52173
+
52174
+ class WorkspaceContextInputError extends Error {
52175
+ constructor() {
52176
+ super("Provide the observed user ID and exact lowercase membership ID.");
52177
+ this.name = "WorkspaceContextInputError";
52178
+ }
52179
+ }
52180
+
52181
+ class WorkspaceIdentityMismatchError extends Error {
52182
+ constructor() {
52183
+ super("The verified account does not match the requested workspace context.");
52184
+ this.name = "WorkspaceIdentityMismatchError";
52185
+ }
52186
+ }
52187
+ function workspaceExpectedUserId(value) {
52188
+ if (!uuid(value))
52189
+ throw new WorkspaceContextInputError;
52190
+ return value;
52191
+ }
52192
+ function workspaceContext(value) {
52193
+ if (!record(value) || Object.keys(value).sort().join(",") !== "membershipId,userId" || !uuid(value.userId) || !uuid(value.membershipId))
52194
+ throw new WorkspaceContextInputError;
52195
+ return { userId: value.userId, membershipId: value.membershipId };
52196
+ }
52197
+ function invalid() {
52198
+ throw new Error(invalidWorkspaceResult);
52199
+ }
52200
+ function organization(v2) {
52201
+ if (!record(v2) || !uuid(v2.id) || !text(v2.slug) || !text(v2.name))
52202
+ return invalid();
52203
+ return { id: v2.id, slug: v2.slug, name: v2.name };
52204
+ }
52205
+ function parseAccountWorkspaces(value) {
52206
+ if (!record(value) || !Array.isArray(value.workspaces) || !value.workspaces.length || value.workspaces.length > 1000)
52207
+ return invalid();
52208
+ const workspaces = value.workspaces.map((v2) => {
52209
+ if (!record(v2) || !uuid(v2.membershipId) || !role(v2.role) || typeof v2.current !== "boolean")
52210
+ return invalid();
52211
+ return { membershipId: v2.membershipId, organization: organization(v2.organization), role: v2.role, current: v2.current };
52212
+ });
52213
+ if (workspaces.filter((w2) => w2.current).length !== 1 || new Set(workspaces.map((w2) => w2.membershipId)).size !== workspaces.length || new Set(workspaces.map((w2) => w2.organization.id)).size !== workspaces.length)
52214
+ return invalid();
52215
+ return { workspaces };
52216
+ }
52217
+ function parseWorkspaceIdentity(value, expectedUserId) {
52218
+ if (!record(value))
52219
+ return invalid();
52220
+ const user = value.user;
52221
+ if (!record(user) || !uuid(user.id) || !uuid(user.membershipId) || !text(user.email, 320) || !(user.displayName === null || text(user.displayName)) || !role(user.role))
52222
+ return invalid();
52223
+ if (user.id !== expectedUserId)
52224
+ throw new WorkspaceIdentityMismatchError;
52225
+ return { user: { id: user.id, membershipId: user.membershipId, email: user.email, displayName: user.displayName, role: user.role }, organization: organization(value.organization) };
52226
+ }
52227
+ function sessionToken(value) {
52228
+ if (typeof value !== "string" || !value || value.length > 8192 || /[^\x21-\x7e]/.test(value) || value.startsWith("sk_"))
52229
+ return invalid();
52230
+ return value;
52231
+ }
52232
+ function parseWorkspaceSession(value, expected) {
52233
+ const identity = parseWorkspaceIdentity(value, expected.userId);
52234
+ if (identity.user.membershipId !== expected.membershipId)
52235
+ throw new WorkspaceIdentityMismatchError;
52236
+ return { token: sessionToken(value.token), ...identity };
52237
+ }
52238
+ function parseWorkspaceLogin(value, expectedUserId) {
52239
+ const user = record(value) && value.user;
52240
+ if (!record(value) || !record(user) || !uuid(user.id))
52241
+ return invalid();
52242
+ if (expectedUserId !== undefined && user.id !== expectedUserId)
52243
+ throw new WorkspaceIdentityMismatchError;
52244
+ return { token: sessionToken(value.token), userId: user.id };
52245
+ }
52246
+ var workspaceSelectionFailures = {
52247
+ INVALID_WORKSPACE_SELECTION: [400, "Provide only the exact membership ID from your workspace list."],
52248
+ SESSION_EXPIRED: [401, "Sign in again before selecting a workspace."],
52249
+ ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
52250
+ INTERACTIVE_SESSION_REQUIRED: [403, "Interactive sign-in is required to select a workspace."],
52251
+ WORKSPACE_UNAVAILABLE: [404, "Workspace is unavailable. Refresh your workspace list."],
52252
+ WORKSPACE_BUSY: [503, "Workspace is busy. Refresh before retrying."]
52253
+ };
52254
+ function workspaceSelectionFailure(value, status) {
52255
+ if (!record(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceSelectionFailures, value.code))
52256
+ return null;
52257
+ const code = value.code;
52258
+ return workspaceSelectionFailures[code][0] === status ? code : null;
52259
+ }
52260
+
52167
52261
  // src/lib/remote-workspace.ts
52168
- var record = (value) => !!value && typeof value === "object" && !Array.isArray(value);
52262
+ var record2 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
52169
52263
  var cursor = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,512}$/.test(value);
52170
- var uuid = (value) => typeof value === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(value);
52264
+ var uuid2 = (value) => typeof value === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(value);
52171
52265
  function workspaceMembersQuery(options = {}) {
52172
- if (!record(options) || Object.keys(options).some((key) => key !== "limit" && key !== "cursor") || options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 100) || options.cursor !== undefined && !cursor(options.cursor))
52266
+ if (!record2(options) || Object.keys(options).some((key) => key !== "limit" && key !== "cursor") || options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 100) || options.cursor !== undefined && !cursor(options.cursor))
52173
52267
  throw new Error("Use a roster limit from 1 to 100 and an unchanged continuation cursor.");
52174
52268
  const query = new URLSearchParams;
52175
52269
  if (options.limit !== undefined)
@@ -52185,7 +52279,7 @@ function timestamp(value) {
52185
52279
  return Number.isFinite(time) && new Date(time).toISOString().slice(0, 23) === value.slice(0, 23);
52186
52280
  }
52187
52281
  function parseMember(row, fail) {
52188
- if (!record(row) || !uuid(row.membershipId) || !uuid(row.userId) || typeof row.email !== "string" || !row.email || !(row.displayName === null || typeof row.displayName === "string") || !isRole(row.role) || !timestamp(row.createdAt))
52282
+ if (!record2(row) || !uuid2(row.membershipId) || !uuid2(row.userId) || typeof row.email !== "string" || !row.email || !(row.displayName === null || typeof row.displayName === "string") || !isRole(row.role) || !timestamp(row.createdAt))
52189
52283
  return fail();
52190
52284
  return {
52191
52285
  membershipId: row.membershipId,
@@ -52205,12 +52299,12 @@ class WorkspaceMemberInputError extends Error {
52205
52299
  }
52206
52300
  }
52207
52301
  function mutationInput(membershipId, input, roleChange) {
52208
- if (typeof membershipId !== "string" || !uuid(membershipId) || membershipId !== membershipId.toLowerCase() || !record(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
52302
+ if (typeof membershipId !== "string" || !uuid2(membershipId) || membershipId !== membershipId.toLowerCase() || !record2(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
52209
52303
  throw new WorkspaceMemberInputError;
52210
- const expectedRole = input.expectedRole, role = roleChange ? input.role : undefined;
52211
- if (!isRole(expectedRole) || roleChange && !isRole(role))
52304
+ const expectedRole = input.expectedRole, role2 = roleChange ? input.role : undefined;
52305
+ if (!isRole(expectedRole) || roleChange && !isRole(role2))
52212
52306
  throw new WorkspaceMemberInputError;
52213
- return { membershipId, role, expectedRole };
52307
+ return { membershipId, role: role2, expectedRole };
52214
52308
  }
52215
52309
  function workspaceMemberRoleInput(membershipId, input) {
52216
52310
  const value = mutationInput(membershipId, input, true);
@@ -52221,19 +52315,19 @@ function workspaceMemberRemovalInput(membershipId, input) {
52221
52315
  return { membershipId: value.membershipId, body: { expectedRole: value.expectedRole } };
52222
52316
  }
52223
52317
  var invalidMemberResult = "The server returned an invalid workspace member result. Refresh the roster before another action.";
52224
- function parseWorkspaceMemberRoleResult(value, membershipId, role) {
52318
+ function parseWorkspaceMemberRoleResult(value, membershipId, role2) {
52225
52319
  const fail = () => {
52226
52320
  throw new Error(invalidMemberResult);
52227
52321
  };
52228
- if (!record(value) || !uuid(value.organizationId) || typeof value.changed !== "boolean")
52322
+ if (!record2(value) || !uuid2(value.organizationId) || typeof value.changed !== "boolean")
52229
52323
  return fail();
52230
52324
  const member = parseMember(value.member, fail);
52231
- if (member.membershipId !== membershipId || member.role !== role)
52325
+ if (member.membershipId !== membershipId || member.role !== role2)
52232
52326
  return fail();
52233
52327
  return { organizationId: value.organizationId, member, changed: value.changed };
52234
52328
  }
52235
52329
  function parseWorkspaceMemberRemovalResult(value, membershipId) {
52236
- if (!record(value) || !uuid(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
52330
+ if (!record2(value) || !uuid2(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
52237
52331
  throw new Error(invalidMemberResult);
52238
52332
  return { organizationId: value.organizationId, membershipId, removed: true, alreadyRemoved: value.alreadyRemoved };
52239
52333
  }
@@ -52250,7 +52344,7 @@ var workspaceMemberFailures = {
52250
52344
  MEMBERSHIP_BUSY: [503, "Membership is busy. Refresh the roster before another action."]
52251
52345
  };
52252
52346
  function workspaceMemberFailure(value, status) {
52253
- if (!record(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
52347
+ if (!record2(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
52254
52348
  return null;
52255
52349
  const code = value.code;
52256
52350
  return workspaceMemberFailures[code][0] === status ? code : null;
@@ -52259,7 +52353,7 @@ function parseWorkspaceMembersPage(value) {
52259
52353
  const fail = () => {
52260
52354
  throw new Error("The server returned an invalid workspace roster.");
52261
52355
  };
52262
- if (!record(value) || !uuid(value.organizationId) || !Array.isArray(value.members) || value.members.length > 100 || !(value.nextCursor === null || cursor(value.nextCursor)) || !value.members.length && value.nextCursor !== null)
52356
+ if (!record2(value) || !uuid2(value.organizationId) || !Array.isArray(value.members) || value.members.length > 100 || !(value.nextCursor === null || cursor(value.nextCursor)) || !value.members.length && value.nextCursor !== null)
52263
52357
  return fail();
52264
52358
  const members = value.members.map((row) => parseMember(row, fail));
52265
52359
  if (new Set(members.map((row) => row.membershipId)).size !== members.length)
@@ -52430,11 +52524,11 @@ function parseUpdatedProfile(value) {
52430
52524
  return { user: { id: user.id, email: user.email, displayName: user.displayName, role: user.role } };
52431
52525
  }
52432
52526
  function parseUpdatedWorkspace(value) {
52433
- const organization = isRecord5(value) && value.organization;
52434
- if (!isRecord5(organization) || !string(organization.id) || !string(organization.slug) || !string(organization.name)) {
52527
+ const organization2 = isRecord5(value) && value.organization;
52528
+ if (!isRecord5(organization2) || !string(organization2.id) || !string(organization2.slug) || !string(organization2.name)) {
52435
52529
  throw new Error("The server returned an invalid workspace.");
52436
52530
  }
52437
- return { organization: { id: organization.id, slug: organization.slug, name: organization.name } };
52531
+ return { organization: { id: organization2.id, slug: organization2.slug, name: organization2.name } };
52438
52532
  }
52439
52533
 
52440
52534
  // src/lib/remote-client.ts
@@ -52472,6 +52566,16 @@ class RemoteWorkspaceMemberError extends RemoteRequestError {
52472
52566
  }
52473
52567
  }
52474
52568
 
52569
+ class RemoteWorkspaceSelectionError extends RemoteRequestError {
52570
+ code;
52571
+ constructor(path, code) {
52572
+ super(path, workspaceSelectionFailures[code][0]);
52573
+ this.code = code;
52574
+ this.name = "RemoteWorkspaceSelectionError";
52575
+ this.message = workspaceSelectionFailures[code][1];
52576
+ }
52577
+ }
52578
+
52475
52579
  class RemoteCapabilityUnavailableError extends RemoteRequestError {
52476
52580
  code = "SUBSCRIPTION_CHECKOUT_UNAVAILABLE";
52477
52581
  constructor() {
@@ -52493,6 +52597,7 @@ class RemoteSkillsClient {
52493
52597
  return fetch(`${this.apiUrl}${path}`, {
52494
52598
  ...options,
52495
52599
  redirect: "error",
52600
+ credentials: "omit",
52496
52601
  signal: options?.signal ?? AbortSignal.timeout(15000),
52497
52602
  headers: {
52498
52603
  Authorization: `Bearer ${this.apiKey}`,
@@ -52599,6 +52704,65 @@ class RemoteSkillsClient {
52599
52704
  async getIdentity() {
52600
52705
  return (await this.requestNewRoute("/api/auth/whoami")).json();
52601
52706
  }
52707
+ async listAccountWorkspaces(expectedUserId) {
52708
+ const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
52709
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
52710
+ let identity;
52711
+ if (expected !== undefined) {
52712
+ const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
52713
+ if (!value || typeof value !== "object" || value.authMethod !== "jwt")
52714
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces", "INTERACTIVE_SESSION_REQUIRED");
52715
+ identity = parseWorkspaceIdentity(value, expected);
52716
+ }
52717
+ const result = parseAccountWorkspaces(await connection.requestWorkspaceSelection("/api/v1/account/workspaces"));
52718
+ const current = result.workspaces.find((workspace) => workspace.current);
52719
+ if (identity && (current.membershipId !== identity.user.membershipId || current.organization.id !== identity.organization.id))
52720
+ throw new WorkspaceIdentityMismatchError;
52721
+ return result;
52722
+ }
52723
+ async switchWorkspace(context) {
52724
+ const target = workspaceContext(context);
52725
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
52726
+ const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
52727
+ if (!value || typeof value !== "object" || value.authMethod !== "jwt")
52728
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
52729
+ parseWorkspaceIdentity(value, target.userId);
52730
+ const selected = parseWorkspaceSession(await connection.requestWorkspaceSelection("/api/v1/account/workspaces/switch", {
52731
+ method: "POST",
52732
+ body: JSON.stringify({ membershipId: target.membershipId })
52733
+ }), target);
52734
+ const verified = await new RemoteSkillsClient(selected.token, connection.apiUrl).requestWorkspaceSelection("/api/auth/whoami");
52735
+ if (!verified || typeof verified !== "object" || verified.authMethod !== "jwt")
52736
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
52737
+ const identity = parseWorkspaceIdentity(verified, target.userId);
52738
+ if (identity.user.membershipId !== target.membershipId || identity.organization.id !== selected.organization.id)
52739
+ throw new WorkspaceIdentityMismatchError;
52740
+ return { token: selected.token, ...identity };
52741
+ }
52742
+ async requestWorkspaceSelection(path, options) {
52743
+ let response;
52744
+ try {
52745
+ response = await this.request(path, { ...options, credentials: "omit" });
52746
+ } catch {
52747
+ throw new Error("Unable to reach the Skills workspace API.");
52748
+ }
52749
+ let value;
52750
+ try {
52751
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 1024 * 1024 : 4096)));
52752
+ } catch {
52753
+ if (response.ok)
52754
+ throw new Error(invalidWorkspaceResult);
52755
+ }
52756
+ if (!response.ok) {
52757
+ const code = workspaceSelectionFailure(value, response.status);
52758
+ if (code)
52759
+ throw new RemoteWorkspaceSelectionError(path, code);
52760
+ if (response.status === 404 || response.status === 405)
52761
+ throw new RemoteRouteUnsupportedError(path, response.status, this.apiUrl);
52762
+ throw new RemoteRequestError(path, response.status);
52763
+ }
52764
+ return value;
52765
+ }
52602
52766
  async updateProfile(input) {
52603
52767
  const body = customerNamePatch(input, "displayName");
52604
52768
  return parseUpdatedProfile(await (await this.requestNewRoute("/api/v1/account/profile", { method: "PATCH", body: JSON.stringify(body) })).json());
@@ -52901,13 +53065,13 @@ class RemoteSkillsClient {
52901
53065
  return normalizeUpdatedSincePage(await response.json());
52902
53066
  }
52903
53067
  }
52904
- function requireOptionalString(record2, field) {
52905
- if (record2[field] === undefined)
53068
+ function requireOptionalString(record3, field) {
53069
+ if (record3[field] === undefined)
52906
53070
  return;
52907
- if (typeof record2[field] !== "string") {
53071
+ if (typeof record3[field] !== "string") {
52908
53072
  throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
52909
53073
  }
52910
- return record2[field];
53074
+ return record3[field];
52911
53075
  }
52912
53076
  var INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
52913
53077
  function isVersionRecord(value) {
@@ -52930,19 +53094,19 @@ function normalizePin(entry) {
52930
53094
  if (!entry || typeof entry !== "object") {
52931
53095
  throw new Error("Remote pin payload did not match the expected contract (expected an object)");
52932
53096
  }
52933
- const record2 = entry;
52934
- const slug = typeof record2.slug === "string" && record2.slug.trim() ? record2.slug.trim() : undefined;
53097
+ const record3 = entry;
53098
+ const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
52935
53099
  if (!slug) {
52936
53100
  throw new Error("Remote pin payload did not match the expected contract (missing slug)");
52937
53101
  }
52938
53102
  let metadata;
52939
- if (record2.metadata !== undefined) {
52940
- if (!record2.metadata || typeof record2.metadata !== "object" || Array.isArray(record2.metadata)) {
53103
+ if (record3.metadata !== undefined) {
53104
+ if (!record3.metadata || typeof record3.metadata !== "object" || Array.isArray(record3.metadata)) {
52941
53105
  throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
52942
53106
  }
52943
- metadata = record2.metadata;
53107
+ metadata = record3.metadata;
52944
53108
  }
52945
- const pinnedAt = requireOptionalString(record2, "pinnedAt");
53109
+ const pinnedAt = requireOptionalString(record3, "pinnedAt");
52946
53110
  return {
52947
53111
  slug,
52948
53112
  ...pinnedAt !== undefined ? { pinnedAt } : {},
@@ -52959,16 +53123,16 @@ function normalizeSkillSummary(entry) {
52959
53123
  if (!entry || typeof entry !== "object") {
52960
53124
  throw new Error("Remote skill payload did not match the expected contract (expected an object)");
52961
53125
  }
52962
- const record2 = entry;
52963
- const slug = typeof record2.slug === "string" && record2.slug.trim() ? record2.slug.trim() : undefined;
53126
+ const record3 = entry;
53127
+ const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
52964
53128
  if (!slug) {
52965
53129
  throw new Error("Remote skill payload did not match the expected contract (missing slug)");
52966
53130
  }
52967
53131
  return {
52968
53132
  slug,
52969
- ...requireOptionalString(record2, "name") !== undefined ? { name: requireOptionalString(record2, "name") } : {},
52970
- ...requireOptionalString(record2, "version") !== undefined ? { version: requireOptionalString(record2, "version") } : {},
52971
- ...requireOptionalString(record2, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record2, "updatedAt") } : {}
53133
+ ...requireOptionalString(record3, "name") !== undefined ? { name: requireOptionalString(record3, "name") } : {},
53134
+ ...requireOptionalString(record3, "version") !== undefined ? { version: requireOptionalString(record3, "version") } : {},
53135
+ ...requireOptionalString(record3, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record3, "updatedAt") } : {}
52972
53136
  };
52973
53137
  }
52974
53138
  function normalizeSkillSummaryList(payload) {
@@ -53021,12 +53185,12 @@ function normalizeUpdatedSincePage(payload) {
53021
53185
  if (!payload || typeof payload !== "object") {
53022
53186
  throw new Error("Updated-since payload did not match the expected contract (expected an object)");
53023
53187
  }
53024
- const record2 = payload;
53025
- if (!Array.isArray(record2.skills)) {
53188
+ const record3 = payload;
53189
+ if (!Array.isArray(record3.skills)) {
53026
53190
  throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
53027
53191
  }
53028
- const skills = record2.skills.map(normalizeSkillSummary);
53029
- const nextCursor = record2.nextCursor === undefined || record2.nextCursor === null ? null : record2.nextCursor;
53192
+ const skills = record3.skills.map(normalizeSkillSummary);
53193
+ const nextCursor = record3.nextCursor === undefined || record3.nextCursor === null ? null : record3.nextCursor;
53030
53194
  if (nextCursor !== null && typeof nextCursor !== "string") {
53031
53195
  throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
53032
53196
  }
@@ -53073,13 +53237,13 @@ async function requestAuthApi(instance, path, options) {
53073
53237
  apiUrl: safeUrl
53074
53238
  });
53075
53239
  }
53076
- const text = await res.text();
53077
- const body = text ? parseJsonBody(text) : {};
53240
+ const text2 = await res.text();
53241
+ const body = text2 ? parseJsonBody(text2) : {};
53078
53242
  if (!res.ok) {
53079
- const record2 = isRecord6(body) ? body : {};
53080
- const detail = typeof record2.detail === "string" ? record2.detail : undefined;
53081
- const error = typeof record2.error === "string" ? record2.error : undefined;
53082
- const code = typeof record2.code === "string" ? record2.code : undefined;
53243
+ const record3 = isRecord6(body) ? body : {};
53244
+ const detail = typeof record3.detail === "string" ? record3.detail : undefined;
53245
+ const error = typeof record3.error === "string" ? record3.error : undefined;
53246
+ const code = typeof record3.code === "string" ? record3.code : undefined;
53083
53247
  throw new HostedApiError(detail || error || `${res.status} ${res.statusText}`, {
53084
53248
  status: res.status,
53085
53249
  code,
@@ -53090,15 +53254,15 @@ async function requestAuthApi(instance, path, options) {
53090
53254
  }
53091
53255
  return body;
53092
53256
  }
53093
- function parseJsonBody(text) {
53257
+ function parseJsonBody(text2) {
53094
53258
  try {
53095
- return JSON.parse(text);
53259
+ return JSON.parse(text2);
53096
53260
  } catch {
53097
- return { detail: condenseErrorBody(text) };
53261
+ return { detail: condenseErrorBody(text2) };
53098
53262
  }
53099
53263
  }
53100
- function condenseErrorBody(text) {
53101
- const stripped = /<[a-z!/]/i.test(text) ? text.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text;
53264
+ function condenseErrorBody(text2) {
53265
+ const stripped = /<[a-z!/]/i.test(text2) ? text2.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text2;
53102
53266
  const collapsed = stripped.replace(/\s+/g, " ").trim();
53103
53267
  if (collapsed.length <= MAX_ERROR_DETAIL_LENGTH)
53104
53268
  return collapsed;
@@ -53125,7 +53289,12 @@ class RemoteSkillsAuthClient {
53125
53289
  pollDevice(deviceCode) {
53126
53290
  return this.request("/api/auth/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) });
53127
53291
  }
53128
- async sessionClient(email, code) {
53292
+ async sessionClient(email, code, context) {
53293
+ if (context !== undefined) {
53294
+ const target = workspaceContext(context), apiOrigin2 = this.apiOrigin;
53295
+ const session = await this.switchWorkspace(email, code, target);
53296
+ return new RemoteSkillsClient(session.token, apiOrigin2);
53297
+ }
53129
53298
  const apiOrigin = this.apiOrigin;
53130
53299
  if (!email.includes("@") || !/^\d{6}$/.test(code))
53131
53300
  throw new Error("Fresh email and six-digit verification code are required to manage this account");
@@ -53134,34 +53303,76 @@ class RemoteSkillsAuthClient {
53134
53303
  throw new Error("The server did not return an authorized account session");
53135
53304
  return new RemoteSkillsClient(login.token, apiOrigin);
53136
53305
  }
53137
- async createApiKey(email, code, name, scopes) {
53138
- return (await this.sessionClient(email, code)).createApiKey(name, scopes);
53306
+ async listAccountWorkspaces(email, code, expectedUserId) {
53307
+ const login = await this.workspaceLogin(email, code, expectedUserId);
53308
+ const result = await new RemoteSkillsClient(login.token, login.apiOrigin).listAccountWorkspaces(login.userId);
53309
+ return { userId: login.userId, ...result };
53139
53310
  }
53140
- async listApiKeys(email, code) {
53141
- return (await this.sessionClient(email, code)).listApiKeys();
53311
+ async switchWorkspace(email, code, context) {
53312
+ const target = workspaceContext(context);
53313
+ const login = await this.workspaceLogin(email, code, target.userId);
53314
+ return new RemoteSkillsClient(login.token, login.apiOrigin).switchWorkspace(target);
53142
53315
  }
53143
- async revokeApiKey(email, code, keyId) {
53144
- return (await this.sessionClient(email, code)).revokeApiKey(keyId);
53316
+ async workspaceLogin(email, code, expectedUserId) {
53317
+ const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
53318
+ const apiOrigin = this.apiOrigin;
53319
+ if (typeof email !== "string" || !email.includes("@") || typeof code !== "string" || !/^\d{6}$/.test(code))
53320
+ throw new Error("Fresh email and six-digit verification code are required to manage this account");
53321
+ let response;
53322
+ try {
53323
+ response = await fetch(`${apiOrigin}/api/auth/verify`, {
53324
+ method: "POST",
53325
+ redirect: "error",
53326
+ credentials: "omit",
53327
+ signal: AbortSignal.timeout(15000),
53328
+ headers: { "Content-Type": "application/json" },
53329
+ body: JSON.stringify({ email, code })
53330
+ });
53331
+ } catch {
53332
+ throw new HostedApiError("Unable to verify the Skills account.");
53333
+ }
53334
+ if (!response.ok) {
53335
+ response.body?.cancel().catch(() => {});
53336
+ throw new HostedApiError("Unable to verify the Skills account.", { status: response.status });
53337
+ }
53338
+ let value;
53339
+ try {
53340
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 64 * 1024)));
53341
+ } catch {
53342
+ throw new HostedApiError("The server returned an invalid account verification result.");
53343
+ }
53344
+ return { ...parseWorkspaceLogin(value, expected), apiOrigin };
53345
+ }
53346
+ async createApiKey(email, code, name, scopes, context) {
53347
+ const capturedScopes = scopes === undefined ? undefined : [...scopes];
53348
+ return (await this.sessionClient(email, code, context)).createApiKey(name, capturedScopes);
53349
+ }
53350
+ async listApiKeys(email, code, context) {
53351
+ return (await this.sessionClient(email, code, context)).listApiKeys();
53145
53352
  }
53146
- async updateProfile(email, code, input) {
53147
- customerNamePatch(input, "displayName");
53148
- return (await this.sessionClient(email, code)).updateProfile(input);
53353
+ async revokeApiKey(email, code, keyId, context) {
53354
+ return (await this.sessionClient(email, code, context)).revokeApiKey(keyId);
53149
53355
  }
53150
- async updateCurrentWorkspace(email, code, input) {
53151
- customerNamePatch(input, "name");
53152
- return (await this.sessionClient(email, code)).updateCurrentWorkspace(input);
53356
+ async updateProfile(email, code, input, context) {
53357
+ const body = customerNamePatch(input, "displayName");
53358
+ return (await this.sessionClient(email, code, context)).updateProfile({ displayName: body.displayName });
53359
+ }
53360
+ async updateCurrentWorkspace(email, code, input, context) {
53361
+ const body = customerNamePatch(input, "name");
53362
+ return (await this.sessionClient(email, code, context)).updateCurrentWorkspace({ name: body.name });
53153
53363
  }
53154
- async listWorkspaceMembers(email, code, options = {}) {
53364
+ async listWorkspaceMembers(email, code, options = {}, context) {
53155
53365
  workspaceMembersQuery(options);
53156
- return (await this.sessionClient(email, code)).listWorkspaceMembers(options);
53366
+ const captured = { ...options };
53367
+ return (await this.sessionClient(email, code, context)).listWorkspaceMembers(captured);
53157
53368
  }
53158
- async setWorkspaceMemberRole(email, code, membershipId, input) {
53369
+ async setWorkspaceMemberRole(email, code, membershipId, input, context) {
53159
53370
  const captured = workspaceMemberRoleInput(membershipId, input);
53160
- return (await this.sessionClient(email, code)).setWorkspaceMemberRole(captured.membershipId, captured.body);
53371
+ return (await this.sessionClient(email, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
53161
53372
  }
53162
- async removeWorkspaceMember(email, code, membershipId, input) {
53373
+ async removeWorkspaceMember(email, code, membershipId, input, context) {
53163
53374
  const captured = workspaceMemberRemovalInput(membershipId, input);
53164
- return (await this.sessionClient(email, code)).removeWorkspaceMember(captured.membershipId, captured.body);
53375
+ return (await this.sessionClient(email, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
53165
53376
  }
53166
53377
  request(path, options) {
53167
53378
  if (!["/api/auth/login", "/api/auth/verify", "/api/auth/device/start", "/api/auth/device/token", "/api/auth/keys", "/api/auth/whoami"].includes(path))
@@ -53242,6 +53453,8 @@ export {
53242
53453
  assertDurableTarget,
53243
53454
  assertDurableStore,
53244
53455
  artifactStorageSeam,
53456
+ WorkspaceIdentityMismatchError,
53457
+ WorkspaceContextInputError,
53245
53458
  SqliteSkillsStore,
53246
53459
  SqliteRunExecutionStore,
53247
53460
  SqliteGovernanceStore,
@@ -53250,6 +53463,7 @@ export {
53250
53463
  SKILLS_APP,
53251
53464
  SKILLS_API_URL_ENV,
53252
53465
  SKILLS_API_KEY_ENV,
53466
+ RemoteWorkspaceSelectionError,
53253
53467
  RemoteWorkspaceMemberError,
53254
53468
  RemoteSkillsClient,
53255
53469
  RemoteSkillsAuthClient,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {