@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/index.js CHANGED
@@ -10254,12 +10254,106 @@ function primitiveHaystack(primitive) {
10254
10254
  function clone(value) {
10255
10255
  return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
10256
10256
  }
10257
+ // src/lib/remote-workspace-selection.ts
10258
+ var record = (v) => !!v && typeof v === "object" && !Array.isArray(v);
10259
+ var uuid = (v) => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v);
10260
+ var text = (v, max = 1024) => typeof v === "string" && !!v.trim() && v.length <= max && !/[\p{Cc}\p{Cs}\u2028\u2029]/u.test(v);
10261
+ var role = (v) => typeof v === "string" && ["owner", "admin", "member", "viewer"].includes(v);
10262
+ var invalidWorkspaceResult = "The server returned an invalid workspace selection result.";
10263
+
10264
+ class WorkspaceContextInputError extends Error {
10265
+ constructor() {
10266
+ super("Provide the observed user ID and exact lowercase membership ID.");
10267
+ this.name = "WorkspaceContextInputError";
10268
+ }
10269
+ }
10270
+
10271
+ class WorkspaceIdentityMismatchError extends Error {
10272
+ constructor() {
10273
+ super("The verified account does not match the requested workspace context.");
10274
+ this.name = "WorkspaceIdentityMismatchError";
10275
+ }
10276
+ }
10277
+ function workspaceExpectedUserId(value) {
10278
+ if (!uuid(value))
10279
+ throw new WorkspaceContextInputError;
10280
+ return value;
10281
+ }
10282
+ function workspaceContext(value) {
10283
+ if (!record(value) || Object.keys(value).sort().join(",") !== "membershipId,userId" || !uuid(value.userId) || !uuid(value.membershipId))
10284
+ throw new WorkspaceContextInputError;
10285
+ return { userId: value.userId, membershipId: value.membershipId };
10286
+ }
10287
+ function invalid() {
10288
+ throw new Error(invalidWorkspaceResult);
10289
+ }
10290
+ function organization(v) {
10291
+ if (!record(v) || !uuid(v.id) || !text(v.slug) || !text(v.name))
10292
+ return invalid();
10293
+ return { id: v.id, slug: v.slug, name: v.name };
10294
+ }
10295
+ function parseAccountWorkspaces(value) {
10296
+ if (!record(value) || !Array.isArray(value.workspaces) || !value.workspaces.length || value.workspaces.length > 1000)
10297
+ return invalid();
10298
+ const workspaces = value.workspaces.map((v) => {
10299
+ if (!record(v) || !uuid(v.membershipId) || !role(v.role) || typeof v.current !== "boolean")
10300
+ return invalid();
10301
+ return { membershipId: v.membershipId, organization: organization(v.organization), role: v.role, current: v.current };
10302
+ });
10303
+ if (workspaces.filter((w) => w.current).length !== 1 || new Set(workspaces.map((w) => w.membershipId)).size !== workspaces.length || new Set(workspaces.map((w) => w.organization.id)).size !== workspaces.length)
10304
+ return invalid();
10305
+ return { workspaces };
10306
+ }
10307
+ function parseWorkspaceIdentity(value, expectedUserId) {
10308
+ if (!record(value))
10309
+ return invalid();
10310
+ const user = value.user;
10311
+ if (!record(user) || !uuid(user.id) || !uuid(user.membershipId) || !text(user.email, 320) || !(user.displayName === null || text(user.displayName)) || !role(user.role))
10312
+ return invalid();
10313
+ if (user.id !== expectedUserId)
10314
+ throw new WorkspaceIdentityMismatchError;
10315
+ return { user: { id: user.id, membershipId: user.membershipId, email: user.email, displayName: user.displayName, role: user.role }, organization: organization(value.organization) };
10316
+ }
10317
+ function sessionToken(value) {
10318
+ if (typeof value !== "string" || !value || value.length > 8192 || /[^\x21-\x7e]/.test(value) || value.startsWith("sk_"))
10319
+ return invalid();
10320
+ return value;
10321
+ }
10322
+ function parseWorkspaceSession(value, expected) {
10323
+ const identity = parseWorkspaceIdentity(value, expected.userId);
10324
+ if (identity.user.membershipId !== expected.membershipId)
10325
+ throw new WorkspaceIdentityMismatchError;
10326
+ return { token: sessionToken(value.token), ...identity };
10327
+ }
10328
+ function parseWorkspaceLogin(value, expectedUserId) {
10329
+ const user = record(value) && value.user;
10330
+ if (!record(value) || !record(user) || !uuid(user.id))
10331
+ return invalid();
10332
+ if (expectedUserId !== undefined && user.id !== expectedUserId)
10333
+ throw new WorkspaceIdentityMismatchError;
10334
+ return { token: sessionToken(value.token), userId: user.id };
10335
+ }
10336
+ var workspaceSelectionFailures = {
10337
+ INVALID_WORKSPACE_SELECTION: [400, "Provide only the exact membership ID from your workspace list."],
10338
+ SESSION_EXPIRED: [401, "Sign in again before selecting a workspace."],
10339
+ ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
10340
+ INTERACTIVE_SESSION_REQUIRED: [403, "Interactive sign-in is required to select a workspace."],
10341
+ WORKSPACE_UNAVAILABLE: [404, "Workspace is unavailable. Refresh your workspace list."],
10342
+ WORKSPACE_BUSY: [503, "Workspace is busy. Refresh before retrying."]
10343
+ };
10344
+ function workspaceSelectionFailure(value, status) {
10345
+ if (!record(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceSelectionFailures, value.code))
10346
+ return null;
10347
+ const code = value.code;
10348
+ return workspaceSelectionFailures[code][0] === status ? code : null;
10349
+ }
10350
+
10257
10351
  // src/lib/remote-workspace.ts
