@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/bin/mcp.js CHANGED
@@ -8016,9 +8016,14 @@ var init_fleet_credentials = __esm(() => {
8016
8016
 
8017
8017
  // src/lib/auth-store.ts
8018
8018
  import { chmodSync, existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync11, renameSync as renameSync2, statSync as statSync7, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
8019
+ import { basename as basename3, dirname as dirname5, join as join13 } from "path";
8019
8020
  function getAuthFilePath(env = process.env) {
8020
8021
  return skillsCredentialFilePath(env);
8021
8022
  }
8023
+ function getIdentityFilePath(env = process.env) {
8024
+ const file = skillsCredentialFilePath(env);
8025
+ return join13(dirname5(file), basename3(file).replace(/^credentials/, "identity") + ".json");
8026
+ }
8022
8027
  function getApiUrl(action, env = process.env, options = {}) {
8023
8028
  return requireSkillsApiOrigin(action, env, options);
8024
8029
  }
@@ -8295,9 +8300,99 @@ var init_remote_files = __esm(() => {
8295
8300
  MAX_REMOTE_FILE_BYTES = 64 * 1024 * 1024;
8296
8301
  });
8297
8302
 
8303
+ // src/lib/remote-workspace-selection.ts
8304
+ function workspaceExpectedUserId(value) {
8305
+ if (!uuid2(value))
8306
+ throw new WorkspaceContextInputError;
8307
+ return value;
8308
+ }
8309
+ function workspaceContext(value) {
8310
+ if (!record3(value) || Object.keys(value).sort().join(",") !== "membershipId,userId" || !uuid2(value.userId) || !uuid2(value.membershipId))
8311
+ throw new WorkspaceContextInputError;
8312
+ return { userId: value.userId, membershipId: value.membershipId };
8313
+ }
8314
+ function invalid() {
8315
+ throw new Error(invalidWorkspaceResult);
8316
+ }
8317
+ function organization(v) {
8318
+ if (!record3(v) || !uuid2(v.id) || !text(v.slug) || !text(v.name))
8319
+ return invalid();
8320
+ return { id: v.id, slug: v.slug, name: v.name };
8321
+ }
8322
+ function parseAccountWorkspaces(value) {
8323
+ if (!record3(value) || !Array.isArray(value.workspaces) || !value.workspaces.length || value.workspaces.length > 1000)
8324
+ return invalid();
8325
+ const workspaces = value.workspaces.map((v) => {
8326
+ if (!record3(v) || !uuid2(v.membershipId) || !role(v.role) || typeof v.current !== "boolean")
8327
+ return invalid();
8328
+ return { membershipId: v.membershipId, organization: organization(v.organization), role: v.role, current: v.current };
8329
+ });
8330
+ 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)
8331
+ return invalid();
8332
+ return { workspaces };
8333
+ }
8334
+ function parseWorkspaceIdentity(value, expectedUserId) {
8335
+ if (!record3(value))
8336
+ return invalid();
8337
+ const user = value.user;
8338
+ if (!record3(user) || !uuid2(user.id) || !uuid2(user.membershipId) || !text(user.email, 320) || !(user.displayName === null || text(user.displayName)) || !role(user.role))
8339
+ return invalid();
8340
+ if (user.id !== expectedUserId)
8341
+ throw new WorkspaceIdentityMismatchError;
8342
+ return { user: { id: user.id, membershipId: user.membershipId, email: user.email, displayName: user.displayName, role: user.role }, organization: organization(value.organization) };
8343
+ }
8344
+ function sessionToken(value) {
8345
+ if (typeof value !== "string" || !value || value.length > 8192 || /[^\x21-\x7e]/.test(value) || value.startsWith("sk_"))
8346
+ return invalid();
8347
+ return value;
8348
+ }
8349
+ function parseWorkspaceSession(value, expected) {
8350
+ const identity = parseWorkspaceIdentity(value, expected.userId);
8351
+ if (identity.user.membershipId !== expected.membershipId)
8352
+ throw new WorkspaceIdentityMismatchError;
8353
+ return { token: sessionToken(value.token), ...identity };
8354
+ }
8355
+ function parseWorkspaceLogin(value, expectedUserId) {
8356
+ const user = record3(value) && value.user;
8357
+ if (!record3(value) || !record3(user) || !uuid2(user.id))
8358
+ return invalid();
8359
+ if (expectedUserId !== undefined && user.id !== expectedUserId)
8360
+ throw new WorkspaceIdentityMismatchError;
8361
+ return { token: sessionToken(value.token), userId: user.id };
8362
+ }
8363
+ function workspaceSelectionFailure(value, status) {
8364
+ if (!record3(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceSelectionFailures, value.code))
8365
+ return null;
8366
+ const code = value.code;
8367
+ return workspaceSelectionFailures[code][0] === status ? code : null;
8368
+ }
8369
+ var record3 = (v) => !!v && typeof v === "object" && !Array.isArray(v), uuid2 = (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), text = (v, max = 1024) => typeof v === "string" && !!v.trim() && v.length <= max && !/[\p{Cc}\p{Cs}\u2028\u2029]/u.test(v), role = (v) => typeof v === "string" && ["owner", "admin", "member", "viewer"].includes(v), invalidWorkspaceResult = "The server returned an invalid workspace selection result.", WorkspaceContextInputError, WorkspaceIdentityMismatchError, workspaceSelectionFailures;
8370
+ var init_remote_workspace_selection = __esm(() => {
8371
+ WorkspaceContextInputError = class WorkspaceContextInputError extends Error {
8372
+ constructor() {
8373
+ super("Provide the observed user ID and exact lowercase membership ID.");
8374
+ this.name = "WorkspaceContextInputError";
8375
+ }
8376
+ };
8377
+ WorkspaceIdentityMismatchError = class WorkspaceIdentityMismatchError extends Error {
8378
+ constructor() {
8379
+ super("The verified account does not match the requested workspace context.");
8380
+ this.name = "WorkspaceIdentityMismatchError";
8381
+ }
8382
+ };
8383
+ workspaceSelectionFailures = {
8384
+ INVALID_WORKSPACE_SELECTION: [400, "Provide only the exact membership ID from your workspace list."],
8385
+ SESSION_EXPIRED: [401, "Sign in again before selecting a workspace."],
8386
+ ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
8387
+ INTERACTIVE_SESSION_REQUIRED: [403, "Interactive sign-in is required to select a workspace."],
8388
+ WORKSPACE_UNAVAILABLE: [404, "Workspace is unavailable. Refresh your workspace list."],
8389
+ WORKSPACE_BUSY: [503, "Workspace is busy. Refresh before retrying."]
8390
+ };
8391
+ });
8392
+
8298
8393
  // src/lib/remote-workspace.ts
8299
8394
  function workspaceMembersQuery(options = {}) {
8300
- if (!record3(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))
8395
+ if (!record4(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))
8301
8396
  throw new Error("Use a roster limit from 1 to 100 and an unchanged continuation cursor.");
8302
8397
  const query = new URLSearchParams;
8303
8398
  if (options.limit !== undefined)
@@ -8313,7 +8408,7 @@ function timestamp(value) {
8313
8408
  return Number.isFinite(time3) && new Date(time3).toISOString().slice(0, 23) === value.slice(0, 23);
8314
8409
  }
8315
8410
  function parseMember(row, fail) {
8316
- if (!record3(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))
8411
+ if (!record4(row) || !uuid3(row.membershipId) || !uuid3(row.userId) || typeof row.email !== "string" || !row.email || !(row.displayName === null || typeof row.displayName === "string") || !isRole(row.role) || !timestamp(row.createdAt))
8317
8412
  return fail();
8318
8413
  return {
8319
8414
  membershipId: row.membershipId,
@@ -8325,12 +8420,12 @@ function parseMember(row, fail) {
8325
8420
  };
8326
8421
  }
8327
8422
  function mutationInput(membershipId, input, roleChange) {
8328
- if (typeof membershipId !== "string" || !uuid2(membershipId) || membershipId !== membershipId.toLowerCase() || !record3(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
8423
+ if (typeof membershipId !== "string" || !uuid3(membershipId) || membershipId !== membershipId.toLowerCase() || !record4(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
8329
8424
  throw new WorkspaceMemberInputError;
8330
- const expectedRole = input.expectedRole, role = roleChange ? input.role : undefined;
8331
- if (!isRole(expectedRole) || roleChange && !isRole(role))
8425
+ const expectedRole = input.expectedRole, role2 = roleChange ? input.role : undefined;
8426
+ if (!isRole(expectedRole) || roleChange && !isRole(role2))
8332
8427
  throw new WorkspaceMemberInputError;
8333
- return { membershipId, role, expectedRole };
8428
+ return { membershipId, role: role2, expectedRole };
8334
8429
  }
8335
8430
  function workspaceMemberRoleInput(membershipId, input) {
8336
8431
  const value = mutationInput(membershipId, input, true);
@@ -8340,24 +8435,24 @@ function workspaceMemberRemovalInput(membershipId, input) {
8340
8435
  const value = mutationInput(membershipId, input, false);
8341
8436
  return { membershipId: value.membershipId, body: { expectedRole: value.expectedRole } };
8342
8437
  }
8343
- function parseWorkspaceMemberRoleResult(value, membershipId, role) {
8438
+ function parseWorkspaceMemberRoleResult(value, membershipId, role2) {
8344
8439
  const fail = () => {
8345
8440
  throw new Error(invalidMemberResult);
8346
8441
  };
8347
- if (!record3(value) || !uuid2(value.organizationId) || typeof value.changed !== "boolean")
8442
+ if (!record4(value) || !uuid3(value.organizationId) || typeof value.changed !== "boolean")
8348
8443
  return fail();
8349
8444
  const member = parseMember(value.member, fail);
8350
- if (member.membershipId !== membershipId || member.role !== role)
8445
+ if (member.membershipId !== membershipId || member.role !== role2)
8351
8446
  return fail();
8352
8447
  return { organizationId: value.organizationId, member, changed: value.changed };
8353
8448
  }
8354
8449
  function parseWorkspaceMemberRemovalResult(value, membershipId) {
8355
- if (!record3(value) || !uuid2(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
8450
+ if (!record4(value) || !uuid3(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
8356
8451
  throw new Error(invalidMemberResult);
8357
8452
  return { organizationId: value.organizationId, membershipId, removed: true, alreadyRemoved: value.alreadyRemoved };
8358
8453
  }
8359
8454
  function workspaceMemberFailure(value, status) {
8360
- if (!record3(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
8455
+ if (!record4(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
8361
8456
  return null;
8362
8457
  const code = value.code;
8363
8458
  return workspaceMemberFailures[code][0] === status ? code : null;
@@ -8366,14 +8461,14 @@ function parseWorkspaceMembersPage(value) {
8366
8461
  const fail = () => {
8367
8462
  throw new Error("The server returned an invalid workspace roster.");
8368
8463
  };
8369
- if (!record3(value) || !uuid2(value.organizationId) || !Array.isArray(value.members) || value.members.length > 100 || !(value.nextCursor === null || cursor(value.nextCursor)) || !value.members.length && value.nextCursor !== null)
8464
+ if (!record4(value) || !uuid3(value.organizationId) || !Array.isArray(value.members) || value.members.length > 100 || !(value.nextCursor === null || cursor(value.nextCursor)) || !value.members.length && value.nextCursor !== null)
8370
8465
  return fail();
8371
8466
  const members = value.members.map((row) => parseMember(row, fail));
8372
8467
  if (new Set(members.map((row) => row.membershipId)).size !== members.length)
8373
8468
  return fail();
8374
8469
  return { organizationId: value.organizationId, members, nextCursor: value.nextCursor };
8375
8470
  }
8376
- var record3 = (value) => !!value && typeof value === "object" && !Array.isArray(value), cursor = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,512}$/.test(value), 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), isRole = (value) => typeof value === "string" && ["owner", "admin", "member", "viewer"].includes(value), WorkspaceMemberInputError, invalidMemberResult = "The server returned an invalid workspace member result. Refresh the roster before another action.", workspaceMemberFailures;
8471
+ var record4 = (value) => !!value && typeof value === "object" && !Array.isArray(value), cursor = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,512}$/.test(value), uuid3 = (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), isRole = (value) => typeof value === "string" && ["owner", "admin", "member", "viewer"].includes(value), WorkspaceMemberInputError, invalidMemberResult = "The server returned an invalid workspace member result. Refresh the roster before another action.", workspaceMemberFailures;
8377
8472
  var init_remote_workspace = __esm(() => {
8378
8473
  WorkspaceMemberInputError = class WorkspaceMemberInputError extends Error {
8379
8474
  constructor() {
@@ -8395,6 +8490,75 @@ var init_remote_workspace = __esm(() => {
8395
8490
  };
8396
8491
  });
8397
8492
 
8493
+ // src/lib/remote-workspace-leave.ts
8494
+ function workspaceLeaveInput(context, input) {
8495
+ const target = workspaceContext(context);
8496
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).sort().join(",") !== "confirm,expectedRole" || input.confirm !== true)
8497
+ throw new WorkspaceLeaveInputError;
8498
+ const captured = workspaceMemberRemovalInput(target.membershipId, { expectedRole: input.expectedRole });
8499
+ return {
8500
+ context: target,
8501
+ input: { expectedRole: captured.body.expectedRole, confirm: true },
8502
+ body: { membershipId: target.membershipId, expectedRole: captured.body.expectedRole }
8503
+ };
8504
+ }
8505
+ function workspaceLeaveFailure(value, status) {
8506
+ if (!value || typeof value !== "object" || Array.isArray(value))
8507
+ return null;
8508
+ const code = value.code;
8509
+ return typeof code === "string" && Object.hasOwn(workspaceLeaveFailures, code) && workspaceLeaveFailures[code][0] === status ? code : null;
8510
+ }
8511
+ function parseWorkspaceLeaveResult(value, membershipId, organizationId) {
8512
+ const row = value;
8513
+ if (!row || typeof row !== "object" || Array.isArray(row) || row.membershipId !== membershipId || row.organizationId !== organizationId || row.removed !== true || row.signInRequired !== true)
8514
+ throw new RemoteWorkspaceLeaveUnconfirmedError;
8515
+ return { membershipId, organizationId, removed: true, signInRequired: true };
8516
+ }
8517
+ function workspaceLeaveProfileContext(membershipId, userId, profile) {
8518
+ const observed = profile === undefined ? undefined : workspaceContext(profile);
8519
+ const target = workspaceContext({ userId: userId ?? observed?.userId, membershipId });
8520
+ if (observed && (observed.userId !== target.userId || observed.membershipId !== target.membershipId))
8521
+ throw new WorkspaceIdentityMismatchError;
8522
+ return target;
8523
+ }
8524
+ var WorkspaceLeaveInputError, workspaceLeaveFailures, RemoteWorkspaceLeaveError, RemoteWorkspaceLeaveUnconfirmedError;
8525
+ var init_remote_workspace_leave = __esm(() => {
8526
+ init_remote_workspace_selection();
8527
+ init_remote_workspace();
8528
+ WorkspaceLeaveInputError = class WorkspaceLeaveInputError extends Error {
8529
+ constructor() {
8530
+ super("Confirm leaving the exact observed user and membership with its expected role.");
8531
+ this.name = "WorkspaceLeaveInputError";
8532
+ }
8533
+ };
8534
+ workspaceLeaveFailures = {
8535
+ INVALID_REQUEST: [400, "Provide the exact current membership and expected role."],
8536
+ ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
8537
+ INTERACTIVE_SESSION_REQUIRED: [403, "Fresh interactive sign-in is required to leave a workspace."],
8538
+ MEMBERSHIP_ROLE_CHANGED: [409, "Your role changed. Sign in and inspect the workspace before leaving."],
8539
+ LAST_OWNER_REQUIRED: [409, "The workspace must retain another active owner."],
8540
+ LAST_WORKSPACE_REQUIRED: [409, "Another available workspace is required before leaving."],
8541
+ MEMBERSHIP_BUSY: [503, "Membership is busy. Inspect the workspace before another leave action."]
8542
+ };
8543
+ RemoteWorkspaceLeaveError = class RemoteWorkspaceLeaveError extends Error {
8544
+ code;
8545
+ status;
8546
+ constructor(code) {
8547
+ super(workspaceLeaveFailures[code][1]);
8548
+ this.code = code;
8549
+ this.name = "RemoteWorkspaceLeaveError";
8550
+ this.status = workspaceLeaveFailures[code][0];
8551
+ }
8552
+ };
8553
+ RemoteWorkspaceLeaveUnconfirmedError = class RemoteWorkspaceLeaveUnconfirmedError extends Error {
8554
+ code = "WORKSPACE_LEAVE_UNCONFIRMED";
8555
+ constructor() {
8556
+ super("The leave outcome is unconfirmed. Sign in again and inspect available memberships before another action. Do not retry automatically; saved credentials are unchanged.");
8557
+ this.name = "RemoteWorkspaceLeaveUnconfirmedError";
8558
+ }
8559
+ };
8560
+ });
8561
+
8398
8562
  // src/lib/remote-account.ts
8399
8563
  function creditCount(value) {
8400
8564
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0 || value > 2147483647) {
@@ -8498,11 +8662,11 @@ function parseUpdatedProfile(value) {
8498
8662
  return { user: { id: user.id, email: user.email, displayName: user.displayName, role: user.role } };
8499
8663
  }
8500
8664
  function parseUpdatedWorkspace(value) {
8501
- const organization = isRecord4(value) && value.organization;
8502
- if (!isRecord4(organization) || !string4(organization.id) || !string4(organization.slug) || !string4(organization.name)) {
8665
+ const organization2 = isRecord4(value) && value.organization;
8666
+ if (!isRecord4(organization2) || !string4(organization2.id) || !string4(organization2.slug) || !string4(organization2.name)) {
8503
8667
  throw new Error("The server returned an invalid workspace.");
8504
8668
  }
8505
- return { organization: { id: organization.id, slug: organization.slug, name: organization.name } };
8669
+ return { organization: { id: organization2.id, slug: organization2.slug, name: organization2.name } };
8506
8670
  }
8507
8671
 
8508
8672
  // src/lib/remote-client.ts
@@ -8510,6 +8674,7 @@ var exports_remote_client = {};
8510
8674
  __export(exports_remote_client, {
8511
8675
  createRemoteSkillsClientReadOnly: () => createRemoteSkillsClientReadOnly,
8512
8676
  createRemoteSkillsClient: () => createRemoteSkillsClient,
8677
+ RemoteWorkspaceSelectionError: () => RemoteWorkspaceSelectionError,
8513
8678
  RemoteWorkspaceMemberError: () => RemoteWorkspaceMemberError,
8514
8679
  RemoteSkillsClient: () => RemoteSkillsClient,
8515
8680
  RemoteRouteUnsupportedError: () => RemoteRouteUnsupportedError,
@@ -8529,6 +8694,7 @@ class RemoteSkillsClient {
8529
8694
  return fetch(`${this.apiUrl}${path}`, {
8530
8695
  ...options,
8531
8696
  redirect: "error",
8697
+ credentials: "omit",
8532
8698
  signal: options?.signal ?? AbortSignal.timeout(15000),
8533
8699
  headers: {
8534
8700
  Authorization: `Bearer ${this.apiKey}`,
@@ -8635,6 +8801,65 @@ class RemoteSkillsClient {
8635
8801
  async getIdentity() {
8636
8802
  return (await this.requestNewRoute("/api/auth/whoami")).json();
8637
8803
  }
8804
+ async listAccountWorkspaces(expectedUserId) {
8805
+ const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
8806
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
8807
+ let identity;
8808
+ if (expected !== undefined) {
8809
+ const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
8810
+ if (!value || typeof value !== "object" || value.authMethod !== "jwt")
8811
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces", "INTERACTIVE_SESSION_REQUIRED");
8812
+ identity = parseWorkspaceIdentity(value, expected);
8813
+ }
8814
+ const result = parseAccountWorkspaces(await connection.requestWorkspaceSelection("/api/v1/account/workspaces"));
8815
+ const current = result.workspaces.find((workspace) => workspace.current);
8816
+ if (identity && (current.membershipId !== identity.user.membershipId || current.organization.id !== identity.organization.id))
8817
+ throw new WorkspaceIdentityMismatchError;
8818
+ return result;
8819
+ }
8820
+ async switchWorkspace(context) {
8821
+ const target = workspaceContext(context);
8822
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
8823
+ const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
8824
+ if (!value || typeof value !== "object" || value.authMethod !== "jwt")
8825
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
8826
+ parseWorkspaceIdentity(value, target.userId);
8827
+ const selected = parseWorkspaceSession(await connection.requestWorkspaceSelection("/api/v1/account/workspaces/switch", {
8828
+ method: "POST",
8829
+ body: JSON.stringify({ membershipId: target.membershipId })
8830
+ }), target);
8831
+ const verified = await new RemoteSkillsClient(selected.token, connection.apiUrl).requestWorkspaceSelection("/api/auth/whoami");
8832
+ if (!verified || typeof verified !== "object" || verified.authMethod !== "jwt")
8833
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
8834
+ const identity = parseWorkspaceIdentity(verified, target.userId);
8835
+ if (identity.user.membershipId !== target.membershipId || identity.organization.id !== selected.organization.id)
8836
+ throw new WorkspaceIdentityMismatchError;
8837
+ return { token: selected.token, ...identity };
8838
+ }
8839
+ async requestWorkspaceSelection(path, options) {
8840
+ let response;
8841
+ try {
8842
+ response = await this.request(path, { ...options, credentials: "omit" });
8843
+ } catch {
8844
+ throw new Error("Unable to reach the Skills workspace API.");
8845
+ }
8846
+ let value;
8847
+ try {
8848
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 1024 * 1024 : 4096)));
8849
+ } catch {
8850
+ if (response.ok)
8851
+ throw new Error(invalidWorkspaceResult);
8852
+ }
8853
+ if (!response.ok) {
8854
+ const code = workspaceSelectionFailure(value, response.status);
8855
+ if (code)
8856
+ throw new RemoteWorkspaceSelectionError(path, code);
8857
+ if (response.status === 404 || response.status === 405)
8858
+ throw new RemoteRouteUnsupportedError(path, response.status, this.apiUrl);
8859
+ throw new RemoteRequestError(path, response.status);
8860
+ }
8861
+ return value;
8862
+ }
8638
8863
  async updateProfile(input) {
8639
8864
  const body = customerNamePatch(input, "displayName");
8640
8865
  return parseUpdatedProfile(await (await this.requestNewRoute("/api/v1/account/profile", { method: "PATCH", body: JSON.stringify(body) })).json());
@@ -8687,6 +8912,30 @@ class RemoteSkillsClient {
8687
8912
  }
8688
8913
  return value;
8689
8914
  }
8915
+ async leaveWorkspace(context, input) {
8916
+ const captured = workspaceLeaveInput(context, input);
8917
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
8918
+ const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
8919
+ if (!value || typeof value !== "object" || value.authMethod !== "jwt")
8920
+ throw new RemoteWorkspaceLeaveError("INTERACTIVE_SESSION_REQUIRED");
8921
+ const identity = parseWorkspaceIdentity(value, captured.context.userId);
8922
+ if (identity.user.membershipId !== captured.context.membershipId)
8923
+ throw new WorkspaceIdentityMismatchError;
8924
+ let response, body;
8925
+ try {
8926
+ response = await connection.request("/api/v1/account/workspaces/leave", { method: "POST", body: JSON.stringify(captured.body) });
8927
+ body = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 4096)));
8928
+ } catch {
8929
+ throw new RemoteWorkspaceLeaveUnconfirmedError;
8930
+ }
8931
+ if (!response.ok) {
8932
+ const code = workspaceLeaveFailure(body, response.status);
8933
+ if (code)
8934
+ throw new RemoteWorkspaceLeaveError(code);
8935
+ throw new RemoteWorkspaceLeaveUnconfirmedError;
8936
+ }
8937
+ return parseWorkspaceLeaveResult(body, captured.context.membershipId, identity.organization.id);
8938
+ }
8690
8939
  async listApiKeys() {
8691
8940
  return this.arrayResponse("/api/auth/keys");
8692
8941
  }
@@ -8937,13 +9186,13 @@ class RemoteSkillsClient {
8937
9186
  return normalizeUpdatedSincePage(await response.json());
8938
9187
  }
8939
9188
  }
8940
- function requireOptionalString(record4, field) {
8941
- if (record4[field] === undefined)
9189
+ function requireOptionalString(record5, field) {
9190
+ if (record5[field] === undefined)
8942
9191
  return;
8943
- if (typeof record4[field] !== "string") {
9192
+ if (typeof record5[field] !== "string") {
8944
9193
  throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
8945
9194
  }
8946
- return record4[field];
9195
+ return record5[field];
8947
9196
  }
8948
9197
  function isVersionRecord(value) {
8949
9198
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -8965,19 +9214,19 @@ function normalizePin(entry) {
8965
9214
  if (!entry || typeof entry !== "object") {
8966
9215
  throw new Error("Remote pin payload did not match the expected contract (expected an object)");
8967
9216
  }
8968
- const record4 = entry;
8969
- const slug = typeof record4.slug === "string" && record4.slug.trim() ? record4.slug.trim() : undefined;
9217
+ const record5 = entry;
9218
+ const slug = typeof record5.slug === "string" && record5.slug.trim() ? record5.slug.trim() : undefined;
8970
9219
  if (!slug) {
8971
9220
  throw new Error("Remote pin payload did not match the expected contract (missing slug)");
8972
9221
  }
8973
9222
  let metadata;
8974
- if (record4.metadata !== undefined) {
8975
- if (!record4.metadata || typeof record4.metadata !== "object" || Array.isArray(record4.metadata)) {
9223
+ if (record5.metadata !== undefined) {
9224
+ if (!record5.metadata || typeof record5.metadata !== "object" || Array.isArray(record5.metadata)) {
8976
9225
  throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
8977
9226
  }
8978
- metadata = record4.metadata;
9227
+ metadata = record5.metadata;
8979
9228
  }
8980
- const pinnedAt = requireOptionalString(record4, "pinnedAt");
9229
+ const pinnedAt = requireOptionalString(record5, "pinnedAt");
8981
9230
  return {
8982
9231
  slug,
8983
9232
  ...pinnedAt !== undefined ? { pinnedAt } : {},
@@ -8994,16 +9243,16 @@ function normalizeSkillSummary(entry) {
8994
9243
  if (!entry || typeof entry !== "object") {
8995
9244
  throw new Error("Remote skill payload did not match the expected contract (expected an object)");
8996
9245
  }
8997
- const record4 = entry;
8998
- const slug = typeof record4.slug === "string" && record4.slug.trim() ? record4.slug.trim() : undefined;
9246
+ const record5 = entry;
9247
+ const slug = typeof record5.slug === "string" && record5.slug.trim() ? record5.slug.trim() : undefined;
8999
9248
  if (!slug) {
9000
9249
  throw new Error("Remote skill payload did not match the expected contract (missing slug)");
9001
9250
  }
9002
9251
  return {
9003
9252
  slug,
9004
- ...requireOptionalString(record4, "name") !== undefined ? { name: requireOptionalString(record4, "name") } : {},
9005
- ...requireOptionalString(record4, "version") !== undefined ? { version: requireOptionalString(record4, "version") } : {},
9006
- ...requireOptionalString(record4, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record4, "updatedAt") } : {}
9253
+ ...requireOptionalString(record5, "name") !== undefined ? { name: requireOptionalString(record5, "name") } : {},
9254
+ ...requireOptionalString(record5, "version") !== undefined ? { version: requireOptionalString(record5, "version") } : {},
9255
+ ...requireOptionalString(record5, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record5, "updatedAt") } : {}
9007
9256
  };
9008
9257
  }
9009
9258
  function normalizeSkillSummaryList(payload) {
@@ -9056,12 +9305,12 @@ function normalizeUpdatedSincePage(payload) {
9056
9305
  if (!payload || typeof payload !== "object") {
9057
9306
  throw new Error("Updated-since payload did not match the expected contract (expected an object)");
9058
9307
  }
9059
- const record4 = payload;
9060
- if (!Array.isArray(record4.skills)) {
9308
+ const record5 = payload;
9309
+ if (!Array.isArray(record5.skills)) {
9061
9310
  throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
9062
9311
  }
9063
- const skills = record4.skills.map(normalizeSkillSummary);
9064
- const nextCursor = record4.nextCursor === undefined || record4.nextCursor === null ? null : record4.nextCursor;
9312
+ const skills = record5.skills.map(normalizeSkillSummary);
9313
+ const nextCursor = record5.nextCursor === undefined || record5.nextCursor === null ? null : record5.nextCursor;
9065
9314
  if (nextCursor !== null && typeof nextCursor !== "string") {
9066
9315
  throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
9067
9316
  }
@@ -9074,8 +9323,10 @@ async function createRemoteSkillsClient(env = process.env) {
9074
9323
  function createRemoteSkillsClientReadOnly(env = process.env) {
9075
9324
  return createRemoteSkillsClient(env);
9076
9325
  }
9077
- var RemoteRouteUnsupportedError, RemoteRequestError, RemoteWorkspaceMemberError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
9326
+ var RemoteRouteUnsupportedError, RemoteRequestError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
9078
9327
  var init_remote_client = __esm(() => {
9328
+ init_remote_workspace_leave();
9329
+ init_remote_workspace_selection();
9079
9330
  init_remote_workspace();
9080
9331
  init_remote_workspace();
9081
9332
  init_auth_store();
@@ -9113,6 +9364,15 @@ var init_remote_client = __esm(() => {
9113
9364
  this.message = workspaceMemberFailures[code][1];
9114
9365
  }
9115
9366
  };
9367
+ RemoteWorkspaceSelectionError = class RemoteWorkspaceSelectionError extends RemoteRequestError {
9368
+ code;
9369
+ constructor(path, code) {
9370
+ super(path, workspaceSelectionFailures[code][0]);
9371
+ this.code = code;
9372
+ this.name = "RemoteWorkspaceSelectionError";
9373
+ this.message = workspaceSelectionFailures[code][1];
9374
+ }
9375
+ };
9116
9376
  RemoteCapabilityUnavailableError = class RemoteCapabilityUnavailableError extends RemoteRequestError {
9117
9377
  code = "SUBSCRIPTION_CHECKOUT_UNAVAILABLE";
9118
9378
  constructor() {
@@ -14639,7 +14899,7 @@ class StdioServerTransport {
14639
14899
  // package.json
14640
14900
  var package_default = {
14641
14901
  name: "@hasna/skills",
14642
- version: "0.5.0",
14902
+ version: "0.5.2",
14643
14903
  description: "Skills library for AI coding agents",
14644
14904
  type: "module",
14645
14905
  bin: {
@@ -27945,7 +28205,7 @@ function registerDiscoveryTools(server) {
27945
28205
 
27946
28206
  // src/mcp/operation-tools.ts
27947
28207
  import { existsSync as existsSync14, readdirSync as readdirSync8, statSync as statSync9 } from "fs";
27948
- import { join as join14 } from "path";
28208
+ import { join as join15 } from "path";
27949
28209
 
27950
28210
  // src/lib/credential-state.ts
27951
28211
  init_auth_store();
@@ -27999,19 +28259,19 @@ function describeCredentialState() {
27999
28259
  // src/lib/run-state.ts
28000
28260
  import { createHash as createHash2, randomBytes } from "crypto";
28001
28261
  import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync12, readdirSync as readdirSync7, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
28002
- import { extname, join as join13, relative as relative2 } from "path";
28262
+ import { extname, join as join14, relative as relative2 } from "path";
28003
28263
  function createSkillRun(params, targetDir = process.cwd()) {
28004
28264
  const now = new Date;
28005
28265
  const id = createRunId(now);
28006
28266
  const day = now.toISOString().slice(0, 10);
28007
28267
  const skillName = normalizeSkillName(params.skill);
28008
28268
  const root = getProjectStateDir(targetDir);
28009
- const runDir = join13(root, "runs", day, id);
28010
- const logsDir = join13(runDir, "logs");
28011
- const exportDir = join13(root, "exports", skillName, id);
28269
+ const runDir = join14(root, "runs", day, id);
28270
+ const logsDir = join14(runDir, "logs");
28271
+ const exportDir = join14(root, "exports", skillName, id);
28012
28272
  mkdirSync6(logsDir, { recursive: true });
28013
28273
  mkdirSync6(exportDir, { recursive: true });
28014
- mkdirSync6(join13(root, "tmp"), { recursive: true });
28274
+ mkdirSync6(join14(root, "tmp"), { recursive: true });
28015
28275
  const record3 = {
28016
28276
  id,
28017
28277
  skill: skillName,
@@ -28063,22 +28323,22 @@ function updateSkillRun(context, patch) {
28063
28323
  return context.record;
28064
28324
  }
28065
28325
  function writeRunLogs(context, stdout = "", stderr = "") {
28066
- writeFileSync6(join13(context.logsDir, "stdout.log"), stdout);
28067
- writeFileSync6(join13(context.logsDir, "stderr.log"), stderr);
28326
+ writeFileSync6(join14(context.logsDir, "stdout.log"), stdout);
28327
+ writeFileSync6(join14(context.logsDir, "stderr.log"), stderr);
28068
28328
  }
28069
28329
  function appendRunEvent(context, event, data = {}) {
28070
28330
  const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
28071
28331
  `;
28072
- const path = join13(context.runDir, "events.ndjson");
28332
+ const path = join14(context.runDir, "events.ndjson");
28073
28333
  const previous = existsSync13(path) ? readFileSync12(path, "utf-8") : "";
28074
28334
  writeFileSync6(path, previous + line);
28075
28335
  }
28076
28336
  function findSkillRun(runId, targetDir = process.cwd()) {
28077
- const runsRoot = join13(getProjectStateDir(targetDir), "runs");
28337
+ const runsRoot = join14(getProjectStateDir(targetDir), "runs");
28078
28338
  if (!existsSync13(runsRoot))
28079
28339
  return null;
28080
28340
  for (const day of readdirSync7(runsRoot)) {
28081
- const record3 = readRunRecord(join13(runsRoot, day, runId));
28341
+ const record3 = readRunRecord(join14(runsRoot, day, runId));
28082
28342
  if (record3)
28083
28343
  return record3;
28084
28344
  }
@@ -28095,11 +28355,11 @@ function skillRunEnv(context) {
28095
28355
  };
28096
28356
  }
28097
28357
  function writeRunRecord(context) {
28098
- writeFileSync6(join13(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
28358
+ writeFileSync6(join14(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
28099
28359
  `);
28100
28360
  }
28101
28361
  function writeArtifactsManifest(context, artifacts) {
28102
- writeFileSync6(join13(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
28362
+ writeFileSync6(join14(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
28103
28363
  `);
28104
28364
  }
28105
28365
  function collectRunArtifacts(context) {
@@ -28119,7 +28379,7 @@ function collectRunArtifacts(context) {
28119
28379
  return artifacts.sort((a, b) => a.path.localeCompare(b.path));
28120
28380
  }
28121
28381
  function readRunRecord(runDir) {
28122
- const path = join13(runDir, "run.json");
28382
+ const path = join14(runDir, "run.json");
28123
28383
  if (!existsSync13(path))
28124
28384
  return null;
28125
28385
  try {
@@ -28131,7 +28391,7 @@ function readRunRecord(runDir) {
28131
28391
  function walkFiles(dir) {
28132
28392
  const files = [];
28133
28393
  for (const entry of readdirSync7(dir)) {
28134
- const full = join13(dir, entry);
28394
+ const full = join14(dir, entry);
28135
28395
  if (statSync8(full).isDirectory())
28136
28396
  files.push(...walkFiles(full));
28137
28397
  else
@@ -28638,7 +28898,7 @@ function registerOperationTools(server) {
28638
28898
  if (exists) {
28639
28899
  try {
28640
28900
  skillCount = readdirSync8(agentSkillsPath).filter((f) => {
28641
- const full = join14(agentSkillsPath, f);
28901
+ const full = join15(agentSkillsPath, f);
28642
28902
  return !f.startsWith(".") && statSync9(full).isDirectory();
28643
28903
  }).length;
28644
28904
  } catch {}
@@ -28686,14 +28946,14 @@ function compactRunToolPayload(payload, detailHint) {
28686
28946
 
28687
28947
  // src/lib/feedback.ts
28688
28948
  import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync7 } from "fs";
28689
- import { dirname as dirname5, join as join15 } from "path";
28949
+ import { dirname as dirname6, join as join16 } from "path";
28690
28950
  import { Database } from "bun:sqlite";
28691
28951
  function getFeedbackDbPath() {
28692
- return join15(getDataDir(), "skills.db");
28952
+ return join16(getDataDir(), "skills.db");
28693
28953
  }
28694
28954
  function getFeedbackDb() {
28695
28955
  const dbPath = getFeedbackDbPath();
28696
- const dir = dirname5(dbPath);
28956
+ const dir = dirname6(dbPath);
28697
28957
  if (!existsSync15(dir))
28698
28958
  mkdirSync7(dir, { recursive: true });
28699
28959
  const db = new Database(dbPath);
@@ -28721,8 +28981,8 @@ function saveFeedback(input) {
28721
28981
  throw new Error("Feedback message is required");
28722
28982
  const category = input.category ?? "general";
28723
28983
  if (isApiMode()) {
28724
- const path = join15(getDataDir(), "feedback.jsonl");
28725
- const dir = dirname5(path);
28984
+ const path = join16(getDataDir(), "feedback.jsonl");
28985
+ const dir = dirname6(path);
28726
28986
  if (!existsSync15(dir))
28727
28987
  mkdirSync7(dir, { recursive: true });
28728
28988
  appendFileSync(path, JSON.stringify({ message, category, email: input.email ?? null, agent: input.agent ?? null, version: input.version ?? null, createdAt: new Date().toISOString() }) + `
@@ -28870,9 +29130,9 @@ function registerResourceMetaTools(server) {
28870
29130
 
28871
29131
  // src/lib/scheduler.ts
28872
29132
  import { existsSync as existsSync16, readFileSync as readFileSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "fs";
28873
- import { join as join16 } from "path";
29133
+ import { join as join17 } from "path";
28874
29134
  function getSchedulesPath(targetDir = process.cwd()) {
28875
- return join16(targetDir, ".skills", "schedules.json");
29135
+ return join17(targetDir, ".skills", "schedules.json");
28876
29136
  }
28877
29137
  function loadSchedules(targetDir = process.cwd()) {
28878
29138
  const path = getSchedulesPath(targetDir);
@@ -28885,7 +29145,7 @@ function loadSchedules(targetDir = process.cwd()) {
28885
29145
  }
28886
29146
  function saveSchedules(data, targetDir = process.cwd()) {
28887
29147
  const path = getSchedulesPath(targetDir);
28888
- const dir = join16(targetDir, ".skills");
29148
+ const dir = join17(targetDir, ".skills");
28889
29149
  if (!existsSync16(dir))
28890
29150
  mkdirSync8(dir, { recursive: true });
28891
29151
  writeFileSync7(path, JSON.stringify(data, null, 2));
@@ -29155,7 +29415,7 @@ import {
29155
29415
  statSync as statSync10,
29156
29416
  writeFileSync as writeFileSync8
29157
29417
  } from "fs";
29158
- import { dirname as dirname6, join as join17, normalize as normalize3, relative as relative3, sep as sep2 } from "path";
29418
+ import { dirname as dirname7, join as join18, normalize as normalize3, relative as relative3, sep as sep2 } from "path";
29159
29419
  var SKILLS_STORAGE_TABLES = [
29160
29420
  "skills_sync_records",
29161
29421
  "skills_sync_cursors"
@@ -29231,7 +29491,7 @@ function getSkillsNativeStorageStatus(options = {}) {
29231
29491
  local: {
29232
29492
  dataDir: getDataDir(),
29233
29493
  projectStateDir: getProjectStateDir(targetDir),
29234
- feedbackDbPath: join17(getDataDir(), "skills.db")
29494
+ feedbackDbPath: join18(getDataDir(), "skills.db")
29235
29495
  },
29236
29496
  remote: {
29237
29497
  databaseConfigured: Boolean(config2.databaseUrl),
@@ -29335,7 +29595,7 @@ function parsePositiveInteger(value) {
29335
29595
  function walkFiles2(dir) {
29336
29596
  const files = [];
29337
29597
  for (const entry of readdirSync9(dir)) {
29338
- const full = join17(dir, entry);
29598
+ const full = join18(dir, entry);
29339
29599
  const stats = statSync10(full);
29340
29600
  if (stats.isDirectory())
29341
29601
  files.push(...walkFiles2(full));
@@ -29391,6 +29651,9 @@ function registerStorageTools(server) {
29391
29651
  }
29392
29652
 
29393
29653
  // src/lib/remote-auth.ts
29654
+ init_remote_workspace_leave();
29655
+ init_remote_workspace_selection();
29656
+ init_remote_files();
29394
29657
  init_remote_workspace();
29395
29658
  init_remote_workspace();
29396
29659
  init_remote_client();
@@ -29431,13 +29694,13 @@ async function requestAuthApi(instance, path, options) {
29431
29694
  apiUrl: safeUrl
29432
29695
  });
29433
29696
  }
29434
- const text = await res.text();
29435
- const body = text ? parseJsonBody(text) : {};
29697
+ const text2 = await res.text();
29698
+ const body = text2 ? parseJsonBody(text2) : {};
29436
29699
  if (!res.ok) {
29437
- const record4 = isRecord5(body) ? body : {};
29438
- const detail = typeof record4.detail === "string" ? record4.detail : undefined;
29439
- const error2 = typeof record4.error === "string" ? record4.error : undefined;
29440
- const code = typeof record4.code === "string" ? record4.code : undefined;
29700
+ const record5 = isRecord5(body) ? body : {};
29701
+ const detail = typeof record5.detail === "string" ? record5.detail : undefined;
29702
+ const error2 = typeof record5.error === "string" ? record5.error : undefined;
29703
+ const code = typeof record5.code === "string" ? record5.code : undefined;
29441
29704
  throw new HostedApiError(detail || error2 || `${res.status} ${res.statusText}`, {
29442
29705
  status: res.status,
29443
29706
  code,
@@ -29448,15 +29711,15 @@ async function requestAuthApi(instance, path, options) {
29448
29711
  }
29449
29712
  return body;
29450
29713
  }
29451
- function parseJsonBody(text) {
29714
+ function parseJsonBody(text2) {
29452
29715
  try {
29453
- return JSON.parse(text);
29716
+ return JSON.parse(text2);
29454
29717
  } catch {
29455
- return { detail: condenseErrorBody(text) };
29718
+ return { detail: condenseErrorBody(text2) };
29456
29719
  }
29457
29720
  }
29458
- function condenseErrorBody(text) {
29459
- const stripped = /<[a-z!/]/i.test(text) ? text.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text;
29721
+ function condenseErrorBody(text2) {
29722
+ const stripped = /<[a-z!/]/i.test(text2) ? text2.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text2;
29460
29723
  const collapsed = stripped.replace(/\s+/g, " ").trim();
29461
29724
  if (collapsed.length <= MAX_ERROR_DETAIL_LENGTH)
29462
29725
  return collapsed;
@@ -29483,7 +29746,12 @@ class RemoteSkillsAuthClient {
29483
29746
  pollDevice(deviceCode) {
29484
29747
  return this.request("/api/auth/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) });
29485
29748
  }
29486
- async sessionClient(email2, code) {
29749
+ async sessionClient(email2, code, context) {
29750
+ if (context !== undefined) {
29751
+ const target = workspaceContext(context), apiOrigin2 = this.apiOrigin;
29752
+ const session = await this.switchWorkspace(email2, code, target);
29753
+ return new RemoteSkillsClient(session.token, apiOrigin2);
29754
+ }
29487
29755
  const apiOrigin = this.apiOrigin;
29488
29756
  if (!email2.includes("@") || !/^\d{6}$/.test(code))
29489
29757
  throw new Error("Fresh email and six-digit verification code are required to manage this account");
@@ -29492,34 +29760,80 @@ class RemoteSkillsAuthClient {
29492
29760
  throw new Error("The server did not return an authorized account session");
29493
29761
  return new RemoteSkillsClient(login.token, apiOrigin);
29494
29762
  }
29495
- async createApiKey(email2, code, name, scopes) {
29496
- return (await this.sessionClient(email2, code)).createApiKey(name, scopes);
29763
+ async listAccountWorkspaces(email2, code, expectedUserId) {
29764
+ const login = await this.workspaceLogin(email2, code, expectedUserId);
29765
+ const result = await new RemoteSkillsClient(login.token, login.apiOrigin).listAccountWorkspaces(login.userId);
29766
+ return { userId: login.userId, ...result };
29497
29767
  }
29498
- async listApiKeys(email2, code) {
29499
- return (await this.sessionClient(email2, code)).listApiKeys();
29768
+ async switchWorkspace(email2, code, context) {
29769
+ const target = workspaceContext(context);
29770
+ const login = await this.workspaceLogin(email2, code, target.userId);
29771
+ return new RemoteSkillsClient(login.token, login.apiOrigin).switchWorkspace(target);
29500
29772
  }
29501
- async revokeApiKey(email2, code, keyId) {
29502
- return (await this.sessionClient(email2, code)).revokeApiKey(keyId);
29773
+ async workspaceLogin(email2, code, expectedUserId) {
29774
+ const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
29775
+ const apiOrigin = this.apiOrigin;
29776
+ if (typeof email2 !== "string" || !email2.includes("@") || typeof code !== "string" || !/^\d{6}$/.test(code))
29777
+ throw new Error("Fresh email and six-digit verification code are required to manage this account");
29778
+ let response;
29779
+ try {
29780
+ response = await fetch(`${apiOrigin}/api/auth/verify`, {
29781
+ method: "POST",
29782
+ redirect: "error",
29783
+ credentials: "omit",
29784
+ signal: AbortSignal.timeout(15000),
29785
+ headers: { "Content-Type": "application/json" },
29786
+ body: JSON.stringify({ email: email2, code })
29787
+ });
29788
+ } catch {
29789
+ throw new HostedApiError("Unable to verify the Skills account.");
29790
+ }
29791
+ if (!response.ok) {
29792
+ response.body?.cancel().catch(() => {});
29793
+ throw new HostedApiError("Unable to verify the Skills account.", { status: response.status });
29794
+ }
29795
+ let value;
29796
+ try {
29797
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 64 * 1024)));
29798
+ } catch {
29799
+ throw new HostedApiError("The server returned an invalid account verification result.");
29800
+ }
29801
+ return { ...parseWorkspaceLogin(value, expected), apiOrigin };
29802
+ }
29803
+ async createApiKey(email2, code, name, scopes, context) {
29804
+ const capturedScopes = scopes === undefined ? undefined : [...scopes];
29805
+ return (await this.sessionClient(email2, code, context)).createApiKey(name, capturedScopes);
29806
+ }
29807
+ async listApiKeys(email2, code, context) {
29808
+ return (await this.sessionClient(email2, code, context)).listApiKeys();
29809
+ }
29810
+ async revokeApiKey(email2, code, keyId, context) {
29811
+ return (await this.sessionClient(email2, code, context)).revokeApiKey(keyId);
29503
29812
  }
29504
- async updateProfile(email2, code, input) {
29505
- customerNamePatch(input, "displayName");
29506
- return (await this.sessionClient(email2, code)).updateProfile(input);
29813
+ async updateProfile(email2, code, input, context) {
29814
+ const body = customerNamePatch(input, "displayName");
29815
+ return (await this.sessionClient(email2, code, context)).updateProfile({ displayName: body.displayName });
29507
29816
  }
29508
- async updateCurrentWorkspace(email2, code, input) {
29509
- customerNamePatch(input, "name");
29510
- return (await this.sessionClient(email2, code)).updateCurrentWorkspace(input);
29817
+ async updateCurrentWorkspace(email2, code, input, context) {
29818
+ const body = customerNamePatch(input, "name");
29819
+ return (await this.sessionClient(email2, code, context)).updateCurrentWorkspace({ name: body.name });
29511
29820
  }
29512
- async listWorkspaceMembers(email2, code, options = {}) {
29821
+ async listWorkspaceMembers(email2, code, options = {}, context) {
29513
29822
  workspaceMembersQuery(options);
29514
- return (await this.sessionClient(email2, code)).listWorkspaceMembers(options);
29823
+ const captured = { ...options };
29824
+ return (await this.sessionClient(email2, code, context)).listWorkspaceMembers(captured);
29515
29825
  }
29516
- async setWorkspaceMemberRole(email2, code, membershipId, input) {
29826
+ async setWorkspaceMemberRole(email2, code, membershipId, input, context) {
29517
29827
  const captured = workspaceMemberRoleInput(membershipId, input);
29518
- return (await this.sessionClient(email2, code)).setWorkspaceMemberRole(captured.membershipId, captured.body);
29828
+ return (await this.sessionClient(email2, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
29519
29829
  }
29520
- async removeWorkspaceMember(email2, code, membershipId, input) {
29830
+ async leaveWorkspace(email2, code, context, input) {
29831
+ const captured = workspaceLeaveInput(context, input);
29832
+ return (await this.sessionClient(email2, code, captured.context)).leaveWorkspace(captured.context, captured.input);
29833
+ }
29834
+ async removeWorkspaceMember(email2, code, membershipId, input, context) {
29521
29835
  const captured = workspaceMemberRemovalInput(membershipId, input);
29522
- return (await this.sessionClient(email2, code)).removeWorkspaceMember(captured.membershipId, captured.body);
29836
+ return (await this.sessionClient(email2, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
29523
29837
  }
29524
29838
  request(path, options) {
29525
29839
  if (!["/api/auth/login", "/api/auth/verify", "/api/auth/device/start", "/api/auth/device/token", "/api/auth/keys", "/api/auth/whoami"].includes(path))
@@ -29528,9 +29842,89 @@ class RemoteSkillsAuthClient {
29528
29842
  }
29529
29843
  }
29530
29844
 
29531
- // src/mcp/remote-customer-tools.ts
29845
+ // src/lib/workspace-profile.ts
29846
+ import { constants as constants2, closeSync as closeSync3, fstatSync as fstatSync3, lstatSync as lstatSync4, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync2, openSync as openSync3, readFileSync as readFileSync15, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync9 } from "fs";
29532
29847
  init_auth_store();
29848
+ init_fleet_credentials();
29849
+ init_instance_credentials();
29533
29850
  init_remote_client();
29851
+ init_remote_workspace_selection();
29852
+
29853
+ class WorkspaceProfileError extends Error {
29854
+ }
29855
+ var fail = (message) => {
29856
+ throw new WorkspaceProfileError(message);
29857
+ };
29858
+ function stat(path) {
29859
+ try {
29860
+ return lstatSync4(path);
29861
+ } catch (error2) {
29862
+ if (error2.code === "ENOENT")
29863
+ return null;
29864
+ throw error2;
29865
+ }
29866
+ }
29867
+ function safeText(file) {
29868
+ if (stat(file) === null)
29869
+ return null;
29870
+ const fd = openSync3(file, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
29871
+ try {
29872
+ const s = fstatSync3(fd);
29873
+ if (!s.isFile() || s.size > 65536 || ![256, 384].includes(s.mode & 4095) || process.getuid && s.uid !== process.getuid())
29874
+ return fail("The selected profile must use bounded owner-only regular files.");
29875
+ return readFileSync15(fd, "utf8");
29876
+ } finally {
29877
+ closeSync3(fd);
29878
+ }
29879
+ }
29880
+ function checkIdentityMetadata(file, identity) {
29881
+ const text2 = safeText(file);
29882
+ if (text2 === null)
29883
+ return;
29884
+ let value;
29885
+ try {
29886
+ value = JSON.parse(text2);
29887
+ } catch {
29888
+ return fail("The profile identity metadata is invalid. Sign in again before managing this workspace.");
29889
+ }
29890
+ if (!value || typeof value !== "object" || Array.isArray(value))
29891
+ return fail("The profile identity metadata is invalid.");
29892
+ for (const [key, expected] of Object.entries({ userId: identity.user.id, orgId: identity.organization.id })) {
29893
+ if (value[key] !== undefined && value[key] !== expected)
29894
+ return fail("The profile identity metadata does not match its authenticated key. Sign in again before managing this workspace.");
29895
+ }
29896
+ }
29897
+ async function keyIdentity(key, origin) {
29898
+ const value = await new RemoteSkillsClient(key, origin).getIdentity();
29899
+ if (value.authMethod !== "api_key")
29900
+ return fail("The selected credential is not a workspace API key.");
29901
+ const user = value.user;
29902
+ return parseWorkspaceIdentity(value, workspaceExpectedUserId(user?.id));
29903
+ }
29904
+ function prepareProfileWorkspace(action, source = process.env) {
29905
+ const env = { ...source }, origin = getApiUrl(action, env), profile = selectedSkillsProfile(env);
29906
+ const unchanged2 = captureSkillsCredentialFiles(skillsProfileCredentialFiles(env));
29907
+ return { origin, async resolve() {
29908
+ unchanged2();
29909
+ if (!profile)
29910
+ return { origin, context: undefined, unchanged: unchanged2 };
29911
+ const connection = await resolveSkillsConnection(env);
29912
+ if (!connection || connection.apiOrigin !== origin)
29913
+ return fail("The selected profile has no usable credential for this server.");
29914
+ const identity = await keyIdentity(connection.apiKey, origin);
29915
+ checkIdentityMetadata(getIdentityFilePath(env), identity);
29916
+ unchanged2();
29917
+ const context = { userId: identity.user.id, membershipId: identity.user.membershipId };
29918
+ return { origin, context, unchanged: unchanged2 };
29919
+ } };
29920
+ }
29921
+ async function captureProfileWorkspace(action, source = process.env) {
29922
+ return prepareProfileWorkspace(action, source).resolve();
29923
+ }
29924
+
29925
+ // src/mcp/remote-customer-tools.ts
29926
+ init_remote_client();
29927
+ init_remote_workspace_leave();
29534
29928
  function registerRemoteCustomerTools(server) {
29535
29929
  const memberRole = exports_external.enum(["owner", "admin", "member", "viewer"]);
29536
29930
  const memberInput = {
@@ -29539,13 +29933,25 @@ function registerRemoteCustomerTools(server) {
29539
29933
  email: exports_external.string().email(),
29540
29934
  code: exports_external.string().regex(/^\d{6}$/)
29541
29935
  };
29936
+ server.registerTool("leave_workspace", {
29937
+ title: "Leave Current Workspace",
29938
+ description: "Leave exactly the observed user and membership with confirm=true and fresh verification. The server enforces last-owner and last-workspace safeguards. Sign in again afterwards; no automatic retry or saved-profile deletion.",
29939
+ annotations: { destructiveHint: true, idempotentHint: false, readOnlyHint: false },
29940
+ inputSchema: exports_external.object({ ...memberInput, userId: memberInput.membershipId, confirm: exports_external.literal(true) }).strict()
29941
+ }, async ({ membershipId, userId, expectedRole, email: email2, code, confirm }) => {
29942
+ try {
29943
+ return mcpJson(await freshAccount("Leave workspace", (client, context) => client.leaveWorkspace(email2, code, workspaceLeaveProfileContext(membershipId, userId, context), { expectedRole, confirm })));
29944
+ } catch (error2) {
29945
+ return error2 instanceof RemoteWorkspaceLeaveError || error2 instanceof RemoteWorkspaceLeaveUnconfirmedError ? mcpError(error2.code, error2.message) : mcpError("WORKSPACE_LEAVE_UNCONFIRMED", "Leaving could not be confirmed. Check the selected profile and exact membership, sign in again and inspect available workspaces before another action. Do not retry automatically; saved credentials are unchanged.");
29946
+ }
29947
+ });
29542
29948
  server.registerTool("set_workspace_member_role", {
29543
29949
  title: "Set Current Workspace Member Role",
29544
29950
  description: "Change exactly this membership incarnation with its observed expectedRole and fresh verification. The server enforces owner/admin policy. No automatic refresh or retry; saved credentials stay unchanged.",
29545
29951
  inputSchema: exports_external.object({ ...memberInput, role: memberRole }).strict()
29546
- }, async ({ membershipId, role, expectedRole, email: email2, code }) => {
29952
+ }, async ({ membershipId, role: role2, expectedRole, email: email2, code }) => {
29547
29953
  try {
29548
- return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Set workspace member role")).setWorkspaceMemberRole(email2, code, membershipId, { role, expectedRole }));
29954
+ return mcpJson(await freshAccount("Set workspace member role", (client, context) => client.setWorkspaceMemberRole(email2, code, membershipId, { role: role2, expectedRole }, context)));
29549
29955
  } catch (error2) {
29550
29956
  return memberError(error2);
29551
29957
  }
@@ -29556,7 +29962,7 @@ function registerRemoteCustomerTools(server) {
29556
29962
  inputSchema: exports_external.object(memberInput).strict()
29557
29963
  }, async ({ membershipId, expectedRole, email: email2, code }) => {
29558
29964
  try {
29559
- return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Remove workspace member")).removeWorkspaceMember(email2, code, membershipId, { expectedRole }));
29965
+ return mcpJson(await freshAccount("Remove workspace member", (client, context) => client.removeWorkspaceMember(email2, code, membershipId, { expectedRole }, context)));
29560
29966
  } catch (error2) {
29561
29967
  return memberError(error2);
29562
29968
  }
@@ -29572,7 +29978,7 @@ function registerRemoteCustomerTools(server) {
29572
29978
  }).strict()
29573
29979
  }, async ({ email: email2, code, limit, cursor: cursor2 }) => {
29574
29980
  try {
29575
- return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("List workspace members")).listWorkspaceMembers(email2, code, { limit, cursor: cursor2 }));
29981
+ return mcpJson(await freshAccount("List workspace members", (client, context) => client.listWorkspaceMembers(email2, code, { limit, cursor: cursor2 }, context)));
29576
29982
  } catch {
29577
29983
  return mcpError("WORKSPACE_MEMBERS_FAILED", "Unable to list workspace members. Check the selected server, owner/admin permissions, pagination and fresh verification code.");
29578
29984
  }
@@ -29584,8 +29990,7 @@ function registerRemoteCustomerTools(server) {
29584
29990
  inputSchema: exports_external.object({ name: exports_external.string().min(1), email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }).strict()
29585
29991
  }, async ({ name, email: email2, code }) => {
29586
29992
  try {
29587
- const client = new RemoteSkillsAuthClient(getApiUrl("Update customer name"));
29588
- return mcpJson(kind === "profile" ? await client.updateProfile(email2, code, { displayName: name }) : await client.updateCurrentWorkspace(email2, code, { name }));
29993
+ return mcpJson(await freshAccount("Update customer name", async (client, context) => kind === "profile" ? client.updateProfile(email2, code, { displayName: name }, context) : client.updateCurrentWorkspace(email2, code, { name }, context)));
29589
29994
  } catch {
29590
29995
  return mcpError("NAME_UPDATE_FAILED", "Unable to update the name. Check the selected server, name, permissions and fresh verification code.");
29591
29996
  }
@@ -29607,9 +30012,9 @@ function registerRemoteCustomerTools(server) {
29607
30012
  inputSchema: { email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }
29608
30013
  }, async ({ email: email2, code }) => {
29609
30014
  try {
29610
- return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("List API keys")).listApiKeys(email2, code));
29611
- } catch (error2) {
29612
- return mcpError("KEY_LIST_FAILED", error2.message);
30015
+ return mcpJson(await freshAccount("List API keys", (client, context) => client.listApiKeys(email2, code, context)));
30016
+ } catch {
30017
+ return mcpError("KEY_LIST_FAILED", "Unable to list API keys. Check the selected profile, server, account and fresh verification code.");
29613
30018
  }
29614
30019
  });
29615
30020
  server.registerTool("revoke_api_key", {
@@ -29618,9 +30023,9 @@ function registerRemoteCustomerTools(server) {
29618
30023
  inputSchema: { key_id: exports_external.string().min(1), email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }
29619
30024
  }, async ({ key_id, email: email2, code }) => {
29620
30025
  try {
29621
- return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Revoke API key")).revokeApiKey(email2, code, key_id));
29622
- } catch (error2) {
29623
- return mcpError("KEY_REVOKE_FAILED", error2.message);
30026
+ return mcpJson(await freshAccount("Revoke API key", (client, context) => client.revokeApiKey(email2, code, key_id, context)));
30027
+ } catch {
30028
+ return mcpError("KEY_REVOKE_FAILED", "Unable to revoke this API key. Check the selected profile, key, account and fresh verification code.");
29624
30029
  }
29625
30030
  });
29626
30031
  server.registerTool("create_api_key", {
@@ -29628,10 +30033,11 @@ function registerRemoteCustomerTools(server) {
29628
30033
  description: "Create an API key using fresh email OTP reauthentication; returns its secret once. A stored API key cannot grant this authority.",
29629
30034
  inputSchema: { name: exports_external.string().min(1), email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/), scopes: exports_external.array(exports_external.string()).optional() }
29630
30035
  }, async ({ name, email: email2, code, scopes }) => {
30036
+ const capturedScopes = scopes === undefined ? undefined : [...scopes];
29631
30037
  try {
29632
- return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Create API key")).createApiKey(email2, code, name, scopes));
29633
- } catch (error2) {
29634
- return mcpError("KEY_CREATION_FAILED", error2.message);
30038
+ return mcpJson(await freshAccount("Create API key", (client, context) => client.createApiKey(email2, code, name, capturedScopes, context)));
30039
+ } catch {
30040
+ return mcpError("KEY_CREATION_FAILED", "API key creation could not be confirmed. Check the selected profile and workspace keys before retrying; a lost response may still have created a key.");
29635
30041
  }
29636
30042
  });
29637
30043
  server.registerTool("quote_skill", {
@@ -29665,6 +30071,11 @@ async function callRemote(action) {
29665
30071
  return mcpError("REMOTE_REQUEST_FAILED", error2 instanceof Error ? error2.message : "Skills server request failed");
29666
30072
  }
29667
30073
  }
30074
+ async function freshAccount(action, operation) {
30075
+ const target = await captureProfileWorkspace(action);
30076
+ target.unchanged();
30077
+ return operation(new RemoteSkillsAuthClient(target.origin), target.context);
30078
+ }
29668
30079
 
29669
30080
  // src/mcp/server.ts
29670
30081
  function buildServer() {