@hasna/skills 0.5.0 → 0.5.2

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.2",
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,13 +52353,72 @@ 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)
52266
52360
  return fail();
52267
52361
  return { organizationId: value.organizationId, members, nextCursor: value.nextCursor };
52268
52362
  }
52363
+
52364
+ // src/lib/remote-workspace-leave.ts
52365
+ class WorkspaceLeaveInputError extends Error {
52366
+ constructor() {
52367
+ super("Confirm leaving the exact observed user and membership with its expected role.");
52368
+ this.name = "WorkspaceLeaveInputError";
52369
+ }
52370
+ }
52371
+ function workspaceLeaveInput(context, input) {
52372
+ const target = workspaceContext(context);
52373
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).sort().join(",") !== "confirm,expectedRole" || input.confirm !== true)
52374
+ throw new WorkspaceLeaveInputError;
52375
+ const captured = workspaceMemberRemovalInput(target.membershipId, { expectedRole: input.expectedRole });
52376
+ return {
52377
+ context: target,
52378
+ input: { expectedRole: captured.body.expectedRole, confirm: true },
52379
+ body: { membershipId: target.membershipId, expectedRole: captured.body.expectedRole }
52380
+ };
52381
+ }
52382
+ var workspaceLeaveFailures = {
52383
+ INVALID_REQUEST: [400, "Provide the exact current membership and expected role."],
52384
+ ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
52385
+ INTERACTIVE_SESSION_REQUIRED: [403, "Fresh interactive sign-in is required to leave a workspace."],
52386
+ MEMBERSHIP_ROLE_CHANGED: [409, "Your role changed. Sign in and inspect the workspace before leaving."],
52387
+ LAST_OWNER_REQUIRED: [409, "The workspace must retain another active owner."],
52388
+ LAST_WORKSPACE_REQUIRED: [409, "Another available workspace is required before leaving."],
52389
+ MEMBERSHIP_BUSY: [503, "Membership is busy. Inspect the workspace before another leave action."]
52390
+ };
52391
+
52392
+ class RemoteWorkspaceLeaveError extends Error {
52393
+ code;
52394
+ status;
52395
+ constructor(code) {
52396
+ super(workspaceLeaveFailures[code][1]);
52397
+ this.code = code;
52398
+ this.name = "RemoteWorkspaceLeaveError";
52399
+ this.status = workspaceLeaveFailures[code][0];
52400
+ }
52401
+ }
52402
+
52403
+ class RemoteWorkspaceLeaveUnconfirmedError extends Error {
52404
+ code = "WORKSPACE_LEAVE_UNCONFIRMED";
52405
+ constructor() {
52406
+ super("The leave outcome is unconfirmed. Sign in again and inspect available memberships before another action. Do not retry automatically; saved credentials are unchanged.");
52407
+ this.name = "RemoteWorkspaceLeaveUnconfirmedError";
52408
+ }
52409
+ }
52410
+ function workspaceLeaveFailure(value, status) {
52411
+ if (!value || typeof value !== "object" || Array.isArray(value))
52412
+ return null;
52413
+ const code = value.code;
52414
+ return typeof code === "string" && Object.hasOwn(workspaceLeaveFailures, code) && workspaceLeaveFailures[code][0] === status ? code : null;
52415
+ }
52416
+ function parseWorkspaceLeaveResult(value, membershipId, organizationId) {
52417
+ const row = value;
52418
+ if (!row || typeof row !== "object" || Array.isArray(row) || row.membershipId !== membershipId || row.organizationId !== organizationId || row.removed !== true || row.signInRequired !== true)
52419
+ throw new RemoteWorkspaceLeaveUnconfirmedError;
52420
+ return { membershipId, organizationId, removed: true, signInRequired: true };
52421
+ }
52269
52422
  // src/lib/auth-store.ts