10258
- var record = (value) => !!value && typeof value === "object" && !Array.isArray(value);
10352
+ var record2 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
10259
10353
  var cursor = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,512}$/.test(value);
10260
- 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);
10354
+ 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);
10261
10355
  function workspaceMembersQuery(options = {}) {
10262
- 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))
10356
+ 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))
10263
10357
  throw new Error("Use a roster limit from 1 to 100 and an unchanged continuation cursor.");
10264
10358
  const query = new URLSearchParams;
10265
10359
  if (options.limit !== undefined)
@@ -10275,7 +10369,7 @@ function timestamp(value) {
10275
10369
  return Number.isFinite(time) && new Date(time).toISOString().slice(0, 23) === value.slice(0, 23);
10276
10370
  }
10277
10371
  function parseMember(row, fail) {
10278
- 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))
10372
+ 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))
10279
10373
  return fail();
10280
10374
  return {
10281
10375
  membershipId: row.membershipId,
@@ -10295,12 +10389,12 @@ class WorkspaceMemberInputError extends Error {
10295
10389
  }
10296
10390
  }
10297
10391
  function mutationInput(membershipId, input, roleChange) {
10298
- if (typeof membershipId !== "string" || !uuid(membershipId) || membershipId !== membershipId.toLowerCase() || !record(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
10392
+ if (typeof membershipId !== "string" || !uuid2(membershipId) || membershipId !== membershipId.toLowerCase() || !record2(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
10299
10393
  throw new WorkspaceMemberInputError;
10300
- const expectedRole = input.expectedRole, role = roleChange ? input.role : undefined;
10301
- if (!isRole(expectedRole) || roleChange && !isRole(role))
10394
+ const expectedRole = input.expectedRole, role2 = roleChange ? input.role : undefined;
10395
+ if (!isRole(expectedRole) || roleChange && !isRole(role2))
10302
10396
  throw new WorkspaceMemberInputError;
10303
- return { membershipId, role, expectedRole };
10397
+ return { membershipId, role: role2, expectedRole };
10304
10398
  }
10305
10399
  function workspaceMemberRoleInput(membershipId, input) {
10306
10400
  const value = mutationInput(membershipId, input, true);
@@ -10311,19 +10405,19 @@ function workspaceMemberRemovalInput(membershipId, input) {
10311
10405
  return { membershipId: value.membershipId, body: { expectedRole: value.expectedRole } };
10312
10406
  }
10313
10407
  var invalidMemberResult = "The server returned an invalid workspace member result. Refresh the roster before another action.";
10314
- function parseWorkspaceMemberRoleResult(value, membershipId, role) {
10408
+ function parseWorkspaceMemberRoleResult(value, membershipId, role2) {
10315
10409
  const fail = () => {
10316
10410
  throw new Error(invalidMemberResult);
10317
10411
  };
10318
- if (!record(value) || !uuid(value.organizationId) || typeof value.changed !== "boolean")
10412
+ if (!record2(value) || !uuid2(value.organizationId) || typeof value.changed !== "boolean")
10319
10413
  return fail();
10320
10414
  const member = parseMember(value.member, fail);
10321
- if (member.membershipId !== membershipId || member.role !== role)
10415
+ if (member.membershipId !== membershipId || member.role !== role2)
10322
10416
  return fail();
10323
10417
  return { organizationId: value.organizationId, member, changed: value.changed };
10324
10418
  }
10325
10419
  function parseWorkspaceMemberRemovalResult(value, membershipId) {
10326
- if (!record(value) || !uuid(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
10420
+ if (!record2(value) || !uuid2(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
10327
10421
  throw new Error(invalidMemberResult);
10328
10422
  return { organizationId: value.organizationId, membershipId, removed: true, alreadyRemoved: value.alreadyRemoved };
10329
10423
  }
@@ -10340,7 +10434,7 @@ var workspaceMemberFailures = {
10340
10434
  MEMBERSHIP_BUSY: [503, "Membership is busy. Refresh the roster before another action."]
10341
10435
  };
10342
10436
  function workspaceMemberFailure(value, status) {
10343
- if (!record(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
10437
+ if (!record2(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
10344
10438
  return null;
10345
10439
  const code = value.code;
10346
10440
  return workspaceMemberFailures[code][0] === status ? code : null;
@@ -10349,7 +10443,7 @@ function parseWorkspaceMembersPage(value) {
10349
10443
  const fail = () => {
10350
10444
  throw new Error("The server returned an invalid workspace roster.");
10351
10445
  };
10352
- 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)
10446
+ 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)
10353
10447
  return fail();
10354
10448
  const members = value.members.map((row) => parseMember(row, fail));
10355
10449
  if (new Set(members.map((row) => row.membershipId)).size !== members.length)
@@ -10364,44 +10458,44 @@ function getApiUrl(action, env = process.env, options = {}) {
10364
10458
  // src/lib/remote-run-contract.ts
10365
10459
  var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
10366
10460
  function normalizeRemoteSkillRunContract(payload, fallbackSkill) {
10367
- const record2 = isRecord3(payload) ? payload : {};
10461
+ const record3 = isRecord3(payload) ? payload : {};
10368
10462
  return {
10369
10463
  contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
10370
- ...pickString(record2, "id"),
10371
- skill: pickStringValue(record2, "skill") ?? fallbackSkill,
10372
- ...pickString(record2, "requestedSlug"),
10373
- ...pickString(record2, "status"),
10374
- ...pickNumber(record2, "exitCode"),
10375
- ...pickString(record2, "correlationId"),
10376
- ...pickString(record2, "createdAt"),
10377
- ...pickString(record2, "startedAt"),
10378
- ...pickString(record2, "completedAt"),
10379
- ...pickNumber(record2, "durationMs"),
10380
- ...pickString(record2, "outputType"),
10381
- ...hasOwn(record2, "outputPreview") ? { outputPreview: record2.outputPreview } : {},
10382
- ...pickString(record2, "errorCode"),
10383
- ...pickString(record2, "errorMessage"),
10384
- ...pickString(record2, "error"),
10385
- ...pickString(record2, "code"),
10386
- ...hasOwn(record2, "details") ? { details: record2.details } : {}
10464
+ ...pickString(record3, "id"),
10465
+ skill: pickStringValue(record3, "skill") ?? fallbackSkill,
10466
+ ...pickString(record3, "requestedSlug"),
10467
+ ...pickString(record3, "status"),
10468
+ ...pickNumber(record3, "exitCode"),
10469
+ ...pickString(record3, "correlationId"),
10470
+ ...pickString(record3, "createdAt"),
10471
+ ...pickString(record3, "startedAt"),
10472
+ ...pickString(record3, "completedAt"),
10473
+ ...pickNumber(record3, "durationMs"),
10474
+ ...pickString(record3, "outputType"),
10475
+ ...hasOwn(record3, "outputPreview") ? { outputPreview: record3.outputPreview } : {},
10476
+ ...pickString(record3, "errorCode"),
10477
+ ...pickString(record3, "errorMessage"),
10478
+ ...pickString(record3, "error"),
10479
+ ...pickString(record3, "code"),
10480
+ ...hasOwn(record3, "details") ? { details: record3.details } : {}
10387
10481
  };
10388
10482
  }
10389
10483
  function isRecord3(value) {
10390
10484
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
10391
10485
  }
10392
- function hasOwn(record2, key) {
10393
- return Object.prototype.hasOwnProperty.call(record2, key);
10486
+ function hasOwn(record3, key) {
10487
+ return Object.prototype.hasOwnProperty.call(record3, key);
10394
10488
  }
10395
- function pickString(record2, key) {
10396
- const value = pickStringValue(record2, key);
10489
+ function pickString(record3, key) {
10490
+ const value = pickStringValue(record3, key);
10397
10491
  return value === undefined ? {} : { [key]: value };
10398
10492
  }
10399
- function pickStringValue(record2, key) {
10400
- const value = record2[key];
10493
+ function pickStringValue(record3, key) {
10494
+ const value = record3[key];
10401
10495
  return typeof value === "string" ? value : undefined;
10402
10496
  }
10403
- function pickNumber(record2, key) {
10404
- const value = record2[key];
10497
+ function pickNumber(record3, key) {
10498
+ const value = record3[key];
10405
10499
  return typeof value === "number" && Number.isFinite(value) ? { [key]: value } : {};
10406
10500
  }
10407
10501
 
@@ -10564,11 +10658,11 @@ function parseUpdatedProfile(value) {
10564
10658
  return { user: { id: user.id, email: user.email, displayName: user.displayName, role: user.role } };
10565
10659
  }
10566
10660
  function parseUpdatedWorkspace(value) {
10567
- const organization = isRecord4(value) && value.organization;
10568
- if (!isRecord4(organization) || !string(organization.id) || !string(organization.slug) || !string(organization.name)) {
10661
+ const organization2 = isRecord4(value) && value.organization;
10662
+ if (!isRecord4(organization2) || !string(organization2.id) || !string(organization2.slug) || !string(organization2.name)) {
10569
10663
  throw new Error("The server returned an invalid workspace.");
10570
10664
  }
10571
- return { organization: { id: organization.id, slug: organization.slug, name: organization.name } };
10665
+ return { organization: { id: organization2.id, slug: organization2.slug, name: organization2.name } };
10572
10666
  }
10573
10667
 
10574
10668
  // src/lib/remote-client.ts
@@ -10606,6 +10700,16 @@ class RemoteWorkspaceMemberError extends RemoteRequestError {
10606
10700
  }
10607
10701
  }
10608
10702
 
10703
+ class RemoteWorkspaceSelectionError extends RemoteRequestError {
10704
+ code;
10705
+ constructor(path, code) {
10706
+ super(path, workspaceSelectionFailures[code][0]);
10707
+ this.code = code;
10708
+ this.name = "RemoteWorkspaceSelectionError";
10709
+ this.message = workspaceSelectionFailures[code][1];
10710
+ }
10711
+ }
10712
+
10609
10713
  class RemoteCapabilityUnavailableError extends RemoteRequestError {
10610
10714
  code = "SUBSCRIPTION_CHECKOUT_UNAVAILABLE";
10611
10715
  constructor() {
@@ -10627,6 +10731,7 @@ class RemoteSkillsClient {
10627
10731
  return fetch(`${this.apiUrl}${path}`, {
10628
10732
  ...options,
10629
10733
  redirect: "error",
10734
+ credentials: "omit",
10630
10735
  signal: options?.signal ?? AbortSignal.timeout(15000),
10631
10736
  headers: {
10632
10737
  Authorization: `Bearer ${this.apiKey}`,
@@ -10733,6 +10838,65 @@ class RemoteSkillsClient {
10733
10838
  async getIdentity() {
10734
10839
  return (await this.requestNewRoute("/api/auth/whoami")).json();
10735
10840
  }
10841
+ async listAccountWorkspaces(expectedUserId) {
10842
+ const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
10843
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
10844
+ let identity;
10845
+ if (expected !== undefined) {
10846
+ const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
10847
+ if (!value || typeof value !== "object" || value.authMethod !== "jwt")
10848
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces", "INTERACTIVE_SESSION_REQUIRED");
10849
+ identity = parseWorkspaceIdentity(value, expected);
10850
+ }
10851
+ const result = parseAccountWorkspaces(await connection.requestWorkspaceSelection("/api/v1/account/workspaces"));
10852
+ const current = result.workspaces.find((workspace) => workspace.current);
10853
+ if (identity && (current.membershipId !== identity.user.membershipId || current.organization.id !== identity.organization.id))
10854
+ throw new WorkspaceIdentityMismatchError;
10855
+ return result;
10856
+ }
10857
+ async switchWorkspace(context) {
10858
+ const target = workspaceContext(context);
10859
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
10860
+ const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
10861
+ if (!value || typeof value !== "object" || value.authMethod !== "jwt")
10862
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
10863
+ parseWorkspaceIdentity(value, target.userId);
10864
+ const selected = parseWorkspaceSession(await connection.requestWorkspaceSelection("/api/v1/account/workspaces/switch", {
10865
+ method: "POST",
10866
+ body: JSON.stringify({ membershipId: target.membershipId })
10867
+ }), target);
10868
+ const verified = await new RemoteSkillsClient(selected.token, connection.apiUrl).requestWorkspaceSelection("/api/auth/whoami");
10869
+ if (!verified || typeof verified !== "object" || verified.authMethod !== "jwt")
10870
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
10871
+ const identity = parseWorkspaceIdentity(verified, target.userId);
10872
+ if (identity.user.membershipId !== target.membershipId || identity.organization.id !== selected.organization.id)
10873
+ throw new WorkspaceIdentityMismatchError;
10874
+ return { token: selected.token, ...identity };
10875
+ }
10876
+ async requestWorkspaceSelection(path, options) {
10877
+ let response;
10878
+ try {
10879
+ response = await this.request(path, { ...options, credentials: "omit" });
10880
+ } catch {
10881
+ throw new Error("Unable to reach the Skills workspace API.");
10882
+ }
10883
+ let value;
10884
+ try {
10885
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 1024 * 1024 : 4096)));
10886
+ } catch {
10887
+ if (response.ok)
10888
+ throw new Error(invalidWorkspaceResult);
10889
+ }
10890
+ if (!response.ok) {
10891
+ const code = workspaceSelectionFailure(value, response.status);
10892
+ if (code)
10893
+ throw new RemoteWorkspaceSelectionError(path, code);
10894
+ if (response.status === 404 || response.status === 405)
10895
+ throw new RemoteRouteUnsupportedError(path, response.status, this.apiUrl);
10896
+ throw new RemoteRequestError(path, response.status);
10897
+ }
10898
+ return value;
10899
+ }
10736
10900
  async updateProfile(input) {
10737
10901
  const body = customerNamePatch(input, "displayName");
10738
10902
  return parseUpdatedProfile(await (await this.requestNewRoute("/api/v1/account/profile", { method: "PATCH", body: JSON.stringify(body) })).json());
@@ -11035,13 +11199,13 @@ class RemoteSkillsClient {
11035
11199
  return normalizeUpdatedSincePage(await response.json());
11036
11200
  }
11037
11201
  }
11038
- function requireOptionalString(record2, field) {
11039
- if (record2[field] === undefined)
11202
+ function requireOptionalString(record3, field) {
11203
+ if (record3[field] === undefined)
11040
11204
  return;
11041
- if (typeof record2[field] !== "string") {
11205
+ if (typeof record3[field] !== "string") {
11042
11206
  throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
11043
11207
  }
11044
- return record2[field];
11208
+ return record3[field];
11045
11209
  }
11046
11210
  var INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
11047
11211
  function isVersionRecord(value) {
@@ -11064,19 +11228,19 @@ function normalizePin(entry) {
11064
11228
  if (!entry || typeof entry !== "object") {
11065
11229
  throw new Error("Remote pin payload did not match the expected contract (expected an object)");
11066
11230
  }
11067
- const record2 = entry;
11068
- const slug = typeof record2.slug === "string" && record2.slug.trim() ? record2.slug.trim() : undefined;
11231
+ const record3 = entry;
11232
+ const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
11069
11233
  if (!slug) {
11070
11234
  throw new Error("Remote pin payload did not match the expected contract (missing slug)");
11071
11235
  }
11072
11236
  let metadata;
11073
- if (record2.metadata !== undefined) {
11074
- if (!record2.metadata || typeof record2.metadata !== "object" || Array.isArray(record2.metadata)) {
11237
+ if (record3.metadata !== undefined) {
11238
+ if (!record3.metadata || typeof record3.metadata !== "object" || Array.isArray(record3.metadata)) {
11075
11239
  throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
11076
11240
  }
11077
- metadata = record2.metadata;
11241
+ metadata = record3.metadata;
11078
11242
  }
11079
- const pinnedAt = requireOptionalString(record2, "pinnedAt");
11243
+ const pinnedAt = requireOptionalString(record3, "pinnedAt");
11080
11244
  return {
11081
11245
  slug,
11082
11246
  ...pinnedAt !== undefined ? { pinnedAt } : {},
@@ -11093,16 +11257,16 @@ function normalizeSkillSummary(entry) {
11093
11257
  if (!entry || typeof entry !== "object") {
11094
11258
  throw new Error("Remote skill payload did not match the expected contract (expected an object)");
11095
11259
  }
11096
- const record2 = entry;
11097
- const slug = typeof record2.slug === "string" && record2.slug.trim() ? record2.slug.trim() : undefined;
11260
+ const record3 = entry;
11261
+ const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
11098
11262
  if (!slug) {
11099
11263
  throw new Error("Remote skill payload did not match the expected contract (missing slug)");
11100
11264
  }
11101
11265
  return {
11102
11266
  slug,
11103
- ...requireOptionalString(record2, "name") !== undefined ? { name: requireOptionalString(record2, "name") } : {},
11104
- ...requireOptionalString(record2, "version") !== undefined ? { version: requireOptionalString(record2, "version") } : {},
11105
- ...requireOptionalString(record2, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record2, "updatedAt") } : {}
11267
+ ...requireOptionalString(record3, "name") !== undefined ? { name: requireOptionalString(record3, "name") } : {},
11268
+ ...requireOptionalString(record3, "version") !== undefined ? { version: requireOptionalString(record3, "version") } : {},
11269
+ ...requireOptionalString(record3, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record3, "updatedAt") } : {}
11106
11270
  };
11107
11271
  }
11108
11272
  function normalizeSkillSummaryList(payload) {
@@ -11155,12 +11319,12 @@ function normalizeUpdatedSincePage(payload) {
11155
11319
  if (!payload || typeof payload !== "object") {
11156
11320
  throw new Error("Updated-since payload did not match the expected contract (expected an object)");
11157
11321
  }
11158
- const record2 = payload;
11159
- if (!Array.isArray(record2.skills)) {
11322
+ const record3 = payload;
11323
+ if (!Array.isArray(record3.skills)) {
11160
11324
  throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
11161
11325
  }
11162
- const skills = record2.skills.map(normalizeSkillSummary);
11163
- const nextCursor = record2.nextCursor === undefined || record2.nextCursor === null ? null : record2.nextCursor;
11326
+ const skills = record3.skills.map(normalizeSkillSummary);
11327
+ const nextCursor = record3.nextCursor === undefined || record3.nextCursor === null ? null : record3.nextCursor;
11164
11328
  if (nextCursor !== null && typeof nextCursor !== "string") {
11165
11329
  throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
11166
11330
  }
@@ -11397,8 +11561,8 @@ function revisionIdOf(content) {
11397
11561
  });
11398
11562
  return createHash4("sha256").update(canonical).digest("hex");
11399
11563
  }
11400
- function revisionIdOfRecord(record2) {
11401
- return revisionIdOf(record2);
11564
+ function revisionIdOfRecord(record3) {
11565
+ return revisionIdOf(record3);
11402
11566
  }
11403
11567
 
11404
11568
  // src/lib/skill-bundle.ts
@@ -12105,16 +12269,16 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
12105
12269
  }
12106
12270
  return { path: target, created };
12107
12271
  }
12108
- function writePullMarker(dir, record2) {
12272
+ function writePullMarker(dir, record3) {
12109
12273
  const marker = {
12110
12274
  managedBy: "@hasna/skills",
12111
- skill: record2.skill,
12112
- source: record2.source ?? "pull",
12113
- ...record2.version ? { version: record2.version } : {},
12114
- ...record2.contentHash ? { contentHash: record2.contentHash } : {},
12115
- ...record2.sourceCommit ? { sourceCommit: record2.sourceCommit } : {},
12116
- ...record2.signature ? { signature: record2.signature } : {},
12117
- ...record2.revisionId ? { revisionId: record2.revisionId } : {},
12275
+ skill: record3.skill,
12276
+ source: record3.source ?? "pull",
12277
+ ...record3.version ? { version: record3.version } : {},
12278
+ ...record3.contentHash ? { contentHash: record3.contentHash } : {},
12279
+ ...record3.sourceCommit ? { sourceCommit: record3.sourceCommit } : {},
12280
+ ...record3.signature ? { signature: record3.signature } : {},
12281
+ ...record3.revisionId ? { revisionId: record3.revisionId } : {},
12118
12282
  syncedAt: new Date().toISOString()
12119
12283
  };
12120
12284
  writeFileSync8(join17(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
@@ -12129,19 +12293,19 @@ async function safeMeta(client, slug) {
12129
12293
  }
12130
12294
  if (!raw || typeof raw !== "object")
12131
12295
  return null;
12132
- const record2 = raw;
12133
- const kind = record2.kind === "instruction" || record2.kind === "executable" ? record2.kind : undefined;
12134
- const tags = Array.isArray(record2.tags) ? record2.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0) : undefined;
12296
+ const record3 = raw;
12297
+ const kind = record3.kind === "instruction" || record3.kind === "executable" ? record3.kind : undefined;
12298
+ const tags = Array.isArray(record3.tags) ? record3.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0) : undefined;
12135
12299
  return {
12136
- ...str(record2.displayName) ? { displayName: str(record2.displayName) } : {},
12137
- ...str(record2.description) ? { description: str(record2.description) } : {},
12138
- ...str(record2.category) ? { category: str(record2.category) } : {},
12300
+ ...str(record3.displayName) ? { displayName: str(record3.displayName) } : {},
12301
+ ...str(record3.description) ? { description: str(record3.description) } : {},
12302
+ ...str(record3.category) ? { category: str(record3.category) } : {},
12139
12303
  ...tags && tags.length ? { tags } : {},
12140
- ...str(record2.version) ? { version: str(record2.version) } : {},
12304
+ ...str(record3.version) ? { version: str(record3.version) } : {},
12141
12305
  ...kind ? { kind } : {},
12142
- ...REVISION_ID_PATTERN.test(str(record2.revisionId) ?? "") ? { revisionId: str(record2.revisionId) } : {},
12143
- ...typeof record2.skillMd === "string" && record2.skillMd.length > 0 ? { skillMd: record2.skillMd } : {},
12144
- ...str(record2.publishedSource) ? { publishedSource: str(record2.publishedSource) } : {}
12306
+ ...REVISION_ID_PATTERN.test(str(record3.revisionId) ?? "") ? { revisionId: str(record3.revisionId) } : {},
12307
+ ...typeof record3.skillMd === "string" && record3.skillMd.length > 0 ? { skillMd: record3.skillMd } : {},
12308
+ ...str(record3.publishedSource) ? { publishedSource: str(record3.publishedSource) } : {}
12145
12309
  };
12146
12310
  }
12147
12311
  function pickCorpusOptions(options) {
@@ -12150,8 +12314,8 @@ function pickCorpusOptions(options) {
12150
12314
  function extractSlug(entry) {
12151
12315
  if (!entry || typeof entry !== "object")
12152
12316
  return;
12153
- const record2 = entry;
12154
- return str(record2.slug) ?? str(record2.name);
12317
+ const record3 = entry;
12318
+ return str(record3.slug) ?? str(record3.name);
12155
12319
  }
12156
12320
  function dedupe(values) {
12157
12321
  return [...new Set(values)];
@@ -12277,7 +12441,7 @@ import { dirname as dirname7, relative as relative4 } from "path";
12277
12441
  // package.json
12278
12442
  var package_default = {
12279
12443
  name: "@hasna/skills",
12280
- version: "0.5.0",
12444
+ version: "0.5.1",
12281
12445
  description: "Skills library for AI coding agents",
12282
12446
  type: "module",
12283
12447
  bin: {
@@ -13788,7 +13952,7 @@ class SkillsPostgresSyncStore {
13788
13952
  }
13789
13953
  async upsertRecords(records) {
13790
13954
  let count = 0;
13791
- for (const record2 of records) {
13955
+ for (const record3 of records) {
13792
13956
  await this.client.query([
13793
13957
  "INSERT INTO skills_sync_records",
13794
13958
  "(scope, kind, id, updated_at, deleted_at, source, payload)",
@@ -13799,13 +13963,13 @@ class SkillsPostgresSyncStore {
13799
13963
  "source = EXCLUDED.source,",
13800
13964
  "payload = EXCLUDED.payload"
13801
13965
  ].join(" "), [
13802
- record2.scope,
13803
- record2.kind,
13804
- record2.id,
13805
- record2.updatedAt,
13806
- record2.deletedAt ?? null,
13807
- record2.source ?? null,
13808
- JSON.stringify(record2.payload)
13966
+ record3.scope,
13967
+ record3.kind,
13968
+ record3.id,
13969
+ record3.updatedAt,
13970
+ record3.deletedAt ?? null,
13971
+ record3.source ?? null,
13972
+ JSON.stringify(record3.payload)
13809
13973
  ]);
13810
13974
  count += 1;
13811
13975
  }
@@ -14784,13 +14948,13 @@ async function requestAuthApi(instance, path, options) {
14784
14948
  apiUrl: safeUrl
14785
14949
  });
14786
14950
  }
14787
- const text = await res.text();
14788
- const body = text ? parseJsonBody(text) : {};
14951
+ const text2 = await res.text();
14952
+ const body = text2 ? parseJsonBody(text2) : {};
14789
14953
  if (!res.ok) {
14790
- const record2 = isRecord5(body) ? body : {};
14791
- const detail = typeof record2.detail === "string" ? record2.detail : undefined;
14792
- const error = typeof record2.error === "string" ? record2.error : undefined;
14793
- const code = typeof record2.code === "string" ? record2.code : undefined;
14954
+ const record3 = isRecord5(body) ? body : {};
14955
+ const detail = typeof record3.detail === "string" ? record3.detail : undefined;
14956
+ const error = typeof record3.error === "string" ? record3.error : undefined;
14957
+ const code = typeof record3.code === "string" ? record3.code : undefined;
14794
14958
  throw new HostedApiError(detail || error || `${res.status} ${res.statusText}`, {
14795
14959
  status: res.status,
14796
14960
  code,
@@ -14801,15 +14965,15 @@ async function requestAuthApi(instance, path, options) {
14801
14965
  }
14802
14966
  return body;
14803
14967
  }
14804
- function parseJsonBody(text) {
14968
+ function parseJsonBody(text2) {
14805
14969
  try {
14806
- return JSON.parse(text);
14970
+ return JSON.parse(text2);
14807
14971
  } catch {
14808
- return { detail: condenseErrorBody(text) };
14972
+ return { detail: condenseErrorBody(text2) };
14809
14973
  }
14810
14974
  }
14811
- function condenseErrorBody(text) {
14812
- const stripped = /<[a-z!/]/i.test(text) ? text.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text;
14975
+ function condenseErrorBody(text2) {
14976
+ const stripped = /<[a-z!/]/i.test(text2) ? text2.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text2;
14813
14977
  const collapsed = stripped.replace(/\s+/g, " ").trim();
14814
14978
  if (collapsed.length <= MAX_ERROR_DETAIL_LENGTH)
14815
14979
  return collapsed;
@@ -14836,7 +15000,12 @@ class RemoteSkillsAuthClient {
14836
15000
  pollDevice(deviceCode) {
14837
15001
  return this.request("/api/auth/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) });
14838
15002
  }
14839
- async sessionClient(email, code) {
15003
+ async sessionClient(email, code, context) {
15004
+ if (context !== undefined) {
15005
+ const target = workspaceContext(context), apiOrigin2 = this.apiOrigin;
15006
+ const session = await this.switchWorkspace(email, code, target);
15007
+ return new RemoteSkillsClient(session.token, apiOrigin2);
15008
+ }
14840
15009
  const apiOrigin = this.apiOrigin;
14841
15010
  if (!email.includes("@") || !/^\d{6}$/.test(code))
14842
15011
  throw new Error("Fresh email and six-digit verification code are required to manage this account");
@@ -14845,34 +15014,76 @@ class RemoteSkillsAuthClient {
14845
15014
  throw new Error("The server did not return an authorized account session");
14846
15015
  return new RemoteSkillsClient(login.token, apiOrigin);
14847
15016
  }
14848
- async createApiKey(email, code, name, scopes) {
14849
- return (await this.sessionClient(email, code)).createApiKey(name, scopes);
15017
+ async listAccountWorkspaces(email, code, expectedUserId) {
15018
+ const login = await this.workspaceLogin(email, code, expectedUserId);
15019
+ const result = await new RemoteSkillsClient(login.token, login.apiOrigin).listAccountWorkspaces(login.userId);
15020
+ return { userId: login.userId, ...result };
14850
15021
  }
14851
- async listApiKeys(email, code) {
14852
- return (await this.sessionClient(email, code)).listApiKeys();
15022
+ async switchWorkspace(email, code, context) {
15023
+ const target = workspaceContext(context);
15024
+ const login = await this.workspaceLogin(email, code, target.userId);
15025
+ return new RemoteSkillsClient(login.token, login.apiOrigin).switchWorkspace(target);
14853
15026
  }
14854
- async revokeApiKey(email, code, keyId) {
14855
- return (await this.sessionClient(email, code)).revokeApiKey(keyId);
15027
+ async workspaceLogin(email, code, expectedUserId) {
15028
+ const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
15029
+ const apiOrigin = this.apiOrigin;
15030
+ if (typeof email !== "string" || !email.includes("@") || typeof code !== "string" || !/^\d{6}$/.test(code))
15031
+ throw new Error("Fresh email and six-digit verification code are required to manage this account");
15032
+ let response;
15033
+ try {
15034
+ response = await fetch(`${apiOrigin}/api/auth/verify`, {
15035
+ method: "POST",
15036
+ redirect: "error",
15037
+ credentials: "omit",
15038
+ signal: AbortSignal.timeout(15000),
15039
+ headers: { "Content-Type": "application/json" },
15040
+ body: JSON.stringify({ email, code })
15041
+ });
15042
+ } catch {
15043
+ throw new HostedApiError("Unable to verify the Skills account.");
15044
+ }
15045
+ if (!response.ok) {
15046
+ response.body?.cancel().catch(() => {});
15047
+ throw new HostedApiError("Unable to verify the Skills account.", { status: response.status });
15048
+ }
15049
+ let value;
15050
+ try {
15051
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 64 * 1024)));
15052
+ } catch {
15053
+ throw new HostedApiError("The server returned an invalid account verification result.");
15054
+ }
15055
+ return { ...parseWorkspaceLogin(value, expected), apiOrigin };
15056
+ }
15057
+ async createApiKey(email, code, name, scopes, context) {
15058
+ const capturedScopes = scopes === undefined ? undefined : [...scopes];
15059
+ return (await this.sessionClient(email, code, context)).createApiKey(name, capturedScopes);
15060
+ }
15061
+ async listApiKeys(email, code, context) {
15062
+ return (await this.sessionClient(email, code, context)).listApiKeys();
14856
15063
  }
14857
- async updateProfile(email, code, input) {
14858
- customerNamePatch(input, "displayName");
14859
- return (await this.sessionClient(email, code)).updateProfile(input);
15064
+ async revokeApiKey(email, code, keyId, context) {
15065
+ return (await this.sessionClient(email, code, context)).revokeApiKey(keyId);
14860
15066
  }
14861
- async updateCurrentWorkspace(email, code, input) {
14862
- customerNamePatch(input, "name");
14863
- return (await this.sessionClient(email, code)).updateCurrentWorkspace(input);
15067
+ async updateProfile(email, code, input, context) {
15068
+ const body = customerNamePatch(input, "displayName");
15069
+ return (await this.sessionClient(email, code, context)).updateProfile({ displayName: body.displayName });
15070
+ }
15071
+ async updateCurrentWorkspace(email, code, input, context) {
15072
+ const body = customerNamePatch(input, "name");
15073
+ return (await this.sessionClient(email, code, context)).updateCurrentWorkspace({ name: body.name });
14864
15074
  }
14865
- async listWorkspaceMembers(email, code, options = {}) {
15075
+ async listWorkspaceMembers(email, code, options = {}, context) {
14866
15076
  workspaceMembersQuery(options);
14867
- return (await this.sessionClient(email, code)).listWorkspaceMembers(options);
15077
+ const captured = { ...options };
15078
+ return (await this.sessionClient(email, code, context)).listWorkspaceMembers(captured);
14868
15079
  }
14869
- async setWorkspaceMemberRole(email, code, membershipId, input) {
15080
+ async setWorkspaceMemberRole(email, code, membershipId, input, context) {
14870
15081
  const captured = workspaceMemberRoleInput(membershipId, input);
14871
- return (await this.sessionClient(email, code)).setWorkspaceMemberRole(captured.membershipId, captured.body);
15082
+ return (await this.sessionClient(email, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
14872
15083
  }
14873
- async removeWorkspaceMember(email, code, membershipId, input) {
15084
+ async removeWorkspaceMember(email, code, membershipId, input, context) {
14874
15085
  const captured = workspaceMemberRemovalInput(membershipId, input);
14875
- return (await this.sessionClient(email, code)).removeWorkspaceMember(captured.membershipId, captured.body);
15086
+ return (await this.sessionClient(email, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
14876
15087
  }
14877
15088
  request(path, options) {
14878
15089
  if (!["/api/auth/login", "/api/auth/verify", "/api/auth/device/start", "/api/auth/device/token", "/api/auth/keys", "/api/auth/whoami"].includes(path))
@@ -15062,6 +15273,8 @@ export {
15062
15273
  agentGlobalSkillsDir,
15063
15274
  addSchedule,
15064
15275
  adaptSkillMdForAgent,
15276
+ WorkspaceIdentityMismatchError,
15277
+ WorkspaceContextInputError,
15065
15278
  TOOL_PRIMITIVE_SCHEMA_VERSION,
15066
15279
  TOOL_PRIMITIVES,
15067
15280
  StationSnapshotError,
@@ -15093,6 +15306,7 @@ export {
15093
15306
  SKILLS_API_KEY_ENV_KEYS,
15094
15307
  SKILLS_API_KEY_ENV,
15095
15308
  SKILLS,
15309
+ RemoteWorkspaceSelectionError,
15096
15310
  RemoteWorkspaceMemberError,
15097
15311
  RemoteSkillsClient,
15098
15312
  RemoteSkillsAuthClient,