@hasna/skills 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/bin/index.js +927 -393
- package/bin/mcp.js +409 -110
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/cli/commands/workspace-selection.d.ts +11 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +337 -123
- package/dist/lib/remote-auth.d.ts +15 -9
- package/dist/lib/remote-client.d.ts +11 -0
- package/dist/lib/remote-workspace-selection.d.ts +58 -0
- package/dist/lib/workspace-profile.d.ts +49 -0
- package/dist/sdk/index.d.ts +3 -0
- package/dist/sdk/index.js +281 -67
- package/package.json +1 -1
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 (!
|
|
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 (!
|
|
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" || !
|
|
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,
|
|
8331
|
-
if (!isRole(expectedRole) || roleChange && !isRole(
|
|
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,
|
|
8438
|
+
function parseWorkspaceMemberRoleResult(value, membershipId, role2) {
|
|
8344
8439
|
const fail = () => {
|
|
8345
8440
|
throw new Error(invalidMemberResult);
|
|
8346
8441
|
};
|
|
8347
|
-
if (!
|
|
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 !==
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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() {
|
|
@@ -8498,11 +8593,11 @@ function parseUpdatedProfile(value) {
|
|
|
8498
8593
|
return { user: { id: user.id, email: user.email, displayName: user.displayName, role: user.role } };
|
|
8499
8594
|
}
|
|
8500
8595
|
function parseUpdatedWorkspace(value) {
|
|
8501
|
-
const
|
|
8502
|
-
if (!isRecord4(
|
|
8596
|
+
const organization2 = isRecord4(value) && value.organization;
|
|
8597
|
+
if (!isRecord4(organization2) || !string4(organization2.id) || !string4(organization2.slug) || !string4(organization2.name)) {
|
|
8503
8598
|
throw new Error("The server returned an invalid workspace.");
|
|
8504
8599
|
}
|
|
8505
|
-
return { organization: { id:
|
|
8600
|
+
return { organization: { id: organization2.id, slug: organization2.slug, name: organization2.name } };
|
|
8506
8601
|
}
|
|
8507
8602
|
|
|
8508
8603
|
// src/lib/remote-client.ts
|
|
@@ -8510,6 +8605,7 @@ var exports_remote_client = {};
|
|
|
8510
8605
|
__export(exports_remote_client, {
|
|
8511
8606
|
createRemoteSkillsClientReadOnly: () => createRemoteSkillsClientReadOnly,
|
|
8512
8607
|
createRemoteSkillsClient: () => createRemoteSkillsClient,
|
|
8608
|
+
RemoteWorkspaceSelectionError: () => RemoteWorkspaceSelectionError,
|
|
8513
8609
|
RemoteWorkspaceMemberError: () => RemoteWorkspaceMemberError,
|
|
8514
8610
|
RemoteSkillsClient: () => RemoteSkillsClient,
|
|
8515
8611
|
RemoteRouteUnsupportedError: () => RemoteRouteUnsupportedError,
|
|
@@ -8529,6 +8625,7 @@ class RemoteSkillsClient {
|
|
|
8529
8625
|
return fetch(`${this.apiUrl}${path}`, {
|
|
8530
8626
|
...options,
|
|
8531
8627
|
redirect: "error",
|
|
8628
|
+
credentials: "omit",
|
|
8532
8629
|
signal: options?.signal ?? AbortSignal.timeout(15000),
|
|
8533
8630
|
headers: {
|
|
8534
8631
|
Authorization: `Bearer ${this.apiKey}`,
|
|
@@ -8635,6 +8732,65 @@ class RemoteSkillsClient {
|
|
|
8635
8732
|
async getIdentity() {
|
|
8636
8733
|
return (await this.requestNewRoute("/api/auth/whoami")).json();
|
|
8637
8734
|
}
|
|
8735
|
+
async listAccountWorkspaces(expectedUserId) {
|
|
8736
|
+
const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
|
|
8737
|
+
const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
|
|
8738
|
+
let identity;
|
|
8739
|
+
if (expected !== undefined) {
|
|
8740
|
+
const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
|
|
8741
|
+
if (!value || typeof value !== "object" || value.authMethod !== "jwt")
|
|
8742
|
+
throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces", "INTERACTIVE_SESSION_REQUIRED");
|
|
8743
|
+
identity = parseWorkspaceIdentity(value, expected);
|
|
8744
|
+
}
|
|
8745
|
+
const result = parseAccountWorkspaces(await connection.requestWorkspaceSelection("/api/v1/account/workspaces"));
|
|
8746
|
+
const current = result.workspaces.find((workspace) => workspace.current);
|
|
8747
|
+
if (identity && (current.membershipId !== identity.user.membershipId || current.organization.id !== identity.organization.id))
|
|
8748
|
+
throw new WorkspaceIdentityMismatchError;
|
|
8749
|
+
return result;
|
|
8750
|
+
}
|
|
8751
|
+
async switchWorkspace(context) {
|
|
8752
|
+
const target = workspaceContext(context);
|
|
8753
|
+
const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
|
|
8754
|
+
const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
|
|
8755
|
+
if (!value || typeof value !== "object" || value.authMethod !== "jwt")
|
|
8756
|
+
throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
|
|
8757
|
+
parseWorkspaceIdentity(value, target.userId);
|
|
8758
|
+
const selected = parseWorkspaceSession(await connection.requestWorkspaceSelection("/api/v1/account/workspaces/switch", {
|
|
8759
|
+
method: "POST",
|
|
8760
|
+
body: JSON.stringify({ membershipId: target.membershipId })
|
|
8761
|
+
}), target);
|
|
8762
|
+
const verified = await new RemoteSkillsClient(selected.token, connection.apiUrl).requestWorkspaceSelection("/api/auth/whoami");
|
|
8763
|
+
if (!verified || typeof verified !== "object" || verified.authMethod !== "jwt")
|
|
8764
|
+
throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
|
|
8765
|
+
const identity = parseWorkspaceIdentity(verified, target.userId);
|
|
8766
|
+
if (identity.user.membershipId !== target.membershipId || identity.organization.id !== selected.organization.id)
|
|
8767
|
+
throw new WorkspaceIdentityMismatchError;
|
|
8768
|
+
return { token: selected.token, ...identity };
|
|
8769
|
+
}
|
|
8770
|
+
async requestWorkspaceSelection(path, options) {
|
|
8771
|
+
let response;
|
|
8772
|
+
try {
|
|
8773
|
+
response = await this.request(path, { ...options, credentials: "omit" });
|
|
8774
|
+
} catch {
|
|
8775
|
+
throw new Error("Unable to reach the Skills workspace API.");
|
|
8776
|
+
}
|
|
8777
|
+
let value;
|
|
8778
|
+
try {
|
|
8779
|
+
value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 1024 * 1024 : 4096)));
|
|
8780
|
+
} catch {
|
|
8781
|
+
if (response.ok)
|
|
8782
|
+
throw new Error(invalidWorkspaceResult);
|
|
8783
|
+
}
|
|
8784
|
+
if (!response.ok) {
|
|
8785
|
+
const code = workspaceSelectionFailure(value, response.status);
|
|
8786
|
+
if (code)
|
|
8787
|
+
throw new RemoteWorkspaceSelectionError(path, code);
|
|
8788
|
+
if (response.status === 404 || response.status === 405)
|
|
8789
|
+
throw new RemoteRouteUnsupportedError(path, response.status, this.apiUrl);
|
|
8790
|
+
throw new RemoteRequestError(path, response.status);
|
|
8791
|
+
}
|
|
8792
|
+
return value;
|
|
8793
|
+
}
|
|
8638
8794
|
async updateProfile(input) {
|
|
8639
8795
|
const body = customerNamePatch(input, "displayName");
|
|
8640
8796
|
return parseUpdatedProfile(await (await this.requestNewRoute("/api/v1/account/profile", { method: "PATCH", body: JSON.stringify(body) })).json());
|
|
@@ -8937,13 +9093,13 @@ class RemoteSkillsClient {
|
|
|
8937
9093
|
return normalizeUpdatedSincePage(await response.json());
|
|
8938
9094
|
}
|
|
8939
9095
|
}
|
|
8940
|
-
function requireOptionalString(
|
|
8941
|
-
if (
|
|
9096
|
+
function requireOptionalString(record5, field) {
|
|
9097
|
+
if (record5[field] === undefined)
|
|
8942
9098
|
return;
|
|
8943
|
-
if (typeof
|
|
9099
|
+
if (typeof record5[field] !== "string") {
|
|
8944
9100
|
throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
|
|
8945
9101
|
}
|
|
8946
|
-
return
|
|
9102
|
+
return record5[field];
|
|
8947
9103
|
}
|
|
8948
9104
|
function isVersionRecord(value) {
|
|
8949
9105
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -8965,19 +9121,19 @@ function normalizePin(entry) {
|
|
|
8965
9121
|
if (!entry || typeof entry !== "object") {
|
|
8966
9122
|
throw new Error("Remote pin payload did not match the expected contract (expected an object)");
|
|
8967
9123
|
}
|
|
8968
|
-
const
|
|
8969
|
-
const slug = typeof
|
|
9124
|
+
const record5 = entry;
|
|
9125
|
+
const slug = typeof record5.slug === "string" && record5.slug.trim() ? record5.slug.trim() : undefined;
|
|
8970
9126
|
if (!slug) {
|
|
8971
9127
|
throw new Error("Remote pin payload did not match the expected contract (missing slug)");
|
|
8972
9128
|
}
|
|
8973
9129
|
let metadata;
|
|
8974
|
-
if (
|
|
8975
|
-
if (!
|
|
9130
|
+
if (record5.metadata !== undefined) {
|
|
9131
|
+
if (!record5.metadata || typeof record5.metadata !== "object" || Array.isArray(record5.metadata)) {
|
|
8976
9132
|
throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
|
|
8977
9133
|
}
|
|
8978
|
-
metadata =
|
|
9134
|
+
metadata = record5.metadata;
|
|
8979
9135
|
}
|
|
8980
|
-
const pinnedAt = requireOptionalString(
|
|
9136
|
+
const pinnedAt = requireOptionalString(record5, "pinnedAt");
|
|
8981
9137
|
return {
|
|
8982
9138
|
slug,
|
|
8983
9139
|
...pinnedAt !== undefined ? { pinnedAt } : {},
|
|
@@ -8994,16 +9150,16 @@ function normalizeSkillSummary(entry) {
|
|
|
8994
9150
|
if (!entry || typeof entry !== "object") {
|
|
8995
9151
|
throw new Error("Remote skill payload did not match the expected contract (expected an object)");
|
|
8996
9152
|
}
|
|
8997
|
-
const
|
|
8998
|
-
const slug = typeof
|
|
9153
|
+
const record5 = entry;
|
|
9154
|
+
const slug = typeof record5.slug === "string" && record5.slug.trim() ? record5.slug.trim() : undefined;
|
|
8999
9155
|
if (!slug) {
|
|
9000
9156
|
throw new Error("Remote skill payload did not match the expected contract (missing slug)");
|
|
9001
9157
|
}
|
|
9002
9158
|
return {
|
|
9003
9159
|
slug,
|
|
9004
|
-
...requireOptionalString(
|
|
9005
|
-
...requireOptionalString(
|
|
9006
|
-
...requireOptionalString(
|
|
9160
|
+
...requireOptionalString(record5, "name") !== undefined ? { name: requireOptionalString(record5, "name") } : {},
|
|
9161
|
+
...requireOptionalString(record5, "version") !== undefined ? { version: requireOptionalString(record5, "version") } : {},
|
|
9162
|
+
...requireOptionalString(record5, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record5, "updatedAt") } : {}
|
|
9007
9163
|
};
|
|
9008
9164
|
}
|
|
9009
9165
|
function normalizeSkillSummaryList(payload) {
|
|
@@ -9056,12 +9212,12 @@ function normalizeUpdatedSincePage(payload) {
|
|
|
9056
9212
|
if (!payload || typeof payload !== "object") {
|
|
9057
9213
|
throw new Error("Updated-since payload did not match the expected contract (expected an object)");
|
|
9058
9214
|
}
|
|
9059
|
-
const
|
|
9060
|
-
if (!Array.isArray(
|
|
9215
|
+
const record5 = payload;
|
|
9216
|
+
if (!Array.isArray(record5.skills)) {
|
|
9061
9217
|
throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
|
|
9062
9218
|
}
|
|
9063
|
-
const skills =
|
|
9064
|
-
const nextCursor =
|
|
9219
|
+
const skills = record5.skills.map(normalizeSkillSummary);
|
|
9220
|
+
const nextCursor = record5.nextCursor === undefined || record5.nextCursor === null ? null : record5.nextCursor;
|
|
9065
9221
|
if (nextCursor !== null && typeof nextCursor !== "string") {
|
|
9066
9222
|
throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
|
|
9067
9223
|
}
|
|
@@ -9074,8 +9230,9 @@ async function createRemoteSkillsClient(env = process.env) {
|
|
|
9074
9230
|
function createRemoteSkillsClientReadOnly(env = process.env) {
|
|
9075
9231
|
return createRemoteSkillsClient(env);
|
|
9076
9232
|
}
|
|
9077
|
-
var RemoteRouteUnsupportedError, RemoteRequestError, RemoteWorkspaceMemberError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
|
|
9233
|
+
var RemoteRouteUnsupportedError, RemoteRequestError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
|
|
9078
9234
|
var init_remote_client = __esm(() => {
|
|
9235
|
+
init_remote_workspace_selection();
|
|
9079
9236
|
init_remote_workspace();
|
|
9080
9237
|
init_remote_workspace();
|
|
9081
9238
|
init_auth_store();
|
|
@@ -9113,6 +9270,15 @@ var init_remote_client = __esm(() => {
|
|
|
9113
9270
|
this.message = workspaceMemberFailures[code][1];
|
|
9114
9271
|
}
|
|
9115
9272
|
};
|
|
9273
|
+
RemoteWorkspaceSelectionError = class RemoteWorkspaceSelectionError extends RemoteRequestError {
|
|
9274
|
+
code;
|
|
9275
|
+
constructor(path, code) {
|
|
9276
|
+
super(path, workspaceSelectionFailures[code][0]);
|
|
9277
|
+
this.code = code;
|
|
9278
|
+
this.name = "RemoteWorkspaceSelectionError";
|
|
9279
|
+
this.message = workspaceSelectionFailures[code][1];
|
|
9280
|
+
}
|
|
9281
|
+
};
|
|
9116
9282
|
RemoteCapabilityUnavailableError = class RemoteCapabilityUnavailableError extends RemoteRequestError {
|
|
9117
9283
|
code = "SUBSCRIPTION_CHECKOUT_UNAVAILABLE";
|
|
9118
9284
|
constructor() {
|
|
@@ -14639,7 +14805,7 @@ class StdioServerTransport {
|
|
|
14639
14805
|
// package.json
|
|
14640
14806
|
var package_default = {
|
|
14641
14807
|
name: "@hasna/skills",
|
|
14642
|
-
version: "0.5.
|
|
14808
|
+
version: "0.5.1",
|
|
14643
14809
|
description: "Skills library for AI coding agents",
|
|
14644
14810
|
type: "module",
|
|
14645
14811
|
bin: {
|
|
@@ -27945,7 +28111,7 @@ function registerDiscoveryTools(server) {
|
|
|
27945
28111
|
|
|
27946
28112
|
// src/mcp/operation-tools.ts
|
|
27947
28113
|
import { existsSync as existsSync14, readdirSync as readdirSync8, statSync as statSync9 } from "fs";
|
|
27948
|
-
import { join as
|
|
28114
|
+
import { join as join15 } from "path";
|
|
27949
28115
|
|
|
27950
28116
|
// src/lib/credential-state.ts
|
|
27951
28117
|
init_auth_store();
|
|
@@ -27999,19 +28165,19 @@ function describeCredentialState() {
|
|
|
27999
28165
|
// src/lib/run-state.ts
|
|
28000
28166
|
import { createHash as createHash2, randomBytes } from "crypto";
|
|
28001
28167
|
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
|
|
28168
|
+
import { extname, join as join14, relative as relative2 } from "path";
|
|
28003
28169
|
function createSkillRun(params, targetDir = process.cwd()) {
|
|
28004
28170
|
const now = new Date;
|
|
28005
28171
|
const id = createRunId(now);
|
|
28006
28172
|
const day = now.toISOString().slice(0, 10);
|
|
28007
28173
|
const skillName = normalizeSkillName(params.skill);
|
|
28008
28174
|
const root = getProjectStateDir(targetDir);
|
|
28009
|
-
const runDir =
|
|
28010
|
-
const logsDir =
|
|
28011
|
-
const exportDir =
|
|
28175
|
+
const runDir = join14(root, "runs", day, id);
|
|
28176
|
+
const logsDir = join14(runDir, "logs");
|
|
28177
|
+
const exportDir = join14(root, "exports", skillName, id);
|
|
28012
28178
|
mkdirSync6(logsDir, { recursive: true });
|
|
28013
28179
|
mkdirSync6(exportDir, { recursive: true });
|
|
28014
|
-
mkdirSync6(
|
|
28180
|
+
mkdirSync6(join14(root, "tmp"), { recursive: true });
|
|
28015
28181
|
const record3 = {
|
|
28016
28182
|
id,
|
|
28017
28183
|
skill: skillName,
|
|
@@ -28063,22 +28229,22 @@ function updateSkillRun(context, patch) {
|
|
|
28063
28229
|
return context.record;
|
|
28064
28230
|
}
|
|
28065
28231
|
function writeRunLogs(context, stdout = "", stderr = "") {
|
|
28066
|
-
writeFileSync6(
|
|
28067
|
-
writeFileSync6(
|
|
28232
|
+
writeFileSync6(join14(context.logsDir, "stdout.log"), stdout);
|
|
28233
|
+
writeFileSync6(join14(context.logsDir, "stderr.log"), stderr);
|
|
28068
28234
|
}
|
|
28069
28235
|
function appendRunEvent(context, event, data = {}) {
|
|
28070
28236
|
const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
|
|
28071
28237
|
`;
|
|
28072
|
-
const path =
|
|
28238
|
+
const path = join14(context.runDir, "events.ndjson");
|
|
28073
28239
|
const previous = existsSync13(path) ? readFileSync12(path, "utf-8") : "";
|
|
28074
28240
|
writeFileSync6(path, previous + line);
|
|
28075
28241
|
}
|
|
28076
28242
|
function findSkillRun(runId, targetDir = process.cwd()) {
|
|
28077
|
-
const runsRoot =
|
|
28243
|
+
const runsRoot = join14(getProjectStateDir(targetDir), "runs");
|
|
28078
28244
|
if (!existsSync13(runsRoot))
|
|
28079
28245
|
return null;
|
|
28080
28246
|
for (const day of readdirSync7(runsRoot)) {
|
|
28081
|
-
const record3 = readRunRecord(
|
|
28247
|
+
const record3 = readRunRecord(join14(runsRoot, day, runId));
|
|
28082
28248
|
if (record3)
|
|
28083
28249
|
return record3;
|
|
28084
28250
|
}
|
|
@@ -28095,11 +28261,11 @@ function skillRunEnv(context) {
|
|
|
28095
28261
|
};
|
|
28096
28262
|
}
|
|
28097
28263
|
function writeRunRecord(context) {
|
|
28098
|
-
writeFileSync6(
|
|
28264
|
+
writeFileSync6(join14(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
|
|
28099
28265
|
`);
|
|
28100
28266
|
}
|
|
28101
28267
|
function writeArtifactsManifest(context, artifacts) {
|
|
28102
|
-
writeFileSync6(
|
|
28268
|
+
writeFileSync6(join14(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
|
|
28103
28269
|
`);
|
|
28104
28270
|
}
|
|
28105
28271
|
function collectRunArtifacts(context) {
|
|
@@ -28119,7 +28285,7 @@ function collectRunArtifacts(context) {
|
|
|
28119
28285
|
return artifacts.sort((a, b) => a.path.localeCompare(b.path));
|
|
28120
28286
|
}
|
|
28121
28287
|
function readRunRecord(runDir) {
|
|
28122
|
-
const path =
|
|
28288
|
+
const path = join14(runDir, "run.json");
|
|
28123
28289
|
if (!existsSync13(path))
|
|
28124
28290
|
return null;
|
|
28125
28291
|
try {
|
|
@@ -28131,7 +28297,7 @@ function readRunRecord(runDir) {
|
|
|
28131
28297
|
function walkFiles(dir) {
|
|
28132
28298
|
const files = [];
|
|
28133
28299
|
for (const entry of readdirSync7(dir)) {
|
|
28134
|
-
const full =
|
|
28300
|
+
const full = join14(dir, entry);
|
|
28135
28301
|
if (statSync8(full).isDirectory())
|
|
28136
28302
|
files.push(...walkFiles(full));
|
|
28137
28303
|
else
|
|
@@ -28638,7 +28804,7 @@ function registerOperationTools(server) {
|
|
|
28638
28804
|
if (exists) {
|
|
28639
28805
|
try {
|
|
28640
28806
|
skillCount = readdirSync8(agentSkillsPath).filter((f) => {
|
|
28641
|
-
const full =
|
|
28807
|
+
const full = join15(agentSkillsPath, f);
|
|
28642
28808
|
return !f.startsWith(".") && statSync9(full).isDirectory();
|
|
28643
28809
|
}).length;
|
|
28644
28810
|
} catch {}
|
|
@@ -28686,14 +28852,14 @@ function compactRunToolPayload(payload, detailHint) {
|
|
|
28686
28852
|
|
|
28687
28853
|
// src/lib/feedback.ts
|
|
28688
28854
|
import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync7 } from "fs";
|
|
28689
|
-
import { dirname as
|
|
28855
|
+
import { dirname as dirname6, join as join16 } from "path";
|
|
28690
28856
|
import { Database } from "bun:sqlite";
|
|
28691
28857
|
function getFeedbackDbPath() {
|
|
28692
|
-
return
|
|
28858
|
+
return join16(getDataDir(), "skills.db");
|
|
28693
28859
|
}
|
|
28694
28860
|
function getFeedbackDb() {
|
|
28695
28861
|
const dbPath = getFeedbackDbPath();
|
|
28696
|
-
const dir =
|
|
28862
|
+
const dir = dirname6(dbPath);
|
|
28697
28863
|
if (!existsSync15(dir))
|
|
28698
28864
|
mkdirSync7(dir, { recursive: true });
|
|
28699
28865
|
const db = new Database(dbPath);
|
|
@@ -28721,8 +28887,8 @@ function saveFeedback(input) {
|
|
|
28721
28887
|
throw new Error("Feedback message is required");
|
|
28722
28888
|
const category = input.category ?? "general";
|
|
28723
28889
|
if (isApiMode()) {
|
|
28724
|
-
const path =
|
|
28725
|
-
const dir =
|
|
28890
|
+
const path = join16(getDataDir(), "feedback.jsonl");
|
|
28891
|
+
const dir = dirname6(path);
|
|
28726
28892
|
if (!existsSync15(dir))
|
|
28727
28893
|
mkdirSync7(dir, { recursive: true });
|
|
28728
28894
|
appendFileSync(path, JSON.stringify({ message, category, email: input.email ?? null, agent: input.agent ?? null, version: input.version ?? null, createdAt: new Date().toISOString() }) + `
|
|
@@ -28870,9 +29036,9 @@ function registerResourceMetaTools(server) {
|
|
|
28870
29036
|
|
|
28871
29037
|
// src/lib/scheduler.ts
|
|
28872
29038
|
import { existsSync as existsSync16, readFileSync as readFileSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "fs";
|
|
28873
|
-
import { join as
|
|
29039
|
+
import { join as join17 } from "path";
|
|
28874
29040
|
function getSchedulesPath(targetDir = process.cwd()) {
|
|
28875
|
-
return
|
|
29041
|
+
return join17(targetDir, ".skills", "schedules.json");
|
|
28876
29042
|
}
|
|
28877
29043
|
function loadSchedules(targetDir = process.cwd()) {
|
|
28878
29044
|
const path = getSchedulesPath(targetDir);
|
|
@@ -28885,7 +29051,7 @@ function loadSchedules(targetDir = process.cwd()) {
|
|
|
28885
29051
|
}
|
|
28886
29052
|
function saveSchedules(data, targetDir = process.cwd()) {
|
|
28887
29053
|
const path = getSchedulesPath(targetDir);
|
|
28888
|
-
const dir =
|
|
29054
|
+
const dir = join17(targetDir, ".skills");
|
|
28889
29055
|
if (!existsSync16(dir))
|
|
28890
29056
|
mkdirSync8(dir, { recursive: true });
|
|
28891
29057
|
writeFileSync7(path, JSON.stringify(data, null, 2));
|
|
@@ -29155,7 +29321,7 @@ import {
|
|
|
29155
29321
|
statSync as statSync10,
|
|
29156
29322
|
writeFileSync as writeFileSync8
|
|
29157
29323
|
} from "fs";
|
|
29158
|
-
import { dirname as
|
|
29324
|
+
import { dirname as dirname7, join as join18, normalize as normalize3, relative as relative3, sep as sep2 } from "path";
|
|
29159
29325
|
var SKILLS_STORAGE_TABLES = [
|
|
29160
29326
|
"skills_sync_records",
|
|
29161
29327
|
"skills_sync_cursors"
|
|
@@ -29231,7 +29397,7 @@ function getSkillsNativeStorageStatus(options = {}) {
|
|
|
29231
29397
|
local: {
|
|
29232
29398
|
dataDir: getDataDir(),
|
|
29233
29399
|
projectStateDir: getProjectStateDir(targetDir),
|
|
29234
|
-
feedbackDbPath:
|
|
29400
|
+
feedbackDbPath: join18(getDataDir(), "skills.db")
|
|
29235
29401
|
},
|
|
29236
29402
|
remote: {
|
|
29237
29403
|
databaseConfigured: Boolean(config2.databaseUrl),
|
|
@@ -29335,7 +29501,7 @@ function parsePositiveInteger(value) {
|
|
|
29335
29501
|
function walkFiles2(dir) {
|
|
29336
29502
|
const files = [];
|
|
29337
29503
|
for (const entry of readdirSync9(dir)) {
|
|
29338
|
-
const full =
|
|
29504
|
+
const full = join18(dir, entry);
|
|
29339
29505
|
const stats = statSync10(full);
|
|
29340
29506
|
if (stats.isDirectory())
|
|
29341
29507
|
files.push(...walkFiles2(full));
|
|
@@ -29391,6 +29557,8 @@ function registerStorageTools(server) {
|
|
|
29391
29557
|
}
|
|
29392
29558
|
|
|
29393
29559
|
// src/lib/remote-auth.ts
|
|
29560
|
+
init_remote_workspace_selection();
|
|
29561
|
+
init_remote_files();
|
|
29394
29562
|
init_remote_workspace();
|
|
29395
29563
|
init_remote_workspace();
|
|
29396
29564
|
init_remote_client();
|
|
@@ -29431,13 +29599,13 @@ async function requestAuthApi(instance, path, options) {
|
|
|
29431
29599
|
apiUrl: safeUrl
|
|
29432
29600
|
});
|
|
29433
29601
|
}
|
|
29434
|
-
const
|
|
29435
|
-
const body =
|
|
29602
|
+
const text2 = await res.text();
|
|
29603
|
+
const body = text2 ? parseJsonBody(text2) : {};
|
|
29436
29604
|
if (!res.ok) {
|
|
29437
|
-
const
|
|
29438
|
-
const detail = typeof
|
|
29439
|
-
const error2 = typeof
|
|
29440
|
-
const code = typeof
|
|
29605
|
+
const record5 = isRecord5(body) ? body : {};
|
|
29606
|
+
const detail = typeof record5.detail === "string" ? record5.detail : undefined;
|
|
29607
|
+
const error2 = typeof record5.error === "string" ? record5.error : undefined;
|
|
29608
|
+
const code = typeof record5.code === "string" ? record5.code : undefined;
|
|
29441
29609
|
throw new HostedApiError(detail || error2 || `${res.status} ${res.statusText}`, {
|
|
29442
29610
|
status: res.status,
|
|
29443
29611
|
code,
|
|
@@ -29448,15 +29616,15 @@ async function requestAuthApi(instance, path, options) {
|
|
|
29448
29616
|
}
|
|
29449
29617
|
return body;
|
|
29450
29618
|
}
|
|
29451
|
-
function parseJsonBody(
|
|
29619
|
+
function parseJsonBody(text2) {
|
|
29452
29620
|
try {
|
|
29453
|
-
return JSON.parse(
|
|
29621
|
+
return JSON.parse(text2);
|
|
29454
29622
|
} catch {
|
|
29455
|
-
return { detail: condenseErrorBody(
|
|
29623
|
+
return { detail: condenseErrorBody(text2) };
|
|
29456
29624
|
}
|
|
29457
29625
|
}
|
|
29458
|
-
function condenseErrorBody(
|
|
29459
|
-
const stripped = /<[a-z!/]/i.test(
|
|
29626
|
+
function condenseErrorBody(text2) {
|
|
29627
|
+
const stripped = /<[a-z!/]/i.test(text2) ? text2.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text2;
|
|
29460
29628
|
const collapsed = stripped.replace(/\s+/g, " ").trim();
|
|
29461
29629
|
if (collapsed.length <= MAX_ERROR_DETAIL_LENGTH)
|
|
29462
29630
|
return collapsed;
|
|
@@ -29483,7 +29651,12 @@ class RemoteSkillsAuthClient {
|
|
|
29483
29651
|
pollDevice(deviceCode) {
|
|
29484
29652
|
return this.request("/api/auth/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) });
|
|
29485
29653
|
}
|
|
29486
|
-
async sessionClient(email2, code) {
|
|
29654
|
+
async sessionClient(email2, code, context) {
|
|
29655
|
+
if (context !== undefined) {
|
|
29656
|
+
const target = workspaceContext(context), apiOrigin2 = this.apiOrigin;
|
|
29657
|
+
const session = await this.switchWorkspace(email2, code, target);
|
|
29658
|
+
return new RemoteSkillsClient(session.token, apiOrigin2);
|
|
29659
|
+
}
|
|
29487
29660
|
const apiOrigin = this.apiOrigin;
|
|
29488
29661
|
if (!email2.includes("@") || !/^\d{6}$/.test(code))
|
|
29489
29662
|
throw new Error("Fresh email and six-digit verification code are required to manage this account");
|
|
@@ -29492,34 +29665,76 @@ class RemoteSkillsAuthClient {
|
|
|
29492
29665
|
throw new Error("The server did not return an authorized account session");
|
|
29493
29666
|
return new RemoteSkillsClient(login.token, apiOrigin);
|
|
29494
29667
|
}
|
|
29495
|
-
async
|
|
29496
|
-
|
|
29668
|
+
async listAccountWorkspaces(email2, code, expectedUserId) {
|
|
29669
|
+
const login = await this.workspaceLogin(email2, code, expectedUserId);
|
|
29670
|
+
const result = await new RemoteSkillsClient(login.token, login.apiOrigin).listAccountWorkspaces(login.userId);
|
|
29671
|
+
return { userId: login.userId, ...result };
|
|
29497
29672
|
}
|
|
29498
|
-
async
|
|
29499
|
-
|
|
29673
|
+
async switchWorkspace(email2, code, context) {
|
|
29674
|
+
const target = workspaceContext(context);
|
|
29675
|
+
const login = await this.workspaceLogin(email2, code, target.userId);
|
|
29676
|
+
return new RemoteSkillsClient(login.token, login.apiOrigin).switchWorkspace(target);
|
|
29500
29677
|
}
|
|
29501
|
-
async
|
|
29502
|
-
|
|
29678
|
+
async workspaceLogin(email2, code, expectedUserId) {
|
|
29679
|
+
const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
|
|
29680
|
+
const apiOrigin = this.apiOrigin;
|
|
29681
|
+
if (typeof email2 !== "string" || !email2.includes("@") || typeof code !== "string" || !/^\d{6}$/.test(code))
|
|
29682
|
+
throw new Error("Fresh email and six-digit verification code are required to manage this account");
|
|
29683
|
+
let response;
|
|
29684
|
+
try {
|
|
29685
|
+
response = await fetch(`${apiOrigin}/api/auth/verify`, {
|
|
29686
|
+
method: "POST",
|
|
29687
|
+
redirect: "error",
|
|
29688
|
+
credentials: "omit",
|
|
29689
|
+
signal: AbortSignal.timeout(15000),
|
|
29690
|
+
headers: { "Content-Type": "application/json" },
|
|
29691
|
+
body: JSON.stringify({ email: email2, code })
|
|
29692
|
+
});
|
|
29693
|
+
} catch {
|
|
29694
|
+
throw new HostedApiError("Unable to verify the Skills account.");
|
|
29695
|
+
}
|
|
29696
|
+
if (!response.ok) {
|
|
29697
|
+
response.body?.cancel().catch(() => {});
|
|
29698
|
+
throw new HostedApiError("Unable to verify the Skills account.", { status: response.status });
|
|
29699
|
+
}
|
|
29700
|
+
let value;
|
|
29701
|
+
try {
|
|
29702
|
+
value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 64 * 1024)));
|
|
29703
|
+
} catch {
|
|
29704
|
+
throw new HostedApiError("The server returned an invalid account verification result.");
|
|
29705
|
+
}
|
|
29706
|
+
return { ...parseWorkspaceLogin(value, expected), apiOrigin };
|
|
29707
|
+
}
|
|
29708
|
+
async createApiKey(email2, code, name, scopes, context) {
|
|
29709
|
+
const capturedScopes = scopes === undefined ? undefined : [...scopes];
|
|
29710
|
+
return (await this.sessionClient(email2, code, context)).createApiKey(name, capturedScopes);
|
|
29711
|
+
}
|
|
29712
|
+
async listApiKeys(email2, code, context) {
|
|
29713
|
+
return (await this.sessionClient(email2, code, context)).listApiKeys();
|
|
29714
|
+
}
|
|
29715
|
+
async revokeApiKey(email2, code, keyId, context) {
|
|
29716
|
+
return (await this.sessionClient(email2, code, context)).revokeApiKey(keyId);
|
|
29503
29717
|
}
|
|
29504
|
-
async updateProfile(email2, code, input) {
|
|
29505
|
-
customerNamePatch(input, "displayName");
|
|
29506
|
-
return (await this.sessionClient(email2, code)).updateProfile(
|
|
29718
|
+
async updateProfile(email2, code, input, context) {
|
|
29719
|
+
const body = customerNamePatch(input, "displayName");
|
|
29720
|
+
return (await this.sessionClient(email2, code, context)).updateProfile({ displayName: body.displayName });
|
|
29507
29721
|
}
|
|
29508
|
-
async updateCurrentWorkspace(email2, code, input) {
|
|
29509
|
-
customerNamePatch(input, "name");
|
|
29510
|
-
return (await this.sessionClient(email2, code)).updateCurrentWorkspace(
|
|
29722
|
+
async updateCurrentWorkspace(email2, code, input, context) {
|
|
29723
|
+
const body = customerNamePatch(input, "name");
|
|
29724
|
+
return (await this.sessionClient(email2, code, context)).updateCurrentWorkspace({ name: body.name });
|
|
29511
29725
|
}
|
|
29512
|
-
async listWorkspaceMembers(email2, code, options = {}) {
|
|
29726
|
+
async listWorkspaceMembers(email2, code, options = {}, context) {
|
|
29513
29727
|
workspaceMembersQuery(options);
|
|
29514
|
-
|
|
29728
|
+
const captured = { ...options };
|
|
29729
|
+
return (await this.sessionClient(email2, code, context)).listWorkspaceMembers(captured);
|
|
29515
29730
|
}
|
|
29516
|
-
async setWorkspaceMemberRole(email2, code, membershipId, input) {
|
|
29731
|
+
async setWorkspaceMemberRole(email2, code, membershipId, input, context) {
|
|
29517
29732
|
const captured = workspaceMemberRoleInput(membershipId, input);
|
|
29518
|
-
return (await this.sessionClient(email2, code)).setWorkspaceMemberRole(captured.membershipId, captured.body);
|
|
29733
|
+
return (await this.sessionClient(email2, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
|
|
29519
29734
|
}
|
|
29520
|
-
async removeWorkspaceMember(email2, code, membershipId, input) {
|
|
29735
|
+
async removeWorkspaceMember(email2, code, membershipId, input, context) {
|
|
29521
29736
|
const captured = workspaceMemberRemovalInput(membershipId, input);
|
|
29522
|
-
return (await this.sessionClient(email2, code)).removeWorkspaceMember(captured.membershipId, captured.body);
|
|
29737
|
+
return (await this.sessionClient(email2, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
|
|
29523
29738
|
}
|
|
29524
29739
|
request(path, options) {
|
|
29525
29740
|
if (!["/api/auth/login", "/api/auth/verify", "/api/auth/device/start", "/api/auth/device/token", "/api/auth/keys", "/api/auth/whoami"].includes(path))
|
|
@@ -29528,8 +29743,87 @@ class RemoteSkillsAuthClient {
|
|
|
29528
29743
|
}
|
|
29529
29744
|
}
|
|
29530
29745
|
|
|
29531
|
-
// src/
|
|
29746
|
+
// src/lib/workspace-profile.ts
|
|
29747
|
+
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
29748
|
init_auth_store();
|
|
29749
|
+
init_fleet_credentials();
|
|
29750
|
+
init_instance_credentials();
|
|
29751
|
+
init_remote_client();
|
|
29752
|
+
init_remote_workspace_selection();
|
|
29753
|
+
|
|
29754
|
+
class WorkspaceProfileError extends Error {
|
|
29755
|
+
}
|
|
29756
|
+
var fail = (message) => {
|
|
29757
|
+
throw new WorkspaceProfileError(message);
|
|
29758
|
+
};
|
|
29759
|
+
function stat(path) {
|
|
29760
|
+
try {
|
|
29761
|
+
return lstatSync4(path);
|
|
29762
|
+
} catch (error2) {
|
|
29763
|
+
if (error2.code === "ENOENT")
|
|
29764
|
+
return null;
|
|
29765
|
+
throw error2;
|
|
29766
|
+
}
|
|
29767
|
+
}
|
|
29768
|
+
function safeText(file) {
|
|
29769
|
+
if (stat(file) === null)
|
|
29770
|
+
return null;
|
|
29771
|
+
const fd = openSync3(file, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
|
|
29772
|
+
try {
|
|
29773
|
+
const s = fstatSync3(fd);
|
|
29774
|
+
if (!s.isFile() || s.size > 65536 || ![256, 384].includes(s.mode & 4095) || process.getuid && s.uid !== process.getuid())
|
|
29775
|
+
return fail("The selected profile must use bounded owner-only regular files.");
|
|
29776
|
+
return readFileSync15(fd, "utf8");
|
|
29777
|
+
} finally {
|
|
29778
|
+
closeSync3(fd);
|
|
29779
|
+
}
|
|
29780
|
+
}
|
|
29781
|
+
function checkIdentityMetadata(file, identity) {
|
|
29782
|
+
const text2 = safeText(file);
|
|
29783
|
+
if (text2 === null)
|
|
29784
|
+
return;
|
|
29785
|
+
let value;
|
|
29786
|
+
try {
|
|
29787
|
+
value = JSON.parse(text2);
|
|
29788
|
+
} catch {
|
|
29789
|
+
return fail("The profile identity metadata is invalid. Sign in again before managing this workspace.");
|
|
29790
|
+
}
|
|
29791
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
29792
|
+
return fail("The profile identity metadata is invalid.");
|
|
29793
|
+
for (const [key, expected] of Object.entries({ userId: identity.user.id, orgId: identity.organization.id })) {
|
|
29794
|
+
if (value[key] !== undefined && value[key] !== expected)
|
|
29795
|
+
return fail("The profile identity metadata does not match its authenticated key. Sign in again before managing this workspace.");
|
|
29796
|
+
}
|
|
29797
|
+
}
|
|
29798
|
+
async function keyIdentity(key, origin) {
|
|
29799
|
+
const value = await new RemoteSkillsClient(key, origin).getIdentity();
|
|
29800
|
+
if (value.authMethod !== "api_key")
|
|
29801
|
+
return fail("The selected credential is not a workspace API key.");
|
|
29802
|
+
const user = value.user;
|
|
29803
|
+
return parseWorkspaceIdentity(value, workspaceExpectedUserId(user?.id));
|
|
29804
|
+
}
|
|
29805
|
+
function prepareProfileWorkspace(action, source = process.env) {
|
|
29806
|
+
const env = { ...source }, origin = getApiUrl(action, env), profile = selectedSkillsProfile(env);
|
|
29807
|
+
const unchanged2 = captureSkillsCredentialFiles(skillsProfileCredentialFiles(env));
|
|
29808
|
+
return { origin, async resolve() {
|
|
29809
|
+
unchanged2();
|
|
29810
|
+
if (!profile)
|
|
29811
|
+
return { origin, context: undefined, unchanged: unchanged2 };
|
|
29812
|
+
const connection = await resolveSkillsConnection(env);
|
|
29813
|
+
if (!connection || connection.apiOrigin !== origin)
|
|
29814
|
+
return fail("The selected profile has no usable credential for this server.");
|
|
29815
|
+
const identity = await keyIdentity(connection.apiKey, origin);
|
|
29816
|
+
checkIdentityMetadata(getIdentityFilePath(env), identity);
|
|
29817
|
+
unchanged2();
|
|
29818
|
+
const context = { userId: identity.user.id, membershipId: identity.user.membershipId };
|
|
29819
|
+
return { origin, context, unchanged: unchanged2 };
|
|
29820
|
+
} };
|
|
29821
|
+
}
|
|
29822
|
+
async function captureProfileWorkspace(action, source = process.env) {
|
|
29823
|
+
return prepareProfileWorkspace(action, source).resolve();
|
|
29824
|
+
}
|
|
29825
|
+
|
|
29826
|
+
// src/mcp/remote-customer-tools.ts
|
|
29533
29827
|
init_remote_client();
|
|
29534
29828
|
function registerRemoteCustomerTools(server) {
|
|
29535
29829
|
const memberRole = exports_external.enum(["owner", "admin", "member", "viewer"]);
|
|
@@ -29543,9 +29837,9 @@ function registerRemoteCustomerTools(server) {
|
|
|
29543
29837
|
title: "Set Current Workspace Member Role",
|
|
29544
29838
|
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
29839
|
inputSchema: exports_external.object({ ...memberInput, role: memberRole }).strict()
|
|
29546
|
-
}, async ({ membershipId, role, expectedRole, email: email2, code }) => {
|
|
29840
|
+
}, async ({ membershipId, role: role2, expectedRole, email: email2, code }) => {
|
|
29547
29841
|
try {
|
|
29548
|
-
return mcpJson(await
|
|
29842
|
+
return mcpJson(await freshAccount("Set workspace member role", (client, context) => client.setWorkspaceMemberRole(email2, code, membershipId, { role: role2, expectedRole }, context)));
|
|
29549
29843
|
} catch (error2) {
|
|
29550
29844
|
return memberError(error2);
|
|
29551
29845
|
}
|
|
@@ -29556,7 +29850,7 @@ function registerRemoteCustomerTools(server) {
|
|
|
29556
29850
|
inputSchema: exports_external.object(memberInput).strict()
|
|
29557
29851
|
}, async ({ membershipId, expectedRole, email: email2, code }) => {
|
|
29558
29852
|
try {
|
|
29559
|
-
return mcpJson(await
|
|
29853
|
+
return mcpJson(await freshAccount("Remove workspace member", (client, context) => client.removeWorkspaceMember(email2, code, membershipId, { expectedRole }, context)));
|
|
29560
29854
|
} catch (error2) {
|
|
29561
29855
|
return memberError(error2);
|
|
29562
29856
|
}
|
|
@@ -29572,7 +29866,7 @@ function registerRemoteCustomerTools(server) {
|
|
|
29572
29866
|
}).strict()
|
|
29573
29867
|
}, async ({ email: email2, code, limit, cursor: cursor2 }) => {
|
|
29574
29868
|
try {
|
|
29575
|
-
return mcpJson(await
|
|
29869
|
+
return mcpJson(await freshAccount("List workspace members", (client, context) => client.listWorkspaceMembers(email2, code, { limit, cursor: cursor2 }, context)));
|
|
29576
29870
|
} catch {
|
|
29577
29871
|
return mcpError("WORKSPACE_MEMBERS_FAILED", "Unable to list workspace members. Check the selected server, owner/admin permissions, pagination and fresh verification code.");
|
|
29578
29872
|
}
|
|
@@ -29584,8 +29878,7 @@ function registerRemoteCustomerTools(server) {
|
|
|
29584
29878
|
inputSchema: exports_external.object({ name: exports_external.string().min(1), email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }).strict()
|
|
29585
29879
|
}, async ({ name, email: email2, code }) => {
|
|
29586
29880
|
try {
|
|
29587
|
-
|
|
29588
|
-
return mcpJson(kind === "profile" ? await client.updateProfile(email2, code, { displayName: name }) : await client.updateCurrentWorkspace(email2, code, { name }));
|
|
29881
|
+
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
29882
|
} catch {
|
|
29590
29883
|
return mcpError("NAME_UPDATE_FAILED", "Unable to update the name. Check the selected server, name, permissions and fresh verification code.");
|
|
29591
29884
|
}
|
|
@@ -29607,9 +29900,9 @@ function registerRemoteCustomerTools(server) {
|
|
|
29607
29900
|
inputSchema: { email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }
|
|
29608
29901
|
}, async ({ email: email2, code }) => {
|
|
29609
29902
|
try {
|
|
29610
|
-
return mcpJson(await
|
|
29611
|
-
} catch
|
|
29612
|
-
return mcpError("KEY_LIST_FAILED",
|
|
29903
|
+
return mcpJson(await freshAccount("List API keys", (client, context) => client.listApiKeys(email2, code, context)));
|
|
29904
|
+
} catch {
|
|
29905
|
+
return mcpError("KEY_LIST_FAILED", "Unable to list API keys. Check the selected profile, server, account and fresh verification code.");
|
|
29613
29906
|
}
|
|
29614
29907
|
});
|
|
29615
29908
|
server.registerTool("revoke_api_key", {
|
|
@@ -29618,9 +29911,9 @@ function registerRemoteCustomerTools(server) {
|
|
|
29618
29911
|
inputSchema: { key_id: exports_external.string().min(1), email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }
|
|
29619
29912
|
}, async ({ key_id, email: email2, code }) => {
|
|
29620
29913
|
try {
|
|
29621
|
-
return mcpJson(await
|
|
29622
|
-
} catch
|
|
29623
|
-
return mcpError("KEY_REVOKE_FAILED",
|
|
29914
|
+
return mcpJson(await freshAccount("Revoke API key", (client, context) => client.revokeApiKey(email2, code, key_id, context)));
|
|
29915
|
+
} catch {
|
|
29916
|
+
return mcpError("KEY_REVOKE_FAILED", "Unable to revoke this API key. Check the selected profile, key, account and fresh verification code.");
|
|
29624
29917
|
}
|
|
29625
29918
|
});
|
|
29626
29919
|
server.registerTool("create_api_key", {
|
|
@@ -29628,10 +29921,11 @@ function registerRemoteCustomerTools(server) {
|
|
|
29628
29921
|
description: "Create an API key using fresh email OTP reauthentication; returns its secret once. A stored API key cannot grant this authority.",
|
|
29629
29922
|
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
29923
|
}, async ({ name, email: email2, code, scopes }) => {
|
|
29924
|
+
const capturedScopes = scopes === undefined ? undefined : [...scopes];
|
|
29631
29925
|
try {
|
|
29632
|
-
return mcpJson(await
|
|
29633
|
-
} catch
|
|
29634
|
-
return mcpError("KEY_CREATION_FAILED",
|
|
29926
|
+
return mcpJson(await freshAccount("Create API key", (client, context) => client.createApiKey(email2, code, name, capturedScopes, context)));
|
|
29927
|
+
} catch {
|
|
29928
|
+
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
29929
|
}
|
|
29636
29930
|
});
|
|
29637
29931
|
server.registerTool("quote_skill", {
|
|
@@ -29665,6 +29959,11 @@ async function callRemote(action) {
|
|
|
29665
29959
|
return mcpError("REMOTE_REQUEST_FAILED", error2 instanceof Error ? error2.message : "Skills server request failed");
|
|
29666
29960
|
}
|
|
29667
29961
|
}
|
|
29962
|
+
async function freshAccount(action, operation) {
|
|
29963
|
+
const target = await captureProfileWorkspace(action);
|
|
29964
|
+
target.unchanged();
|
|
29965
|
+
return operation(new RemoteSkillsAuthClient(target.origin), target.context);
|
|
29966
|
+
}
|
|
29668
29967
|
|
|
29669
29968
|
// src/mcp/server.ts
|
|
29670
29969
|
function buildServer() {
|