52270
52423
  function getApiUrl(action, env = process.env, options = {}) {
52271
52424
  return requireSkillsApiOrigin(action, env, options);
@@ -52430,11 +52583,11 @@ function parseUpdatedProfile(value) {
52430
52583
  return { user: { id: user.id, email: user.email, displayName: user.displayName, role: user.role } };
52431
52584
  }
52432
52585
  function parseUpdatedWorkspace(value) {
52433
- const organization = isRecord5(value) && value.organization;
52434
- if (!isRecord5(organization) || !string(organization.id) || !string(organization.slug) || !string(organization.name)) {
52586
+ const organization2 = isRecord5(value) && value.organization;
52587
+ if (!isRecord5(organization2) || !string(organization2.id) || !string(organization2.slug) || !string(organization2.name)) {
52435
52588
  throw new Error("The server returned an invalid workspace.");
52436
52589
  }
52437
- return { organization: { id: organization.id, slug: organization.slug, name: organization.name } };
52590
+ return { organization: { id: organization2.id, slug: organization2.slug, name: organization2.name } };
52438
52591
  }
52439
52592
 
52440
52593
  // src/lib/remote-client.ts
@@ -52472,6 +52625,16 @@ class RemoteWorkspaceMemberError extends RemoteRequestError {
52472
52625
  }
52473
52626
  }
52474
52627
 
52628
+ class RemoteWorkspaceSelectionError extends RemoteRequestError {
52629
+ code;
52630
+ constructor(path, code) {
52631
+ super(path, workspaceSelectionFailures[code][0]);
52632
+ this.code = code;
52633
+ this.name = "RemoteWorkspaceSelectionError";
52634
+ this.message = workspaceSelectionFailures[code][1];
52635
+ }
52636
+ }
52637
+
52475
52638
  class RemoteCapabilityUnavailableError extends RemoteRequestError {
52476
52639
  code = "SUBSCRIPTION_CHECKOUT_UNAVAILABLE";
52477
52640
  constructor() {
@@ -52493,6 +52656,7 @@ class RemoteSkillsClient {
52493
52656
  return fetch(`${this.apiUrl}${path}`, {
52494
52657
  ...options,
52495
52658
  redirect: "error",
52659
+ credentials: "omit",
52496
52660
  signal: options?.signal ?? AbortSignal.timeout(15000),
52497
52661
  headers: {
52498
52662
  Authorization: `Bearer ${this.apiKey}`,
@@ -52599,6 +52763,65 @@ class RemoteSkillsClient {
52599
52763
  async getIdentity() {
52600
52764
  return (await this.requestNewRoute("/api/auth/whoami")).json();
52601
52765
  }
52766
+ async listAccountWorkspaces(expectedUserId) {
52767
+ const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
52768
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
52769
+ let identity;
52770
+ if (expected !== undefined) {
52771
+ const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
52772
+ if (!value || typeof value !== "object" || value.authMethod !== "jwt")
52773
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces", "INTERACTIVE_SESSION_REQUIRED");
52774
+ identity = parseWorkspaceIdentity(value, expected);
52775
+ }
52776
+ const result = parseAccountWorkspaces(await connection.requestWorkspaceSelection("/api/v1/account/workspaces"));
52777
+ const current = result.workspaces.find((workspace) => workspace.current);
52778
+ if (identity && (current.membershipId !== identity.user.membershipId || current.organization.id !== identity.organization.id))
52779
+ throw new WorkspaceIdentityMismatchError;
52780
+ return result;
52781
+ }
52782
+ async switchWorkspace(context) {
52783
+ const target = workspaceContext(context);
52784
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
52785
+ const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
52786
+ if (!value || typeof value !== "object" || value.authMethod !== "jwt")
52787
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
52788
+ parseWorkspaceIdentity(value, target.userId);
52789
+ const selected = parseWorkspaceSession(await connection.requestWorkspaceSelection("/api/v1/account/workspaces/switch", {
52790
+ method: "POST",
52791
+ body: JSON.stringify({ membershipId: target.membershipId })
52792
+ }), target);
52793
+ const verified = await new RemoteSkillsClient(selected.token, connection.apiUrl).requestWorkspaceSelection("/api/auth/whoami");
52794
+ if (!verified || typeof verified !== "object" || verified.authMethod !== "jwt")
52795
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
52796
+ const identity = parseWorkspaceIdentity(verified, target.userId);
52797
+ if (identity.user.membershipId !== target.membershipId || identity.organization.id !== selected.organization.id)
52798
+ throw new WorkspaceIdentityMismatchError;
52799
+ return { token: selected.token, ...identity };
52800
+ }
52801
+ async requestWorkspaceSelection(path, options) {
52802
+ let response;
52803
+ try {
52804
+ response = await this.request(path, { ...options, credentials: "omit" });
52805
+ } catch {
52806
+ throw new Error("Unable to reach the Skills workspace API.");
52807
+ }
52808
+ let value;
52809
+ try {
52810
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 1024 * 1024 : 4096)));
52811
+ } catch {
52812
+ if (response.ok)
52813
+ throw new Error(invalidWorkspaceResult);
52814
+ }
52815
+ if (!response.ok) {
52816
+ const code = workspaceSelectionFailure(value, response.status);
52817
+ if (code)
52818
+ throw new RemoteWorkspaceSelectionError(path, code);
52819
+ if (response.status === 404 || response.status === 405)
52820
+ throw new RemoteRouteUnsupportedError(path, response.status, this.apiUrl);
52821
+ throw new RemoteRequestError(path, response.status);
52822
+ }
52823
+ return value;
52824
+ }
52602
52825
  async updateProfile(input) {
52603
52826
  const body = customerNamePatch(input, "displayName");
52604
52827
  return parseUpdatedProfile(await (await this.requestNewRoute("/api/v1/account/profile", { method: "PATCH", body: JSON.stringify(body) })).json());
@@ -52651,6 +52874,30 @@ class RemoteSkillsClient {
52651
52874
  }
52652
52875
  return value;
52653
52876
  }
52877
+ async leaveWorkspace(context, input) {
52878
+ const captured = workspaceLeaveInput(context, input);
52879
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
52880
+ const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
52881
+ if (!value || typeof value !== "object" || value.authMethod !== "jwt")
52882
+ throw new RemoteWorkspaceLeaveError("INTERACTIVE_SESSION_REQUIRED");
52883
+ const identity = parseWorkspaceIdentity(value, captured.context.userId);
52884
+ if (identity.user.membershipId !== captured.context.membershipId)
52885
+ throw new WorkspaceIdentityMismatchError;
52886
+ let response, body;
52887
+ try {
52888
+ response = await connection.request("/api/v1/account/workspaces/leave", { method: "POST", body: JSON.stringify(captured.body) });
52889
+ body = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 4096)));
52890
+ } catch {
52891
+ throw new RemoteWorkspaceLeaveUnconfirmedError;
52892
+ }
52893
+ if (!response.ok) {
52894
+ const code = workspaceLeaveFailure(body, response.status);
52895
+ if (code)
52896
+ throw new RemoteWorkspaceLeaveError(code);
52897
+ throw new RemoteWorkspaceLeaveUnconfirmedError;
52898
+ }
52899
+ return parseWorkspaceLeaveResult(body, captured.context.membershipId, identity.organization.id);
52900
+ }
52654
52901
  async listApiKeys() {
52655
52902
  return this.arrayResponse("/api/auth/keys");
52656
52903
  }
@@ -52901,13 +53148,13 @@ class RemoteSkillsClient {
52901
53148
  return normalizeUpdatedSincePage(await response.json());
52902
53149
  }
52903
53150
  }
52904
- function requireOptionalString(record2, field) {
52905
- if (record2[field] === undefined)
53151
+ function requireOptionalString(record3, field) {
53152
+ if (record3[field] === undefined)
52906
53153
  return;
52907
- if (typeof record2[field] !== "string") {
53154
+ if (typeof record3[field] !== "string") {
52908
53155
  throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
52909
53156
  }
52910
- return record2[field];
53157
+ return record3[field];
52911
53158
  }
52912
53159
  var INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
52913
53160
  function isVersionRecord(value) {
@@ -52930,19 +53177,19 @@ function normalizePin(entry) {
52930
53177
  if (!entry || typeof entry !== "object") {
52931
53178
  throw new Error("Remote pin payload did not match the expected contract (expected an object)");
52932
53179
  }
52933
- const record2 = entry;
52934
- const slug = typeof record2.slug === "string" && record2.slug.trim() ? record2.slug.trim() : undefined;
53180
+ const record3 = entry;
53181
+ const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
52935
53182
  if (!slug) {
52936
53183
  throw new Error("Remote pin payload did not match the expected contract (missing slug)");
52937
53184
  }
52938
53185
  let metadata;
52939
- if (record2.metadata !== undefined) {
52940
- if (!record2.metadata || typeof record2.metadata !== "object" || Array.isArray(record2.metadata)) {
53186
+ if (record3.metadata !== undefined) {
53187
+ if (!record3.metadata || typeof record3.metadata !== "object" || Array.isArray(record3.metadata)) {
52941
53188
  throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
52942
53189
  }
52943
- metadata = record2.metadata;
53190
+ metadata = record3.metadata;
52944
53191
  }
52945
- const pinnedAt = requireOptionalString(record2, "pinnedAt");
53192
+ const pinnedAt = requireOptionalString(record3, "pinnedAt");
52946
53193
  return {
52947
53194
  slug,
52948
53195
  ...pinnedAt !== undefined ? { pinnedAt } : {},
@@ -52959,16 +53206,16 @@ function normalizeSkillSummary(entry) {
52959
53206
  if (!entry || typeof entry !== "object") {
52960
53207
  throw new Error("Remote skill payload did not match the expected contract (expected an object)");
52961
53208
  }
52962
- const record2 = entry;
52963
- const slug = typeof record2.slug === "string" && record2.slug.trim() ? record2.slug.trim() : undefined;
53209
+ const record3 = entry;
53210
+ const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
52964
53211
  if (!slug) {
52965
53212
  throw new Error("Remote skill payload did not match the expected contract (missing slug)");
52966
53213
  }
52967
53214
  return {
52968
53215
  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") } : {}
53216
+ ...requireOptionalString(record3, "name") !== undefined ? { name: requireOptionalString(record3, "name") } : {},
53217
+ ...requireOptionalString(record3, "version") !== undefined ? { version: requireOptionalString(record3, "version") } : {},
53218
+ ...requireOptionalString(record3, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record3, "updatedAt") } : {}
52972
53219
  };
52973
53220
  }
52974
53221
  function normalizeSkillSummaryList(payload) {
@@ -53021,12 +53268,12 @@ function normalizeUpdatedSincePage(payload) {
53021
53268
  if (!payload || typeof payload !== "object") {
53022
53269
  throw new Error("Updated-since payload did not match the expected contract (expected an object)");
53023
53270
  }
53024
- const record2 = payload;
53025
- if (!Array.isArray(record2.skills)) {
53271
+ const record3 = payload;
53272
+ if (!Array.isArray(record3.skills)) {
53026
53273
  throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
53027
53274
  }
53028
- const skills = record2.skills.map(normalizeSkillSummary);
53029
- const nextCursor = record2.nextCursor === undefined || record2.nextCursor === null ? null : record2.nextCursor;
53275
+ const skills = record3.skills.map(normalizeSkillSummary);
53276
+ const nextCursor = record3.nextCursor === undefined || record3.nextCursor === null ? null : record3.nextCursor;
53030
53277
  if (nextCursor !== null && typeof nextCursor !== "string") {
53031
53278
  throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
53032
53279
  }
@@ -53073,13 +53320,13 @@ async function requestAuthApi(instance, path, options) {
53073
53320
  apiUrl: safeUrl
53074
53321
  });
53075
53322
  }
53076
- const text = await res.text();
53077
- const body = text ? parseJsonBody(text) : {};
53323
+ const text2 = await res.text();
53324
+ const body = text2 ? parseJsonBody(text2) : {};
53078
53325
  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;
53326
+ const record3 = isRecord6(body) ? body : {};
53327
+ const detail = typeof record3.detail === "string" ? record3.detail : undefined;
53328
+ const error = typeof record3.error === "string" ? record3.error : undefined;
53329
+ const code = typeof record3.code === "string" ? record3.code : undefined;
53083
53330
  throw new HostedApiError(detail || error || `${res.status} ${res.statusText}`, {
53084
53331
  status: res.status,
53085
53332
  code,
@@ -53090,15 +53337,15 @@ async function requestAuthApi(instance, path, options) {
53090
53337
  }
53091
53338
  return body;
53092
53339
  }
53093
- function parseJsonBody(text) {
53340
+ function parseJsonBody(text2) {
53094
53341
  try {
53095
- return JSON.parse(text);
53342
+ return JSON.parse(text2);
53096
53343
  } catch {
53097
- return { detail: condenseErrorBody(text) };
53344
+ return { detail: condenseErrorBody(text2) };
53098
53345
  }
53099
53346
  }
53100
- function condenseErrorBody(text) {
53101
- const stripped = /<[a-z!/]/i.test(text) ? text.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text;
53347
+ function condenseErrorBody(text2) {
53348
+ const stripped = /<[a-z!/]/i.test(text2) ? text2.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text2;
53102
53349
  const collapsed = stripped.replace(/\s+/g, " ").trim();
53103
53350
  if (collapsed.length <= MAX_ERROR_DETAIL_LENGTH)
53104
53351
  return collapsed;
@@ -53125,7 +53372,12 @@ class RemoteSkillsAuthClient {
53125
53372
  pollDevice(deviceCode) {
53126
53373
  return this.request("/api/auth/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) });
53127
53374
  }
53128
- async sessionClient(email, code) {
53375
+ async sessionClient(email, code, context) {
53376
+ if (context !== undefined) {
53377
+ const target = workspaceContext(context), apiOrigin2 = this.apiOrigin;
53378
+ const session = await this.switchWorkspace(email, code, target);
53379
+ return new RemoteSkillsClient(session.token, apiOrigin2);
53380
+ }
53129
53381
  const apiOrigin = this.apiOrigin;
53130
53382
  if (!email.includes("@") || !/^\d{6}$/.test(code))
53131
53383
  throw new Error("Fresh email and six-digit verification code are required to manage this account");
@@ -53134,34 +53386,80 @@ class RemoteSkillsAuthClient {
53134
53386
  throw new Error("The server did not return an authorized account session");
53135
53387
  return new RemoteSkillsClient(login.token, apiOrigin);
53136
53388
  }
53137
- async createApiKey(email, code, name, scopes) {
53138
- return (await this.sessionClient(email, code)).createApiKey(name, scopes);
53389
+ async listAccountWorkspaces(email, code, expectedUserId) {
53390
+ const login = await this.workspaceLogin(email, code, expectedUserId);
53391
+ const result = await new RemoteSkillsClient(login.token, login.apiOrigin).listAccountWorkspaces(login.userId);
53392
+ return { userId: login.userId, ...result };
53139
53393
  }
53140
- async listApiKeys(email, code) {
53141
- return (await this.sessionClient(email, code)).listApiKeys();
53394
+ async switchWorkspace(email, code, context) {
53395
+ const target = workspaceContext(context);
53396
+ const login = await this.workspaceLogin(email, code, target.userId);
53397
+ return new RemoteSkillsClient(login.token, login.apiOrigin).switchWorkspace(target);
53142
53398
  }
53143
- async revokeApiKey(email, code, keyId) {
53144
- return (await this.sessionClient(email, code)).revokeApiKey(keyId);
53399
+ async workspaceLogin(email, code, expectedUserId) {
53400
+ const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
53401
+ const apiOrigin = this.apiOrigin;
53402
+ if (typeof email !== "string" || !email.includes("@") || typeof code !== "string" || !/^\d{6}$/.test(code))
53403
+ throw new Error("Fresh email and six-digit verification code are required to manage this account");
53404
+ let response;
53405
+ try {
53406
+ response = await fetch(`${apiOrigin}/api/auth/verify`, {
53407
+ method: "POST",
53408
+ redirect: "error",
53409
+ credentials: "omit",
53410
+ signal: AbortSignal.timeout(15000),
53411
+ headers: { "Content-Type": "application/json" },
53412
+ body: JSON.stringify({ email, code })
53413
+ });
53414
+ } catch {
53415
+ throw new HostedApiError("Unable to verify the Skills account.");
53416
+ }
53417
+ if (!response.ok) {
53418
+ response.body?.cancel().catch(() => {});
53419
+ throw new HostedApiError("Unable to verify the Skills account.", { status: response.status });
53420
+ }
53421
+ let value;
53422
+ try {
53423
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 64 * 1024)));
53424
+ } catch {
53425
+ throw new HostedApiError("The server returned an invalid account verification result.");
53426
+ }
53427
+ return { ...parseWorkspaceLogin(value, expected), apiOrigin };
53428
+ }
53429
+ async createApiKey(email, code, name, scopes, context) {
53430
+ const capturedScopes = scopes === undefined ? undefined : [...scopes];
53431
+ return (await this.sessionClient(email, code, context)).createApiKey(name, capturedScopes);
53145
53432
  }
53146
- async updateProfile(email, code, input) {
53147
- customerNamePatch(input, "displayName");
53148
- return (await this.sessionClient(email, code)).updateProfile(input);
53433
+ async listApiKeys(email, code, context) {
53434
+ return (await this.sessionClient(email, code, context)).listApiKeys();
53149
53435
  }
53150
- async updateCurrentWorkspace(email, code, input) {
53151
- customerNamePatch(input, "name");
53152
- return (await this.sessionClient(email, code)).updateCurrentWorkspace(input);
53436
+ async revokeApiKey(email, code, keyId, context) {
53437
+ return (await this.sessionClient(email, code, context)).revokeApiKey(keyId);
53153
53438
  }
53154
- async listWorkspaceMembers(email, code, options = {}) {
53439
+ async updateProfile(email, code, input, context) {
53440
+ const body = customerNamePatch(input, "displayName");
53441
+ return (await this.sessionClient(email, code, context)).updateProfile({ displayName: body.displayName });
53442
+ }
53443
+ async updateCurrentWorkspace(email, code, input, context) {
53444
+ const body = customerNamePatch(input, "name");
53445
+ return (await this.sessionClient(email, code, context)).updateCurrentWorkspace({ name: body.name });
53446
+ }
53447
+ async listWorkspaceMembers(email, code, options = {}, context) {
53155
53448
  workspaceMembersQuery(options);
53156
- return (await this.sessionClient(email, code)).listWorkspaceMembers(options);
53449
+ const captured = { ...options };
53450
+ return (await this.sessionClient(email, code, context)).listWorkspaceMembers(captured);
53157
53451
  }
53158
- async setWorkspaceMemberRole(email, code, membershipId, input) {
53452
+ async setWorkspaceMemberRole(email, code, membershipId, input, context) {
53159
53453
  const captured = workspaceMemberRoleInput(membershipId, input);
53160
- return (await this.sessionClient(email, code)).setWorkspaceMemberRole(captured.membershipId, captured.body);
53454
+ return (await this.sessionClient(email, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
53455
+ }
53456
+ async leaveWorkspace(email, code, context, input) {
53457
+ const captured = workspaceLeaveInput(context, input);
53458
+ return (await this.sessionClient(email, code, captured.context)).leaveWorkspace(captured.context, captured.input);
53161
53459
  }
53162
- async removeWorkspaceMember(email, code, membershipId, input) {
53460
+ async removeWorkspaceMember(email, code, membershipId, input, context) {
53163
53461
  const captured = workspaceMemberRemovalInput(membershipId, input);
53164
- return (await this.sessionClient(email, code)).removeWorkspaceMember(captured.membershipId, captured.body);
53462
+ return (await this.sessionClient(email, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
53165
53463
  }
53166
53464
  request(path, options) {
53167
53465
  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 +53540,9 @@ export {
53242
53540
  assertDurableTarget,
53243
53541
  assertDurableStore,
53244
53542
  artifactStorageSeam,
53543
+ WorkspaceLeaveInputError,
53544
+ WorkspaceIdentityMismatchError,
53545
+ WorkspaceContextInputError,
53245
53546
  SqliteSkillsStore,
53246
53547
  SqliteRunExecutionStore,
53247
53548
  SqliteGovernanceStore,
@@ -53250,7 +53551,10 @@ export {
53250
53551
  SKILLS_APP,
53251
53552
  SKILLS_API_URL_ENV,
53252
53553
  SKILLS_API_KEY_ENV,
53554
+ RemoteWorkspaceSelectionError,
53253
53555
  RemoteWorkspaceMemberError,
53556
+ RemoteWorkspaceLeaveUnconfirmedError,
53557
+ RemoteWorkspaceLeaveError,
53254
53558
  RemoteSkillsClient,
53255
53559
  RemoteSkillsAuthClient,
53256
53560
  RemoteRouteUnsupportedError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {