@hasna/skills 0.5.1 → 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/README.md +28 -1
- package/bin/index.js +162 -1
- package/bin/mcp.js +113 -1
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/cli/commands/workspace-leave.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +91 -1
- package/dist/lib/remote-auth.d.ts +3 -0
- package/dist/lib/remote-client.d.ts +3 -0
- package/dist/lib/remote-workspace-leave.d.ts +52 -0
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.js +91 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -674,7 +674,7 @@ skills/ # Public skill contracts and local OSS skills
|
|
|
674
674
|
|---|---|---|
|
|
675
675
|
| Catalog skills | 86 | `SKILLS.length` (`src/lib/registry-data/`) |
|
|
676
676
|
| Categories | 17 | `CATEGORIES` (`src/lib/registry-types.ts`) |
|
|
677
|
-
| MCP tools |
|
|
677
|
+
| MCP tools | 62 | `tools/list` against a live `buildServer()` |
|
|
678
678
|
|
|
679
679
|
Every number in this table is re-derived from the source tree on each test run by
|
|
680
680
|
`src/lib/readme-derived-counts.test.ts`, so a drifted figure fails a test rather
|
|
@@ -933,3 +933,30 @@ through a tool remains a separate follow-up.
|
|
|
933
933
|
If enrollment reports that key issuance was attempted but not confirmed, inspect
|
|
934
934
|
the selected profile and workspace keys before retrying. A lost response can
|
|
935
935
|
still have created a server key; the CLI does not retry issuance automatically.
|
|
936
|
+
|
|
937
|
+
### Leave a workspace
|
|
938
|
+
|
|
939
|
+
Use the exact membership ID and observed role from fresh `workspace list` output.
|
|
940
|
+
Leaving requires deliberate confirmation and a fresh verification code:
|
|
941
|
+
|
|
942
|
+
```sh
|
|
943
|
+
HASNA_PROFILE=team-b skills workspace leave <membership-id> --expected-role member --email you@example.com --code-stdin --confirm --json
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
The selected profile must authenticate the same membership. Without a named
|
|
947
|
+
profile, supply `--user-id <user-id>` from discovery; this also supports viewers
|
|
948
|
+
who cannot create API keys. The server refuses stale roles, the last active
|
|
949
|
+
owner, and leaving your last available workspace. It decides authority atomically.
|
|
950
|
+
|
|
951
|
+
The SDK offers `RemoteSkillsAuthClient.leaveWorkspace(email, code,
|
|
952
|
+
{ userId, membershipId }, { expectedRole, confirm: true })`; an existing interactive
|
|
953
|
+
session can use `RemoteSkillsClient.leaveWorkspace(context, input)`. MCP exposes
|
|
954
|
+
`leave_workspace` with those same explicit IDs, role, confirmation and fresh code.
|
|
955
|
+
All surfaces call the same HTTP method once. API keys cannot authorize the leave.
|
|
956
|
+
|
|
957
|
+
Success returns `{ organizationId, membershipId, removed: true,
|
|
958
|
+
signInRequired: true }`. Sign in again to an available workspace afterwards.
|
|
959
|
+
Saved credentials and unrelated profiles remain unchanged; credentials for the
|
|
960
|
+
left membership no longer grant access. A lost or invalid response raises
|
|
961
|
+
`RemoteWorkspaceLeaveUnconfirmedError`: inspect available memberships before any
|
|
962
|
+
new action. Never automatically retry or substitute another membership ID.
|
package/bin/index.js
CHANGED
|
@@ -36860,7 +36860,7 @@ var package_default;
|
|
|
36860
36860
|
var init_package = __esm(() => {
|
|
36861
36861
|
package_default = {
|
|
36862
36862
|
name: "@hasna/skills",
|
|
36863
|
-
version: "0.5.
|
|
36863
|
+
version: "0.5.2",
|
|
36864
36864
|
description: "Skills library for AI coding agents",
|
|
36865
36865
|
type: "module",
|
|
36866
36866
|
bin: {
|
|
@@ -49235,6 +49235,75 @@ var init_remote_workspace = __esm(() => {
|
|
|
49235
49235
|
};
|
|
49236
49236
|
});
|
|
49237
49237
|
|
|
49238
|
+
// src/lib/remote-workspace-leave.ts
|
|
49239
|
+
function workspaceLeaveInput(context, input) {
|
|
49240
|
+
const target = workspaceContext(context);
|
|
49241
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).sort().join(",") !== "confirm,expectedRole" || input.confirm !== true)
|
|
49242
|
+
throw new WorkspaceLeaveInputError;
|
|
49243
|
+
const captured = workspaceMemberRemovalInput(target.membershipId, { expectedRole: input.expectedRole });
|
|
49244
|
+
return {
|
|
49245
|
+
context: target,
|
|
49246
|
+
input: { expectedRole: captured.body.expectedRole, confirm: true },
|
|
49247
|
+
body: { membershipId: target.membershipId, expectedRole: captured.body.expectedRole }
|
|
49248
|
+
};
|
|
49249
|
+
}
|
|
49250
|
+
function workspaceLeaveFailure(value, status) {
|
|
49251
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
49252
|
+
return null;
|
|
49253
|
+
const code = value.code;
|
|
49254
|
+
return typeof code === "string" && Object.hasOwn(workspaceLeaveFailures, code) && workspaceLeaveFailures[code][0] === status ? code : null;
|
|
49255
|
+
}
|
|
49256
|
+
function parseWorkspaceLeaveResult(value, membershipId, organizationId) {
|
|
49257
|
+
const row = value;
|
|
49258
|
+
if (!row || typeof row !== "object" || Array.isArray(row) || row.membershipId !== membershipId || row.organizationId !== organizationId || row.removed !== true || row.signInRequired !== true)
|
|
49259
|
+
throw new RemoteWorkspaceLeaveUnconfirmedError;
|
|
49260
|
+
return { membershipId, organizationId, removed: true, signInRequired: true };
|
|
49261
|
+
}
|
|
49262
|
+
function workspaceLeaveProfileContext(membershipId, userId, profile) {
|
|
49263
|
+
const observed = profile === undefined ? undefined : workspaceContext(profile);
|
|
49264
|
+
const target = workspaceContext({ userId: userId ?? observed?.userId, membershipId });
|
|
49265
|
+
if (observed && (observed.userId !== target.userId || observed.membershipId !== target.membershipId))
|
|
49266
|
+
throw new WorkspaceIdentityMismatchError;
|
|
49267
|
+
return target;
|
|
49268
|
+
}
|
|
49269
|
+
var WorkspaceLeaveInputError, workspaceLeaveFailures, RemoteWorkspaceLeaveError, RemoteWorkspaceLeaveUnconfirmedError;
|
|
49270
|
+
var init_remote_workspace_leave = __esm(() => {
|
|
49271
|
+
init_remote_workspace_selection();
|
|
49272
|
+
init_remote_workspace();
|
|
49273
|
+
WorkspaceLeaveInputError = class WorkspaceLeaveInputError extends Error {
|
|
49274
|
+
constructor() {
|
|
49275
|
+
super("Confirm leaving the exact observed user and membership with its expected role.");
|
|
49276
|
+
this.name = "WorkspaceLeaveInputError";
|
|
49277
|
+
}
|
|
49278
|
+
};
|
|
49279
|
+
workspaceLeaveFailures = {
|
|
49280
|
+
INVALID_REQUEST: [400, "Provide the exact current membership and expected role."],
|
|
49281
|
+
ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
|
|
49282
|
+
INTERACTIVE_SESSION_REQUIRED: [403, "Fresh interactive sign-in is required to leave a workspace."],
|
|
49283
|
+
MEMBERSHIP_ROLE_CHANGED: [409, "Your role changed. Sign in and inspect the workspace before leaving."],
|
|
49284
|
+
LAST_OWNER_REQUIRED: [409, "The workspace must retain another active owner."],
|
|
49285
|
+
LAST_WORKSPACE_REQUIRED: [409, "Another available workspace is required before leaving."],
|
|
49286
|
+
MEMBERSHIP_BUSY: [503, "Membership is busy. Inspect the workspace before another leave action."]
|
|
49287
|
+
};
|
|
49288
|
+
RemoteWorkspaceLeaveError = class RemoteWorkspaceLeaveError extends Error {
|
|
49289
|
+
code;
|
|
49290
|
+
status;
|
|
49291
|
+
constructor(code) {
|
|
49292
|
+
super(workspaceLeaveFailures[code][1]);
|
|
49293
|
+
this.code = code;
|
|
49294
|
+
this.name = "RemoteWorkspaceLeaveError";
|
|
49295
|
+
this.status = workspaceLeaveFailures[code][0];
|
|
49296
|
+
}
|
|
49297
|
+
};
|
|
49298
|
+
RemoteWorkspaceLeaveUnconfirmedError = class RemoteWorkspaceLeaveUnconfirmedError extends Error {
|
|
49299
|
+
code = "WORKSPACE_LEAVE_UNCONFIRMED";
|
|
49300
|
+
constructor() {
|
|
49301
|
+
super("The leave outcome is unconfirmed. Sign in again and inspect available memberships before another action. Do not retry automatically; saved credentials are unchanged.");
|
|
49302
|
+
this.name = "RemoteWorkspaceLeaveUnconfirmedError";
|
|
49303
|
+
}
|
|
49304
|
+
};
|
|
49305
|
+
});
|
|
49306
|
+
|
|
49238
49307
|
// src/lib/auth-store.ts
|
|
49239
49308
|
import { chmodSync, existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync12, renameSync as renameSync4, statSync as statSync9, unlinkSync, writeFileSync as writeFileSync7 } from "fs";
|
|
49240
49309
|
import { basename as basename4, dirname as dirname6, join as join18 } from "path";
|
|
@@ -49888,6 +49957,30 @@ class RemoteSkillsClient {
|
|
|
49888
49957
|
}
|
|
49889
49958
|
return value;
|
|
49890
49959
|
}
|
|
49960
|
+
async leaveWorkspace(context, input) {
|
|
49961
|
+
const captured = workspaceLeaveInput(context, input);
|
|
49962
|
+
const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
|
|
49963
|
+
const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
|
|
49964
|
+
if (!value || typeof value !== "object" || value.authMethod !== "jwt")
|
|
49965
|
+
throw new RemoteWorkspaceLeaveError("INTERACTIVE_SESSION_REQUIRED");
|
|
49966
|
+
const identity2 = parseWorkspaceIdentity(value, captured.context.userId);
|
|
49967
|
+
if (identity2.user.membershipId !== captured.context.membershipId)
|
|
49968
|
+
throw new WorkspaceIdentityMismatchError;
|
|
49969
|
+
let response, body;
|
|
49970
|
+
try {
|
|
49971
|
+
response = await connection.request("/api/v1/account/workspaces/leave", { method: "POST", body: JSON.stringify(captured.body) });
|
|
49972
|
+
body = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 4096)));
|
|
49973
|
+
} catch {
|
|
49974
|
+
throw new RemoteWorkspaceLeaveUnconfirmedError;
|
|
49975
|
+
}
|
|
49976
|
+
if (!response.ok) {
|
|
49977
|
+
const code = workspaceLeaveFailure(body, response.status);
|
|
49978
|
+
if (code)
|
|
49979
|
+
throw new RemoteWorkspaceLeaveError(code);
|
|
49980
|
+
throw new RemoteWorkspaceLeaveUnconfirmedError;
|
|
49981
|
+
}
|
|
49982
|
+
return parseWorkspaceLeaveResult(body, captured.context.membershipId, identity2.organization.id);
|
|
49983
|
+
}
|
|
49891
49984
|
async listApiKeys() {
|
|
49892
49985
|
return this.arrayResponse("/api/auth/keys");
|
|
49893
49986
|
}
|
|
@@ -50277,6 +50370,7 @@ function createRemoteSkillsClientReadOnly(env3 = process.env) {
|
|
|
50277
50370
|
}
|
|
50278
50371
|
var RemoteRouteUnsupportedError, RemoteRequestError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
|
|
50279
50372
|
var init_remote_client = __esm(() => {
|
|
50373
|
+
init_remote_workspace_leave();
|
|
50280
50374
|
init_remote_workspace_selection();
|
|
50281
50375
|
init_remote_workspace();
|
|
50282
50376
|
init_remote_workspace();
|
|
@@ -73694,6 +73788,10 @@ class RemoteSkillsAuthClient {
|
|
|
73694
73788
|
const captured = workspaceMemberRoleInput(membershipId, input);
|
|
73695
73789
|
return (await this.sessionClient(email2, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
|
|
73696
73790
|
}
|
|
73791
|
+
async leaveWorkspace(email2, code, context, input) {
|
|
73792
|
+
const captured = workspaceLeaveInput(context, input);
|
|
73793
|
+
return (await this.sessionClient(email2, code, captured.context)).leaveWorkspace(captured.context, captured.input);
|
|
73794
|
+
}
|
|
73697
73795
|
async removeWorkspaceMember(email2, code, membershipId, input, context) {
|
|
73698
73796
|
const captured = workspaceMemberRemovalInput(membershipId, input);
|
|
73699
73797
|
return (await this.sessionClient(email2, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
|
|
@@ -73706,6 +73804,7 @@ class RemoteSkillsAuthClient {
|
|
|
73706
73804
|
}
|
|
73707
73805
|
var MAX_ERROR_DETAIL_LENGTH = 200, HostedApiError;
|
|
73708
73806
|
var init_remote_auth = __esm(() => {
|
|
73807
|
+
init_remote_workspace_leave();
|
|
73709
73808
|
init_remote_workspace_selection();
|
|
73710
73809
|
init_remote_files();
|
|
73711
73810
|
init_remote_workspace();
|
|
@@ -73957,6 +74056,18 @@ function registerRemoteCustomerTools(server) {
|
|
|
73957
74056
|
email: exports_external.string().email(),
|
|
73958
74057
|
code: exports_external.string().regex(/^\d{6}$/)
|
|
73959
74058
|
};
|
|
74059
|
+
server.registerTool("leave_workspace", {
|
|
74060
|
+
title: "Leave Current Workspace",
|
|
74061
|
+
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.",
|
|
74062
|
+
annotations: { destructiveHint: true, idempotentHint: false, readOnlyHint: false },
|
|
74063
|
+
inputSchema: exports_external.object({ ...memberInput, userId: memberInput.membershipId, confirm: exports_external.literal(true) }).strict()
|
|
74064
|
+
}, async ({ membershipId, userId, expectedRole, email: email2, code, confirm }) => {
|
|
74065
|
+
try {
|
|
74066
|
+
return mcpJson(await freshAccount("Leave workspace", (client, context) => client.leaveWorkspace(email2, code, workspaceLeaveProfileContext(membershipId, userId, context), { expectedRole, confirm })));
|
|
74067
|
+
} catch (error2) {
|
|
74068
|
+
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.");
|
|
74069
|
+
}
|
|
74070
|
+
});
|
|
73960
74071
|
server.registerTool("set_workspace_member_role", {
|
|
73961
74072
|
title: "Set Current Workspace Member Role",
|
|
73962
74073
|
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.",
|
|
@@ -74094,6 +74205,7 @@ var init_remote_customer_tools = __esm(() => {
|
|
|
74094
74205
|
init_workspace_profile();
|
|
74095
74206
|
init_remote_customer_operations();
|
|
74096
74207
|
init_remote_client();
|
|
74208
|
+
init_remote_workspace_leave();
|
|
74097
74209
|
init_helpers();
|
|
74098
74210
|
});
|
|
74099
74211
|
|
|
@@ -78485,6 +78597,53 @@ var init_auth = __esm(() => {
|
|
|
78485
78597
|
CONFIG_HINT_STATUSES = new Set([401, 403, 404, 405, 501]);
|
|
78486
78598
|
});
|
|
78487
78599
|
|
|
78600
|
+
// src/cli/commands/workspace-leave.ts
|
|
78601
|
+
function registerWorkspaceLeaveCommand(workspace) {
|
|
78602
|
+
workspace.command("leave <membership-id>").allowExcessArguments(false).description("Leave exactly this membership after fresh verification; saved profiles stay unchanged").requiredOption("--expected-role <role>", "Observed role: owner, admin, member or viewer").requiredOption("--email <email>", "Account email for fresh verification").requiredOption("--confirm", "Confirm losing access through this membership and signing in again").option("--user-id <id>", "Observed user ID; required without a named workspace profile").option("--code-stdin", "Read a previously requested six-digit verification code from stdin").option("--json", "Output the confirmed result as JSON").action(async (membershipId, options) => {
|
|
78603
|
+
try {
|
|
78604
|
+
if (options.confirm !== true)
|
|
78605
|
+
throw new WorkspaceLeaveInputError;
|
|
78606
|
+
const pending = prepareProfileWorkspace("Leave workspace");
|
|
78607
|
+
const target = await pending.resolve();
|
|
78608
|
+
const captured = workspaceLeaveInput(workspaceLeaveProfileContext(membershipId, options.userId, target.context), { expectedRole: options.expectedRole, confirm: true });
|
|
78609
|
+
if (!options.email.includes("@"))
|
|
78610
|
+
throw new NameInputError("Provide the verified account email.");
|
|
78611
|
+
if (!options.codeStdin && (options.json || !process.stdin.isTTY || !process.stderr.isTTY))
|
|
78612
|
+
throw new NameInputError("Use --code-stdin with a fresh verification code for noninteractive leave actions.");
|
|
78613
|
+
const client = new RemoteSkillsAuthClient(target.origin);
|
|
78614
|
+
let code;
|
|
78615
|
+
if (options.codeStdin)
|
|
78616
|
+
code = await readCode();
|
|
78617
|
+
else {
|
|
78618
|
+
await client.requestCode(options.email);
|
|
78619
|
+
code = await promptCode();
|
|
78620
|
+
}
|
|
78621
|
+
if (code === null)
|
|
78622
|
+
return;
|
|
78623
|
+
target.unchanged();
|
|
78624
|
+
const result2 = await client.leaveWorkspace(options.email, code, captured.context, captured.input);
|
|
78625
|
+
if (options.json)
|
|
78626
|
+
console.log(JSON.stringify(result2));
|
|
78627
|
+
else
|
|
78628
|
+
console.log("Workspace membership left. Sign in again to an available workspace. Saved credentials are unchanged; this membership's credentials no longer grant access.");
|
|
78629
|
+
} catch (error2) {
|
|
78630
|
+
const known = error2 instanceof RemoteWorkspaceLeaveError || error2 instanceof RemoteWorkspaceLeaveUnconfirmedError;
|
|
78631
|
+
const message = known || error2 instanceof WorkspaceLeaveInputError || error2 instanceof NameInputError ? error2.message : "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.";
|
|
78632
|
+
if (options.json)
|
|
78633
|
+
console.log(JSON.stringify({ error: message, ...known ? { code: error2.code } : {} }));
|
|
78634
|
+
else
|
|
78635
|
+
console.error(message);
|
|
78636
|
+
process.exitCode = 1;
|
|
78637
|
+
}
|
|
78638
|
+
});
|
|
78639
|
+
}
|
|
78640
|
+
var init_workspace_leave = __esm(() => {
|
|
78641
|
+
init_workspace_profile();
|
|
78642
|
+
init_remote_auth();
|
|
78643
|
+
init_remote_workspace_leave();
|
|
78644
|
+
init_customer_verification();
|
|
78645
|
+
});
|
|
78646
|
+
|
|
78488
78647
|
// src/cli/commands/workspace-members.ts
|
|
78489
78648
|
function registerWorkspaceMembersCommand(workspace) {
|
|
78490
78649
|
workspace.command("members").allowExcessArguments(false).description("List the current workspace roster with fresh owner/admin verification").requiredOption("--email <email>", "Account email for fresh verification").option("--code-stdin", "Read a previously requested six-digit verification code from stdin").option("--limit <count>", "Page size from 1 to 100 (server default: 50)").option("--cursor <cursor>", "Unchanged nextCursor from the preceding page").option("--json", "Output the complete page as JSON").action(async (options) => {
|
|
@@ -78609,6 +78768,7 @@ function registerCustomerProfileCommands(program2) {
|
|
|
78609
78768
|
registerWorkspaceListCommand(workspace);
|
|
78610
78769
|
registerWorkspaceMembersCommand(workspace);
|
|
78611
78770
|
registerWorkspaceMemberMutationCommands(workspace);
|
|
78771
|
+
registerWorkspaceLeaveCommand(workspace);
|
|
78612
78772
|
const commands = [
|
|
78613
78773
|
{ kind: "account", command: account.command("update") },
|
|
78614
78774
|
{ kind: "workspace", command: workspace.command("update") }
|
|
@@ -78655,6 +78815,7 @@ var init_customer_profile = __esm(() => {
|
|
|
78655
78815
|
init_workspace_selection();
|
|
78656
78816
|
init_remote_auth();
|
|
78657
78817
|
init_customer_verification();
|
|
78818
|
+
init_workspace_leave();
|
|
78658
78819
|
init_workspace_members();
|
|
78659
78820
|
init_workspace_member_mutations();
|
|
78660
78821
|
});
|
package/bin/mcp.js
CHANGED
|
@@ -8490,6 +8490,75 @@ var init_remote_workspace = __esm(() => {
|
|
|
8490
8490
|
};
|
|
8491
8491
|
});
|
|
8492
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
|
+
|
|
8493
8562
|
// src/lib/remote-account.ts
|
|
8494
8563
|
function creditCount(value) {
|
|
8495
8564
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0 || value > 2147483647) {
|
|
@@ -8843,6 +8912,30 @@ class RemoteSkillsClient {
|
|
|
8843
8912
|
}
|
|
8844
8913
|
return value;
|
|
8845
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
|
+
}
|
|
8846
8939
|
async listApiKeys() {
|
|
8847
8940
|
return this.arrayResponse("/api/auth/keys");
|
|
8848
8941
|
}
|
|
@@ -9232,6 +9325,7 @@ function createRemoteSkillsClientReadOnly(env = process.env) {
|
|
|
9232
9325
|
}
|
|
9233
9326
|
var RemoteRouteUnsupportedError, RemoteRequestError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
|
|
9234
9327
|
var init_remote_client = __esm(() => {
|
|
9328
|
+
init_remote_workspace_leave();
|
|
9235
9329
|
init_remote_workspace_selection();
|
|
9236
9330
|
init_remote_workspace();
|
|
9237
9331
|
init_remote_workspace();
|
|
@@ -14805,7 +14899,7 @@ class StdioServerTransport {
|
|
|
14805
14899
|
// package.json
|
|
14806
14900
|
var package_default = {
|
|
14807
14901
|
name: "@hasna/skills",
|
|
14808
|
-
version: "0.5.
|
|
14902
|
+
version: "0.5.2",
|
|
14809
14903
|
description: "Skills library for AI coding agents",
|
|
14810
14904
|
type: "module",
|
|
14811
14905
|
bin: {
|
|
@@ -29557,6 +29651,7 @@ function registerStorageTools(server) {
|
|
|
29557
29651
|
}
|
|
29558
29652
|
|
|
29559
29653
|
// src/lib/remote-auth.ts
|
|
29654
|
+
init_remote_workspace_leave();
|
|
29560
29655
|
init_remote_workspace_selection();
|
|
29561
29656
|
init_remote_files();
|
|
29562
29657
|
init_remote_workspace();
|
|
@@ -29732,6 +29827,10 @@ class RemoteSkillsAuthClient {
|
|
|
29732
29827
|
const captured = workspaceMemberRoleInput(membershipId, input);
|
|
29733
29828
|
return (await this.sessionClient(email2, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
|
|
29734
29829
|
}
|
|
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
|
+
}
|
|
29735
29834
|
async removeWorkspaceMember(email2, code, membershipId, input, context) {
|
|
29736
29835
|
const captured = workspaceMemberRemovalInput(membershipId, input);
|
|
29737
29836
|
return (await this.sessionClient(email2, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
|
|
@@ -29825,6 +29924,7 @@ async function captureProfileWorkspace(action, source = process.env) {
|
|
|
29825
29924
|
|
|
29826
29925
|
// src/mcp/remote-customer-tools.ts
|
|
29827
29926
|
init_remote_client();
|
|
29927
|
+
init_remote_workspace_leave();
|
|
29828
29928
|
function registerRemoteCustomerTools(server) {
|
|
29829
29929
|
const memberRole = exports_external.enum(["owner", "admin", "member", "viewer"]);
|
|
29830
29930
|
const memberInput = {
|
|
@@ -29833,6 +29933,18 @@ function registerRemoteCustomerTools(server) {
|
|
|
29833
29933
|
email: exports_external.string().email(),
|
|
29834
29934
|
code: exports_external.string().regex(/^\d{6}$/)
|
|
29835
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
|
+
});
|
|
29836
29948
|
server.registerTool("set_workspace_member_role", {
|
|
29837
29949
|
title: "Set Current Workspace Member Role",
|
|
29838
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.",
|
package/bin/migrate.js
CHANGED
package/bin/server.js
CHANGED
|
@@ -23100,7 +23100,7 @@ var init_dist_es9 = __esm(() => {
|
|
|
23100
23100
|
// package.json
|
|
23101
23101
|
var package_default = {
|
|
23102
23102
|
name: "@hasna/skills",
|
|
23103
|
-
version: "0.5.
|
|
23103
|
+
version: "0.5.2",
|
|
23104
23104
|
description: "Skills library for AI coding agents",
|
|
23105
23105
|
type: "module",
|
|
23106
23106
|
bin: {
|
package/bin/worker.js
CHANGED
|
@@ -23103,7 +23103,7 @@ import { randomUUID as randomUUID4 } from "crypto";
|
|
|
23103
23103
|
// package.json
|
|
23104
23104
|
var package_default = {
|
|
23105
23105
|
name: "@hasna/skills",
|
|
23106
|
-
version: "0.5.
|
|
23106
|
+
version: "0.5.2",
|
|
23107
23107
|
description: "Skills library for AI coding agents",
|
|
23108
23108
|
type: "module",
|
|
23109
23109
|
bin: {
|
package/dist/index.d.ts
CHANGED
|
@@ -46,3 +46,5 @@ export type { RemoteCustomerRole, RemoteCustomerProfile, RemoteCurrentWorkspace,
|
|
|
46
46
|
export type { RemoteWorkspaceContext, RemoteAccountWorkspace, RemoteAccountWorkspaces, RemoteWorkspaceIdentity, RemoteWorkspaceSession, RemoteAccountWorkspaceDiscovery, RemoteWorkspaceSelectionErrorCode } from "./lib/remote-workspace-selection.js";
|
|
47
47
|
export { WorkspaceContextInputError, WorkspaceIdentityMismatchError } from "./lib/remote-workspace-selection.js";
|
|
48
48
|
export { RemoteWorkspaceSelectionError } from "./lib/remote-client.js";
|
|
49
|
+
export type { LeaveRemoteWorkspace, RemoteWorkspaceLeaveResult, RemoteWorkspaceLeaveErrorCode } from "./lib/remote-workspace-leave.js";
|
|
50
|
+
export { WorkspaceLeaveInputError, RemoteWorkspaceLeaveError, RemoteWorkspaceLeaveUnconfirmedError } from "./lib/remote-workspace-leave.js";
|
package/dist/index.js
CHANGED
|
@@ -10450,6 +10450,65 @@ function parseWorkspaceMembersPage(value) {
|
|
|
10450
10450
|
return fail();
|
|
10451
10451
|
return { organizationId: value.organizationId, members, nextCursor: value.nextCursor };
|
|
10452
10452
|
}
|
|
10453
|
+
|
|
10454
|
+
// src/lib/remote-workspace-leave.ts
|
|
10455
|
+
class WorkspaceLeaveInputError extends Error {
|
|
10456
|
+
constructor() {
|
|
10457
|
+
super("Confirm leaving the exact observed user and membership with its expected role.");
|
|
10458
|
+
this.name = "WorkspaceLeaveInputError";
|
|
10459
|
+
}
|
|
10460
|
+
}
|
|
10461
|
+
function workspaceLeaveInput(context, input) {
|
|
10462
|
+
const target = workspaceContext(context);
|
|
10463
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).sort().join(",") !== "confirm,expectedRole" || input.confirm !== true)
|
|
10464
|
+
throw new WorkspaceLeaveInputError;
|
|
10465
|
+
const captured = workspaceMemberRemovalInput(target.membershipId, { expectedRole: input.expectedRole });
|
|
10466
|
+
return {
|
|
10467
|
+
context: target,
|
|
10468
|
+
input: { expectedRole: captured.body.expectedRole, confirm: true },
|
|
10469
|
+
body: { membershipId: target.membershipId, expectedRole: captured.body.expectedRole }
|
|
10470
|
+
};
|
|
10471
|
+
}
|
|
10472
|
+
var workspaceLeaveFailures = {
|
|
10473
|
+
INVALID_REQUEST: [400, "Provide the exact current membership and expected role."],
|
|
10474
|
+
ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
|
|
10475
|
+
INTERACTIVE_SESSION_REQUIRED: [403, "Fresh interactive sign-in is required to leave a workspace."],
|
|
10476
|
+
MEMBERSHIP_ROLE_CHANGED: [409, "Your role changed. Sign in and inspect the workspace before leaving."],
|
|
10477
|
+
LAST_OWNER_REQUIRED: [409, "The workspace must retain another active owner."],
|
|
10478
|
+
LAST_WORKSPACE_REQUIRED: [409, "Another available workspace is required before leaving."],
|
|
10479
|
+
MEMBERSHIP_BUSY: [503, "Membership is busy. Inspect the workspace before another leave action."]
|
|
10480
|
+
};
|
|
10481
|
+
|
|
10482
|
+
class RemoteWorkspaceLeaveError extends Error {
|
|
10483
|
+
code;
|
|
10484
|
+
status;
|
|
10485
|
+
constructor(code) {
|
|
10486
|
+
super(workspaceLeaveFailures[code][1]);
|
|
10487
|
+
this.code = code;
|
|
10488
|
+
this.name = "RemoteWorkspaceLeaveError";
|
|
10489
|
+
this.status = workspaceLeaveFailures[code][0];
|
|
10490
|
+
}
|
|
10491
|
+
}
|
|
10492
|
+
|
|
10493
|
+
class RemoteWorkspaceLeaveUnconfirmedError extends Error {
|
|
10494
|
+
code = "WORKSPACE_LEAVE_UNCONFIRMED";
|
|
10495
|
+
constructor() {
|
|
10496
|
+
super("The leave outcome is unconfirmed. Sign in again and inspect available memberships before another action. Do not retry automatically; saved credentials are unchanged.");
|
|
10497
|
+
this.name = "RemoteWorkspaceLeaveUnconfirmedError";
|
|
10498
|
+
}
|
|
10499
|
+
}
|
|
10500
|
+
function workspaceLeaveFailure(value, status) {
|
|
10501
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
10502
|
+
return null;
|
|
10503
|
+
const code = value.code;
|
|
10504
|
+
return typeof code === "string" && Object.hasOwn(workspaceLeaveFailures, code) && workspaceLeaveFailures[code][0] === status ? code : null;
|
|
10505
|
+
}
|
|
10506
|
+
function parseWorkspaceLeaveResult(value, membershipId, organizationId) {
|
|
10507
|
+
const row = value;
|
|
10508
|
+
if (!row || typeof row !== "object" || Array.isArray(row) || row.membershipId !== membershipId || row.organizationId !== organizationId || row.removed !== true || row.signInRequired !== true)
|
|
10509
|
+
throw new RemoteWorkspaceLeaveUnconfirmedError;
|
|
10510
|
+
return { membershipId, organizationId, removed: true, signInRequired: true };
|
|
10511
|
+
}
|
|
10453
10512
|
// src/lib/auth-store.ts
|
|
10454
10513
|
function getApiUrl(action, env = process.env, options = {}) {
|
|
10455
10514
|
return requireSkillsApiOrigin(action, env, options);
|
|
@@ -10949,6 +11008,30 @@ class RemoteSkillsClient {
|
|
|
10949
11008
|
}
|
|
10950
11009
|
return value;
|
|
10951
11010
|
}
|
|
11011
|
+
async leaveWorkspace(context, input) {
|
|
11012
|
+
const captured = workspaceLeaveInput(context, input);
|
|
11013
|
+
const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
|
|
11014
|
+
const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
|
|
11015
|
+
if (!value || typeof value !== "object" || value.authMethod !== "jwt")
|
|
11016
|
+
throw new RemoteWorkspaceLeaveError("INTERACTIVE_SESSION_REQUIRED");
|
|
11017
|
+
const identity = parseWorkspaceIdentity(value, captured.context.userId);
|
|
11018
|
+
if (identity.user.membershipId !== captured.context.membershipId)
|
|
11019
|
+
throw new WorkspaceIdentityMismatchError;
|
|
11020
|
+
let response, body;
|
|
11021
|
+
try {
|
|
11022
|
+
response = await connection.request("/api/v1/account/workspaces/leave", { method: "POST", body: JSON.stringify(captured.body) });
|
|
11023
|
+
body = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 4096)));
|
|
11024
|
+
} catch {
|
|
11025
|
+
throw new RemoteWorkspaceLeaveUnconfirmedError;
|
|
11026
|
+
}
|
|
11027
|
+
if (!response.ok) {
|
|
11028
|
+
const code = workspaceLeaveFailure(body, response.status);
|
|
11029
|
+
if (code)
|
|
11030
|
+
throw new RemoteWorkspaceLeaveError(code);
|
|
11031
|
+
throw new RemoteWorkspaceLeaveUnconfirmedError;
|
|
11032
|
+
}
|
|
11033
|
+
return parseWorkspaceLeaveResult(body, captured.context.membershipId, identity.organization.id);
|
|
11034
|
+
}
|
|
10952
11035
|
async listApiKeys() {
|
|
10953
11036
|
return this.arrayResponse("/api/auth/keys");
|
|
10954
11037
|
}
|
|
@@ -12441,7 +12524,7 @@ import { dirname as dirname7, relative as relative4 } from "path";
|
|
|
12441
12524
|
// package.json
|
|
12442
12525
|
var package_default = {
|
|
12443
12526
|
name: "@hasna/skills",
|
|
12444
|
-
version: "0.5.
|
|
12527
|
+
version: "0.5.2",
|
|
12445
12528
|
description: "Skills library for AI coding agents",
|
|
12446
12529
|
type: "module",
|
|
12447
12530
|
bin: {
|
|
@@ -15081,6 +15164,10 @@ class RemoteSkillsAuthClient {
|
|
|
15081
15164
|
const captured = workspaceMemberRoleInput(membershipId, input);
|
|
15082
15165
|
return (await this.sessionClient(email, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
|
|
15083
15166
|
}
|
|
15167
|
+
async leaveWorkspace(email, code, context, input) {
|
|
15168
|
+
const captured = workspaceLeaveInput(context, input);
|
|
15169
|
+
return (await this.sessionClient(email, code, captured.context)).leaveWorkspace(captured.context, captured.input);
|
|
15170
|
+
}
|
|
15084
15171
|
async removeWorkspaceMember(email, code, membershipId, input, context) {
|
|
15085
15172
|
const captured = workspaceMemberRemovalInput(membershipId, input);
|
|
15086
15173
|
return (await this.sessionClient(email, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
|
|
@@ -15273,6 +15360,7 @@ export {
|
|
|
15273
15360
|
agentGlobalSkillsDir,
|
|
15274
15361
|
addSchedule,
|
|
15275
15362
|
adaptSkillMdForAgent,
|
|
15363
|
+
WorkspaceLeaveInputError,
|
|
15276
15364
|
WorkspaceIdentityMismatchError,
|
|
15277
15365
|
WorkspaceContextInputError,
|
|
15278
15366
|
TOOL_PRIMITIVE_SCHEMA_VERSION,
|
|
@@ -15308,6 +15396,8 @@ export {
|
|
|
15308
15396
|
SKILLS,
|
|
15309
15397
|
RemoteWorkspaceSelectionError,
|
|
15310
15398
|
RemoteWorkspaceMemberError,
|
|
15399
|
+
RemoteWorkspaceLeaveUnconfirmedError,
|
|
15400
|
+
RemoteWorkspaceLeaveError,
|
|
15311
15401
|
RemoteSkillsClient,
|
|
15312
15402
|
RemoteSkillsAuthClient,
|
|
15313
15403
|
RemoteRouteUnsupportedError,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LeaveRemoteWorkspace } from "./remote-workspace-leave.js";
|
|
1
2
|
import { type RemoteWorkspaceContext, type RemoteWorkspaceSession, type RemoteAccountWorkspaceDiscovery } from "./remote-workspace-selection.js";
|
|
2
3
|
import { type RemoteWorkspaceMembersOptions } from "./remote-workspace.js";
|
|
3
4
|
import { type SetRemoteWorkspaceMemberRole, type RemoveRemoteWorkspaceMember } from "./remote-workspace.js";
|
|
@@ -46,6 +47,8 @@ export declare class RemoteSkillsAuthClient {
|
|
|
46
47
|
/** Fresh owner/admin session; explicit context survives default-workspace OTP selection. */
|
|
47
48
|
listWorkspaceMembers(email: string, code: string, options?: RemoteWorkspaceMembersOptions, context?: RemoteWorkspaceContext): Promise<import("./remote-workspace.js").RemoteWorkspaceMembersPage>;
|
|
48
49
|
setWorkspaceMemberRole(email: string, code: string, membershipId: string, input: SetRemoteWorkspaceMemberRole, context?: RemoteWorkspaceContext): Promise<import("./remote-workspace.js").RemoteWorkspaceMemberRoleResult>;
|
|
50
|
+
/** Fresh verification binds the exact observed incarnation before a single confirmed leave. */
|
|
51
|
+
leaveWorkspace(email: string, code: string, context: RemoteWorkspaceContext, input: LeaveRemoteWorkspace): Promise<import("./remote-workspace-leave.js").RemoteWorkspaceLeaveResult>;
|
|
49
52
|
removeWorkspaceMember(email: string, code: string, membershipId: string, input: RemoveRemoteWorkspaceMember, context?: RemoteWorkspaceContext): Promise<import("./remote-workspace.js").RemoteWorkspaceMemberRemovalResult>;
|
|
50
53
|
/** Common auth transport used by CLI login, preserving the selected instance through awaits. */
|
|
51
54
|
request(path: string, options?: RequestInit): Promise<any>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LeaveRemoteWorkspace, type RemoteWorkspaceLeaveResult } from "./remote-workspace-leave.js";
|
|
1
2
|
import { type RemoteWorkspaceContext, type RemoteAccountWorkspaces, type RemoteWorkspaceSession, type RemoteWorkspaceSelectionErrorCode } from "./remote-workspace-selection.js";
|
|
2
3
|
import { type RemoteWorkspaceMembersOptions, type RemoteWorkspaceMembersPage } from "./remote-workspace.js";
|
|
3
4
|
import { type RemoteWorkspaceMemberErrorCode, type SetRemoteWorkspaceMemberRole, type RemoveRemoteWorkspaceMember, type RemoteWorkspaceMemberRoleResult, type RemoteWorkspaceMemberRemovalResult } from "./remote-workspace.js";
|
|
@@ -145,6 +146,8 @@ export declare class RemoteSkillsClient {
|
|
|
145
146
|
/** Removes only this incarnation. A successful tombstone replay is returned unchanged. */
|
|
146
147
|
removeWorkspaceMember(membershipId: string, input: RemoveRemoteWorkspaceMember): Promise<RemoteWorkspaceMemberRemovalResult>;
|
|
147
148
|
private requestWorkspaceMember;
|
|
149
|
+
/** Leave only the explicitly confirmed current incarnation, once. No credential writes or retries. */
|
|
150
|
+
leaveWorkspace(context: RemoteWorkspaceContext, input: LeaveRemoteWorkspace): Promise<RemoteWorkspaceLeaveResult>;
|
|
148
151
|
listApiKeys(): Promise<Record<string, unknown>[]>;
|
|
149
152
|
createApiKey(name: string, scopes?: string[]): Promise<{
|
|
150
153
|
key: string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type RemoteWorkspaceContext } from "./remote-workspace-selection.js";
|
|
2
|
+
import type { RemoteCustomerRole } from "./remote-profile.js";
|
|
3
|
+
export type LeaveRemoteWorkspace = Readonly<{
|
|
4
|
+
expectedRole: RemoteCustomerRole;
|
|
5
|
+
confirm: true;
|
|
6
|
+
}>;
|
|
7
|
+
export type RemoteWorkspaceLeaveResult = {
|
|
8
|
+
organizationId: string;
|
|
9
|
+
membershipId: string;
|
|
10
|
+
removed: true;
|
|
11
|
+
signInRequired: true;
|
|
12
|
+
};
|
|
13
|
+
export declare class WorkspaceLeaveInputError extends Error {
|
|
14
|
+
constructor();
|
|
15
|
+
}
|
|
16
|
+
export declare function workspaceLeaveInput(context: RemoteWorkspaceContext, input: LeaveRemoteWorkspace): {
|
|
17
|
+
context: Readonly<{
|
|
18
|
+
userId: string;
|
|
19
|
+
membershipId: string;
|
|
20
|
+
}>;
|
|
21
|
+
input: {
|
|
22
|
+
expectedRole: RemoteCustomerRole;
|
|
23
|
+
confirm: true;
|
|
24
|
+
};
|
|
25
|
+
body: {
|
|
26
|
+
membershipId: string;
|
|
27
|
+
expectedRole: RemoteCustomerRole;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export declare const workspaceLeaveFailures: {
|
|
31
|
+
readonly INVALID_REQUEST: readonly [400, "Provide the exact current membership and expected role."];
|
|
32
|
+
readonly ACCOUNT_UNAVAILABLE: readonly [403, "Account is unavailable."];
|
|
33
|
+
readonly INTERACTIVE_SESSION_REQUIRED: readonly [403, "Fresh interactive sign-in is required to leave a workspace."];
|
|
34
|
+
readonly MEMBERSHIP_ROLE_CHANGED: readonly [409, "Your role changed. Sign in and inspect the workspace before leaving."];
|
|
35
|
+
readonly LAST_OWNER_REQUIRED: readonly [409, "The workspace must retain another active owner."];
|
|
36
|
+
readonly LAST_WORKSPACE_REQUIRED: readonly [409, "Another available workspace is required before leaving."];
|
|
37
|
+
readonly MEMBERSHIP_BUSY: readonly [503, "Membership is busy. Inspect the workspace before another leave action."];
|
|
38
|
+
};
|
|
39
|
+
export type RemoteWorkspaceLeaveErrorCode = keyof typeof workspaceLeaveFailures;
|
|
40
|
+
export declare class RemoteWorkspaceLeaveError extends Error {
|
|
41
|
+
readonly code: RemoteWorkspaceLeaveErrorCode;
|
|
42
|
+
readonly status: number;
|
|
43
|
+
constructor(code: RemoteWorkspaceLeaveErrorCode);
|
|
44
|
+
}
|
|
45
|
+
export declare class RemoteWorkspaceLeaveUnconfirmedError extends Error {
|
|
46
|
+
readonly code = "WORKSPACE_LEAVE_UNCONFIRMED";
|
|
47
|
+
constructor();
|
|
48
|
+
}
|
|
49
|
+
export declare function workspaceLeaveFailure(value: unknown, status: number): RemoteWorkspaceLeaveErrorCode | null;
|
|
50
|
+
export declare function parseWorkspaceLeaveResult(value: unknown, membershipId: string, organizationId: string): RemoteWorkspaceLeaveResult;
|
|
51
|
+
/** A configured profile may constrain an explicit leave, never retarget it. */
|
|
52
|
+
export declare function workspaceLeaveProfileContext(membershipId: string, userId: string | undefined, profile?: RemoteWorkspaceContext): RemoteWorkspaceContext;
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -40,3 +40,5 @@ export type { RemoteCustomerRole, RemoteCustomerProfile, RemoteCurrentWorkspace,
|
|
|
40
40
|
export type { RemoteWorkspaceContext, RemoteAccountWorkspace, RemoteAccountWorkspaces, RemoteWorkspaceIdentity, RemoteWorkspaceSession, RemoteAccountWorkspaceDiscovery, RemoteWorkspaceSelectionErrorCode } from "../lib/remote-workspace-selection.js";
|
|
41
41
|
export { WorkspaceContextInputError, WorkspaceIdentityMismatchError } from "../lib/remote-workspace-selection.js";
|
|
42
42
|
export { RemoteWorkspaceSelectionError } from "../lib/remote-client.js";
|
|
43
|
+
export type { LeaveRemoteWorkspace, RemoteWorkspaceLeaveResult, RemoteWorkspaceLeaveErrorCode } from "../lib/remote-workspace-leave.js";
|
|
44
|
+
export { WorkspaceLeaveInputError, RemoteWorkspaceLeaveError, RemoteWorkspaceLeaveUnconfirmedError } from "../lib/remote-workspace-leave.js";
|
package/dist/sdk/index.js
CHANGED
|
@@ -25462,7 +25462,7 @@ class MissingSkillsFleetError extends Error {
|
|
|
25462
25462
|
// package.json
|
|
25463
25463
|
var package_default = {
|
|
25464
25464
|
name: "@hasna/skills",
|
|
25465
|
-
version: "0.5.
|
|
25465
|
+
version: "0.5.2",
|
|
25466
25466
|
description: "Skills library for AI coding agents",
|
|
25467
25467
|
type: "module",
|
|
25468
25468
|
bin: {
|
|
@@ -52360,6 +52360,65 @@ function parseWorkspaceMembersPage(value) {
|
|
|
52360
52360
|
return fail();
|
|
52361
52361
|
return { organizationId: value.organizationId, members, nextCursor: value.nextCursor };
|
|
52362
52362
|
}
|
|
52363
|
+
|
|
52364
|
+
// src/lib/remote-workspace-leave.ts
|
|
52365
|
+
class WorkspaceLeaveInputError extends Error {
|
|
52366
|
+
constructor() {
|
|
52367
|
+
super("Confirm leaving the exact observed user and membership with its expected role.");
|
|
52368
|
+
this.name = "WorkspaceLeaveInputError";
|
|
52369
|
+
}
|
|
52370
|
+
}
|
|
52371
|
+
function workspaceLeaveInput(context, input) {
|
|
52372
|
+
const target = workspaceContext(context);
|
|
52373
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).sort().join(",") !== "confirm,expectedRole" || input.confirm !== true)
|
|
52374
|
+
throw new WorkspaceLeaveInputError;
|
|
52375
|
+
const captured = workspaceMemberRemovalInput(target.membershipId, { expectedRole: input.expectedRole });
|
|
52376
|
+
return {
|
|
52377
|
+
context: target,
|
|
52378
|
+
input: { expectedRole: captured.body.expectedRole, confirm: true },
|
|
52379
|
+
body: { membershipId: target.membershipId, expectedRole: captured.body.expectedRole }
|
|
52380
|
+
};
|
|
52381
|
+
}
|
|
52382
|
+
var workspaceLeaveFailures = {
|
|
52383
|
+
INVALID_REQUEST: [400, "Provide the exact current membership and expected role."],
|
|
52384
|
+
ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
|
|
52385
|
+
INTERACTIVE_SESSION_REQUIRED: [403, "Fresh interactive sign-in is required to leave a workspace."],
|
|
52386
|
+
MEMBERSHIP_ROLE_CHANGED: [409, "Your role changed. Sign in and inspect the workspace before leaving."],
|
|
52387
|
+
LAST_OWNER_REQUIRED: [409, "The workspace must retain another active owner."],
|
|
52388
|
+
LAST_WORKSPACE_REQUIRED: [409, "Another available workspace is required before leaving."],
|
|
52389
|
+
MEMBERSHIP_BUSY: [503, "Membership is busy. Inspect the workspace before another leave action."]
|
|
52390
|
+
};
|
|
52391
|
+
|
|
52392
|
+
class RemoteWorkspaceLeaveError extends Error {
|
|
52393
|
+
code;
|
|
52394
|
+
status;
|
|
52395
|
+
constructor(code) {
|
|
52396
|
+
super(workspaceLeaveFailures[code][1]);
|
|
52397
|
+
this.code = code;
|
|
52398
|
+
this.name = "RemoteWorkspaceLeaveError";
|
|
52399
|
+
this.status = workspaceLeaveFailures[code][0];
|
|
52400
|
+
}
|
|
52401
|
+
}
|
|
52402
|
+
|
|
52403
|
+
class RemoteWorkspaceLeaveUnconfirmedError extends Error {
|
|
52404
|
+
code = "WORKSPACE_LEAVE_UNCONFIRMED";
|
|
52405
|
+
constructor() {
|
|
52406
|
+
super("The leave outcome is unconfirmed. Sign in again and inspect available memberships before another action. Do not retry automatically; saved credentials are unchanged.");
|
|
52407
|
+
this.name = "RemoteWorkspaceLeaveUnconfirmedError";
|
|
52408
|
+
}
|
|
52409
|
+
}
|
|
52410
|
+
function workspaceLeaveFailure(value, status) {
|
|
52411
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
52412
|
+
return null;
|
|
52413
|
+
const code = value.code;
|
|
52414
|
+
return typeof code === "string" && Object.hasOwn(workspaceLeaveFailures, code) && workspaceLeaveFailures[code][0] === status ? code : null;
|
|
52415
|
+
}
|
|
52416
|
+
function parseWorkspaceLeaveResult(value, membershipId, organizationId) {
|
|
52417
|
+
const row = value;
|
|
52418
|
+
if (!row || typeof row !== "object" || Array.isArray(row) || row.membershipId !== membershipId || row.organizationId !== organizationId || row.removed !== true || row.signInRequired !== true)
|
|
52419
|
+
throw new RemoteWorkspaceLeaveUnconfirmedError;
|
|
52420
|
+
return { membershipId, organizationId, removed: true, signInRequired: true };
|
|
52421
|
+
}
|
|
52363
52422
|
// src/lib/auth-store.ts
|
|
52364
52423
|
function getApiUrl(action, env = process.env, options = {}) {
|
|
52365
52424
|
return requireSkillsApiOrigin(action, env, options);
|
|
@@ -52815,6 +52874,30 @@ class RemoteSkillsClient {
|
|
|
52815
52874
|
}
|
|
52816
52875
|
return value;
|
|
52817
52876
|
}
|
|
52877
|
+
async leaveWorkspace(context, input) {
|
|
52878
|
+
const captured = workspaceLeaveInput(context, input);
|
|
52879
|
+
const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
|
|
52880
|
+
const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
|
|
52881
|
+
if (!value || typeof value !== "object" || value.authMethod !== "jwt")
|
|
52882
|
+
throw new RemoteWorkspaceLeaveError("INTERACTIVE_SESSION_REQUIRED");
|
|
52883
|
+
const identity = parseWorkspaceIdentity(value, captured.context.userId);
|
|
52884
|
+
if (identity.user.membershipId !== captured.context.membershipId)
|
|
52885
|
+
throw new WorkspaceIdentityMismatchError;
|
|
52886
|
+
let response, body;
|
|
52887
|
+
try {
|
|
52888
|
+
response = await connection.request("/api/v1/account/workspaces/leave", { method: "POST", body: JSON.stringify(captured.body) });
|
|
52889
|
+
body = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 4096)));
|
|
52890
|
+
} catch {
|
|
52891
|
+
throw new RemoteWorkspaceLeaveUnconfirmedError;
|
|
52892
|
+
}
|
|
52893
|
+
if (!response.ok) {
|
|
52894
|
+
const code = workspaceLeaveFailure(body, response.status);
|
|
52895
|
+
if (code)
|
|
52896
|
+
throw new RemoteWorkspaceLeaveError(code);
|
|
52897
|
+
throw new RemoteWorkspaceLeaveUnconfirmedError;
|
|
52898
|
+
}
|
|
52899
|
+
return parseWorkspaceLeaveResult(body, captured.context.membershipId, identity.organization.id);
|
|
52900
|
+
}
|
|
52818
52901
|
async listApiKeys() {
|
|
52819
52902
|
return this.arrayResponse("/api/auth/keys");
|
|
52820
52903
|
}
|
|
@@ -53370,6 +53453,10 @@ class RemoteSkillsAuthClient {
|
|
|
53370
53453
|
const captured = workspaceMemberRoleInput(membershipId, input);
|
|
53371
53454
|
return (await this.sessionClient(email, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
|
|
53372
53455
|
}
|
|
53456
|
+
async leaveWorkspace(email, code, context, input) {
|
|
53457
|
+
const captured = workspaceLeaveInput(context, input);
|
|
53458
|
+
return (await this.sessionClient(email, code, captured.context)).leaveWorkspace(captured.context, captured.input);
|
|
53459
|
+
}
|
|
53373
53460
|
async removeWorkspaceMember(email, code, membershipId, input, context) {
|
|
53374
53461
|
const captured = workspaceMemberRemovalInput(membershipId, input);
|
|
53375
53462
|
return (await this.sessionClient(email, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
|
|
@@ -53453,6 +53540,7 @@ export {
|
|
|
53453
53540
|
assertDurableTarget,
|
|
53454
53541
|
assertDurableStore,
|
|
53455
53542
|
artifactStorageSeam,
|
|
53543
|
+
WorkspaceLeaveInputError,
|
|
53456
53544
|
WorkspaceIdentityMismatchError,
|
|
53457
53545
|
WorkspaceContextInputError,
|
|
53458
53546
|
SqliteSkillsStore,
|
|
@@ -53465,6 +53553,8 @@ export {
|
|
|
53465
53553
|
SKILLS_API_KEY_ENV,
|
|
53466
53554
|
RemoteWorkspaceSelectionError,
|
|
53467
53555
|
RemoteWorkspaceMemberError,
|
|
53556
|
+
RemoteWorkspaceLeaveUnconfirmedError,
|
|
53557
|
+
RemoteWorkspaceLeaveError,
|
|
53468
53558
|
RemoteSkillsClient,
|
|
53469
53559
|
RemoteSkillsAuthClient,
|
|
53470
53560
|
RemoteRouteUnsupportedError,
|