@jarel/myskills 0.1.0-alpha.0 → 0.1.0-alpha.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 +7 -3
- package/dist/index.js +262 -50
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -37,7 +37,8 @@ myskills validate --path <file-directory-or-zip>
|
|
|
37
37
|
myskills scan --path <file-directory-or-zip>
|
|
38
38
|
myskills search [query] [--api-url <url>]
|
|
39
39
|
myskills info <skill-slug> [--api-url <url>]
|
|
40
|
-
myskills login [--api-url <url>] [--email <email>]
|
|
40
|
+
myskills login [--api-url <url>] [--method <password|api-key>] [--email <email>]
|
|
41
|
+
myskills login --api-key [--api-url <url>]
|
|
41
42
|
myskills logout [--api-url <url>] [--token <token>]
|
|
42
43
|
myskills whoami [--api-url <url>] [--token <token>]
|
|
43
44
|
myskills submit --path <file-directory-or-zip> [--api-url <url>] [--token <token>]
|
|
@@ -60,6 +61,7 @@ The first public alpha package is distributed through npm under the `alpha` tag:
|
|
|
60
61
|
```bash
|
|
61
62
|
npm install -g @jarel/myskills@alpha
|
|
62
63
|
myskills --version
|
|
64
|
+
myskills login
|
|
63
65
|
```
|
|
64
66
|
|
|
65
67
|
Update the CLI with:
|
|
@@ -68,9 +70,11 @@ Update the CLI with:
|
|
|
68
70
|
npm install -g @jarel/myskills@alpha
|
|
69
71
|
```
|
|
70
72
|
|
|
71
|
-
`validate`, `scan`, and `submit` accept a manifest file, package directory, or local `.zip` package. `login` prompts for the
|
|
73
|
+
`validate`, `scan`, and `submit` accept a manifest file, package directory, or local `.zip` package. `login` prompts for the API URL when one is not supplied; the default is the local API at `http://localhost:3001`, and custom hosted URLs can be entered manually. Successful login stores the selected API URL in local CLI config so later commands can omit `--api-url`. API URL resolution is `--api-url`, then `MYSKILLS_API_URL`, then saved config, then `http://localhost:3001`.
|
|
72
74
|
|
|
73
|
-
`
|
|
75
|
+
`login` supports an email/password session flow and an API-key flow. The email/password flow handles MFA challenges with a TOTP or recovery code prompt and stores only the verified session token. The API-key flow validates the key with `/v1/me` before storing it. Token resolution is `--token`, then `MYSKILLS_TOKEN`, then the stored login token. The default token store uses the platform credential store through `@napi-rs/keyring` and falls back to `tokens.json` with user-only file permissions when keyring storage is unavailable or `MYSKILLS_TOKEN_STORE=file`/`MYSKILLS_TOKEN_FILE` is set. `logout` revokes stored session tokens and clears the local entry; stored API tokens are removed locally and must be revoked with `token revoke`.
|
|
76
|
+
|
|
77
|
+
`submit` validates and scans locally before sending package directories as normalized text entries or `.zip` packages as base64 archive uploads for server-side extraction. `export` downloads server-authorized bundle content, verifies byte size and SHA-256 against release metadata, and writes normalized package paths under the requested output directory. `install` uses the same verified bundle path, writes into `--dir`, `MYSKILLS_INSTALL_DIR`, or the user data directory, and records local state in `.myskills-app/installed.json`; `update` preserves a rollback snapshot before replacing files, and `rollback` restores the most recent snapshot. `token create` prints the plaintext API token only once and does not overwrite the stored login session. Browser/device login, platform-specific install adapters, and archive creation are still planned.
|
|
74
78
|
|
|
75
79
|
Common scopes:
|
|
76
80
|
|
package/dist/index.js
CHANGED
|
@@ -548,7 +548,7 @@ var require_yauzl = __commonJS({
|
|
|
548
548
|
exports.Entry = Entry;
|
|
549
549
|
exports.LocalFileHeader = LocalFileHeader;
|
|
550
550
|
exports.RandomAccessReader = RandomAccessReader;
|
|
551
|
-
function open(
|
|
551
|
+
function open(path5, options2, callback) {
|
|
552
552
|
if (typeof options2 === "function") {
|
|
553
553
|
callback = options2;
|
|
554
554
|
options2 = null;
|
|
@@ -560,7 +560,7 @@ var require_yauzl = __commonJS({
|
|
|
560
560
|
if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
|
|
561
561
|
if (options2.strictFileNames == null) options2.strictFileNames = false;
|
|
562
562
|
if (callback == null) callback = defaultCallback;
|
|
563
|
-
fs.open(
|
|
563
|
+
fs.open(path5, "r", function(err, fd) {
|
|
564
564
|
if (err) return callback(err);
|
|
565
565
|
fromFd(fd, options2, function(err2, zipfile) {
|
|
566
566
|
if (err2) fs.close(fd, defaultCallback);
|
|
@@ -2039,10 +2039,10 @@ function mergeDefs(...defs) {
|
|
|
2039
2039
|
function cloneDef(schema) {
|
|
2040
2040
|
return mergeDefs(schema._zod.def);
|
|
2041
2041
|
}
|
|
2042
|
-
function getElementAtPath(obj,
|
|
2043
|
-
if (!
|
|
2042
|
+
function getElementAtPath(obj, path5) {
|
|
2043
|
+
if (!path5)
|
|
2044
2044
|
return obj;
|
|
2045
|
-
return
|
|
2045
|
+
return path5.reduce((acc, key) => acc?.[key], obj);
|
|
2046
2046
|
}
|
|
2047
2047
|
function promiseAllObject(promisesObj) {
|
|
2048
2048
|
const keys = Object.keys(promisesObj);
|
|
@@ -2451,11 +2451,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
2451
2451
|
}
|
|
2452
2452
|
return false;
|
|
2453
2453
|
}
|
|
2454
|
-
function prefixIssues(
|
|
2454
|
+
function prefixIssues(path5, issues) {
|
|
2455
2455
|
return issues.map((iss) => {
|
|
2456
2456
|
var _a3;
|
|
2457
2457
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
2458
|
-
iss.path.unshift(
|
|
2458
|
+
iss.path.unshift(path5);
|
|
2459
2459
|
return iss;
|
|
2460
2460
|
});
|
|
2461
2461
|
}
|
|
@@ -2602,16 +2602,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2602
2602
|
}
|
|
2603
2603
|
function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
2604
2604
|
const fieldErrors = { _errors: [] };
|
|
2605
|
-
const processError = (error52,
|
|
2605
|
+
const processError = (error52, path5 = []) => {
|
|
2606
2606
|
for (const issue2 of error52.issues) {
|
|
2607
2607
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
2608
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
2608
|
+
issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
|
|
2609
2609
|
} else if (issue2.code === "invalid_key") {
|
|
2610
|
-
processError({ issues: issue2.issues }, [...
|
|
2610
|
+
processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
|
|
2611
2611
|
} else if (issue2.code === "invalid_element") {
|
|
2612
|
-
processError({ issues: issue2.issues }, [...
|
|
2612
|
+
processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
|
|
2613
2613
|
} else {
|
|
2614
|
-
const fullpath = [...
|
|
2614
|
+
const fullpath = [...path5, ...issue2.path];
|
|
2615
2615
|
if (fullpath.length === 0) {
|
|
2616
2616
|
fieldErrors._errors.push(mapper(issue2));
|
|
2617
2617
|
} else {
|
|
@@ -2638,17 +2638,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2638
2638
|
}
|
|
2639
2639
|
function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
2640
2640
|
const result = { errors: [] };
|
|
2641
|
-
const processError = (error52,
|
|
2641
|
+
const processError = (error52, path5 = []) => {
|
|
2642
2642
|
var _a3, _b;
|
|
2643
2643
|
for (const issue2 of error52.issues) {
|
|
2644
2644
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
2645
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
2645
|
+
issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
|
|
2646
2646
|
} else if (issue2.code === "invalid_key") {
|
|
2647
|
-
processError({ issues: issue2.issues }, [...
|
|
2647
|
+
processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
|
|
2648
2648
|
} else if (issue2.code === "invalid_element") {
|
|
2649
|
-
processError({ issues: issue2.issues }, [...
|
|
2649
|
+
processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
|
|
2650
2650
|
} else {
|
|
2651
|
-
const fullpath = [...
|
|
2651
|
+
const fullpath = [...path5, ...issue2.path];
|
|
2652
2652
|
if (fullpath.length === 0) {
|
|
2653
2653
|
result.errors.push(mapper(issue2));
|
|
2654
2654
|
continue;
|
|
@@ -2680,8 +2680,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2680
2680
|
}
|
|
2681
2681
|
function toDotPath(_path) {
|
|
2682
2682
|
const segs = [];
|
|
2683
|
-
const
|
|
2684
|
-
for (const seg of
|
|
2683
|
+
const path5 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
2684
|
+
for (const seg of path5) {
|
|
2685
2685
|
if (typeof seg === "number")
|
|
2686
2686
|
segs.push(`[${seg}]`);
|
|
2687
2687
|
else if (typeof seg === "symbol")
|
|
@@ -15373,13 +15373,13 @@ function resolveRef(ref, ctx) {
|
|
|
15373
15373
|
if (!ref.startsWith("#")) {
|
|
15374
15374
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
15375
15375
|
}
|
|
15376
|
-
const
|
|
15377
|
-
if (
|
|
15376
|
+
const path5 = ref.slice(1).split("/").filter(Boolean);
|
|
15377
|
+
if (path5.length === 0) {
|
|
15378
15378
|
return ctx.rootSchema;
|
|
15379
15379
|
}
|
|
15380
15380
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
15381
|
-
if (
|
|
15382
|
-
const key =
|
|
15381
|
+
if (path5[0] === defsKey) {
|
|
15382
|
+
const key = path5[1];
|
|
15383
15383
|
if (!key || !ctx.defs[key]) {
|
|
15384
15384
|
throw new Error(`Reference not found: ${ref}`);
|
|
15385
15385
|
}
|
|
@@ -16321,8 +16321,9 @@ function decodePackageText(buffer, relativePath) {
|
|
|
16321
16321
|
|
|
16322
16322
|
// src/cli.ts
|
|
16323
16323
|
var DEFAULT_API_URL = "http://localhost:3001";
|
|
16324
|
-
var CLI_VERSION = "0.1.0-alpha.
|
|
16324
|
+
var CLI_VERSION = "0.1.0-alpha.1";
|
|
16325
16325
|
var CLI_VISIBILITY_SCOPES = ["public", "authenticated", "organization", "team", "private", "explicit-users"];
|
|
16326
|
+
var LOGIN_AUTH_METHODS = ["password", "api-key"];
|
|
16326
16327
|
async function runCli(argv, runtime) {
|
|
16327
16328
|
const parsed = parseArgs(argv);
|
|
16328
16329
|
try {
|
|
@@ -16450,23 +16451,46 @@ async function infoCommand(parsed, runtime) {
|
|
|
16450
16451
|
return 0;
|
|
16451
16452
|
}
|
|
16452
16453
|
async function loginCommand(parsed, runtime) {
|
|
16453
|
-
|
|
16454
|
+
const tokenStore = runtime.tokenStore;
|
|
16455
|
+
if (!tokenStore) {
|
|
16454
16456
|
throw new CliError("No token store is configured. Set MYSKILLS_TOKEN for one-off commands.", 1);
|
|
16455
16457
|
}
|
|
16458
|
+
const apiUrl = await loginApiUrl(parsed, runtime);
|
|
16459
|
+
parsed.options["api-url"] = apiUrl;
|
|
16460
|
+
const method = await loginAuthMethod(parsed, runtime);
|
|
16461
|
+
if (method === "api-key") {
|
|
16462
|
+
return await loginWithApiKey(parsed, runtime, apiUrl, tokenStore);
|
|
16463
|
+
}
|
|
16464
|
+
return await loginWithPassword(parsed, runtime, apiUrl, tokenStore);
|
|
16465
|
+
}
|
|
16466
|
+
async function loginWithPassword(parsed, runtime, apiUrl, tokenStore) {
|
|
16456
16467
|
const email3 = optionalStringOption(parsed, "email") ?? await promptText(runtime, "Email: ");
|
|
16457
16468
|
const password = await promptSecret(runtime, "Password: ");
|
|
16458
16469
|
const loginResponse = await apiPost("/v1/auth/login", { email: email3.trim(), password }, parsed, runtime);
|
|
16459
16470
|
const session = loginResponse.mfaRequired === true ? await completeMfaLogin(loginResponse, parsed, runtime) : authSessionFromResponse(loginResponse);
|
|
16460
|
-
|
|
16461
|
-
await runtime.tokenStore.set(apiUrl, {
|
|
16471
|
+
await tokenStore.set(apiUrl, {
|
|
16462
16472
|
kind: "session",
|
|
16463
16473
|
token: session.token,
|
|
16464
16474
|
email: session.email,
|
|
16465
16475
|
expiresAt: session.expiresAt
|
|
16466
16476
|
});
|
|
16477
|
+
await runtime.configStore?.setApiUrl(apiUrl);
|
|
16467
16478
|
runtime.io.stdout(`${session.email ?? email3.trim()} logged-in expires=${session.expiresAt}`);
|
|
16468
16479
|
return 0;
|
|
16469
16480
|
}
|
|
16481
|
+
async function loginWithApiKey(parsed, runtime, apiUrl, tokenStore) {
|
|
16482
|
+
const apiKey = await promptSecret(runtime, "API key: ");
|
|
16483
|
+
const response = await apiGet("/v1/me", parsed, runtime, apiKey);
|
|
16484
|
+
const user = response.user;
|
|
16485
|
+
await tokenStore.set(apiUrl, {
|
|
16486
|
+
kind: "api",
|
|
16487
|
+
token: apiKey,
|
|
16488
|
+
email: user.email
|
|
16489
|
+
});
|
|
16490
|
+
await runtime.configStore?.setApiUrl(apiUrl);
|
|
16491
|
+
runtime.io.stdout(`${user.email ?? "api-key"} api-key-stored`);
|
|
16492
|
+
return 0;
|
|
16493
|
+
}
|
|
16470
16494
|
async function completeMfaLogin(loginResponse, parsed, runtime) {
|
|
16471
16495
|
const challengeToken = stringFromRecord(loginResponse, "challengeToken", "API login response is missing MFA challenge token.");
|
|
16472
16496
|
const mfaValue = (await promptSecret(runtime, "MFA code or recovery code: ")).trim();
|
|
@@ -17499,8 +17523,22 @@ function requiredPath(parsed) {
|
|
|
17499
17523
|
}
|
|
17500
17524
|
return value;
|
|
17501
17525
|
}
|
|
17526
|
+
function apiBaseUrlResolution(parsed, runtime) {
|
|
17527
|
+
const optionValue = optionalStringOption(parsed, "api-url");
|
|
17528
|
+
if (optionValue) {
|
|
17529
|
+
return { url: normalizeApiUrlOption(optionValue), source: "option" };
|
|
17530
|
+
}
|
|
17531
|
+
if (runtime.env.MYSKILLS_API_URL) {
|
|
17532
|
+
return { url: normalizeApiUrlOption(runtime.env.MYSKILLS_API_URL), source: "env" };
|
|
17533
|
+
}
|
|
17534
|
+
const configuredApiUrl = runtime.configStore?.getApiUrl();
|
|
17535
|
+
if (configuredApiUrl) {
|
|
17536
|
+
return { url: normalizeApiUrlOption(configuredApiUrl), source: "config" };
|
|
17537
|
+
}
|
|
17538
|
+
return { url: DEFAULT_API_URL, source: "default" };
|
|
17539
|
+
}
|
|
17502
17540
|
function apiBaseUrl(parsed, runtime) {
|
|
17503
|
-
return
|
|
17541
|
+
return apiBaseUrlResolution(parsed, runtime).url;
|
|
17504
17542
|
}
|
|
17505
17543
|
async function tokenOption(parsed, runtime) {
|
|
17506
17544
|
return (await resolveToken(parsed, runtime))?.value ?? null;
|
|
@@ -17558,6 +17596,60 @@ async function promptSecret(runtime, label) {
|
|
|
17558
17596
|
}
|
|
17559
17597
|
return value;
|
|
17560
17598
|
}
|
|
17599
|
+
async function promptOptionalText(runtime, label) {
|
|
17600
|
+
if (!runtime.prompt) {
|
|
17601
|
+
throw new CliError("Interactive input is unavailable. Set MYSKILLS_TOKEN for one-off commands.", 1);
|
|
17602
|
+
}
|
|
17603
|
+
return (await runtime.prompt.text(label)).trim();
|
|
17604
|
+
}
|
|
17605
|
+
async function loginApiUrl(parsed, runtime) {
|
|
17606
|
+
const resolved = apiBaseUrlResolution(parsed, runtime);
|
|
17607
|
+
if (resolved.source === "option" || resolved.source === "env" || !runtime.prompt) {
|
|
17608
|
+
return resolved.url;
|
|
17609
|
+
}
|
|
17610
|
+
const defaultUrl = resolved.source === "config" ? resolved.url : DEFAULT_API_URL;
|
|
17611
|
+
const input = await promptOptionalText(runtime, `API URL [${defaultUrl}]: `);
|
|
17612
|
+
return input ? normalizeApiUrlOption(input) : defaultUrl;
|
|
17613
|
+
}
|
|
17614
|
+
async function loginAuthMethod(parsed, runtime) {
|
|
17615
|
+
if (parsed.options["api-key"] === true) {
|
|
17616
|
+
return "api-key";
|
|
17617
|
+
}
|
|
17618
|
+
const methodOption = optionalStringOption(parsed, "auth-method") ?? optionalStringOption(parsed, "method");
|
|
17619
|
+
if (methodOption) {
|
|
17620
|
+
return parseLoginAuthMethod(methodOption);
|
|
17621
|
+
}
|
|
17622
|
+
if (optionalStringOption(parsed, "email") || !runtime.prompt) {
|
|
17623
|
+
return "password";
|
|
17624
|
+
}
|
|
17625
|
+
const input = await promptOptionalText(runtime, "Authentication method [password] (password/api-key): ");
|
|
17626
|
+
return input ? parseLoginAuthMethod(input) : "password";
|
|
17627
|
+
}
|
|
17628
|
+
function parseLoginAuthMethod(input) {
|
|
17629
|
+
const normalized = input.trim().toLowerCase();
|
|
17630
|
+
if (normalized === "password" || normalized === "email" || normalized === "user" || normalized === "username") {
|
|
17631
|
+
return "password";
|
|
17632
|
+
}
|
|
17633
|
+
if (normalized === "api-key" || normalized === "apikey" || normalized === "api" || normalized === "key") {
|
|
17634
|
+
return "api-key";
|
|
17635
|
+
}
|
|
17636
|
+
if (normalized === "browser" || normalized === "web") {
|
|
17637
|
+
throw new CliError("Browser login is not available in this CLI/API version yet. Choose password or api-key.", 2);
|
|
17638
|
+
}
|
|
17639
|
+
throw new CliError(`Authentication method must be one of: ${LOGIN_AUTH_METHODS.join(", ")}.`, 2);
|
|
17640
|
+
}
|
|
17641
|
+
function normalizeApiUrlOption(input) {
|
|
17642
|
+
const trimmed = input.trim().replace(/\/+$/, "");
|
|
17643
|
+
try {
|
|
17644
|
+
const url2 = new URL(trimmed);
|
|
17645
|
+
if (url2.protocol !== "http:" && url2.protocol !== "https:") {
|
|
17646
|
+
throw new Error("unsupported protocol");
|
|
17647
|
+
}
|
|
17648
|
+
return trimmed;
|
|
17649
|
+
} catch {
|
|
17650
|
+
throw new CliError("API URL must be a valid http:// or https:// URL.", 2);
|
|
17651
|
+
}
|
|
17652
|
+
}
|
|
17561
17653
|
function authSessionFromResponse(response) {
|
|
17562
17654
|
const user = response.user;
|
|
17563
17655
|
let email3;
|
|
@@ -17666,7 +17758,7 @@ function parseArgs(argv) {
|
|
|
17666
17758
|
continue;
|
|
17667
17759
|
}
|
|
17668
17760
|
const key = value.slice(2);
|
|
17669
|
-
if (key === "json") {
|
|
17761
|
+
if (key === "json" || key === "api-key") {
|
|
17670
17762
|
options2[key] = true;
|
|
17671
17763
|
continue;
|
|
17672
17764
|
}
|
|
@@ -17696,7 +17788,8 @@ function helpText() {
|
|
|
17696
17788
|
" scan --path <file-directory-or-zip>",
|
|
17697
17789
|
" search [query] [--api-url <url>]",
|
|
17698
17790
|
" info <skill-slug> [--api-url <url>]",
|
|
17699
|
-
" login [--api-url <url>] [--email <email>]",
|
|
17791
|
+
" login [--api-url <url>] [--method <password|api-key>] [--email <email>]",
|
|
17792
|
+
" login --api-key [--api-url <url>]",
|
|
17700
17793
|
" logout [--api-url <url>] [--token <token>]",
|
|
17701
17794
|
" whoami [--api-url <url>] [--token <token>]",
|
|
17702
17795
|
" submit --path <file-directory-or-zip> [--api-url <url>] [--token <token>]",
|
|
@@ -17722,7 +17815,7 @@ function helpText() {
|
|
|
17722
17815
|
"Options:",
|
|
17723
17816
|
" --version Print CLI version.",
|
|
17724
17817
|
" --json Print machine-readable JSON.",
|
|
17725
|
-
" --api-url <url> API base URL. Defaults to MYSKILLS_API_URL or http://localhost:3001.",
|
|
17818
|
+
" --api-url <url> API base URL. Defaults to MYSKILLS_API_URL, saved config, or http://localhost:3001.",
|
|
17726
17819
|
" --token <token> Bearer token. Defaults to MYSKILLS_TOKEN, then stored login token."
|
|
17727
17820
|
].join("\n");
|
|
17728
17821
|
}
|
|
@@ -17734,60 +17827,175 @@ var CliError = class extends Error {
|
|
|
17734
17827
|
exitCode;
|
|
17735
17828
|
};
|
|
17736
17829
|
|
|
17737
|
-
// src/
|
|
17738
|
-
import {
|
|
17830
|
+
// src/config-store.ts
|
|
17831
|
+
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
17739
17832
|
import os from "node:os";
|
|
17740
17833
|
import path3 from "node:path";
|
|
17834
|
+
function createFileConfigStore(env = process.env) {
|
|
17835
|
+
const filePath = configFilePath(env);
|
|
17836
|
+
let payload = readPayload(filePath);
|
|
17837
|
+
return {
|
|
17838
|
+
getApiUrl() {
|
|
17839
|
+
return payload.apiUrl;
|
|
17840
|
+
},
|
|
17841
|
+
async setApiUrl(apiUrl) {
|
|
17842
|
+
payload = {
|
|
17843
|
+
...payload,
|
|
17844
|
+
apiUrl: apiUrl.replace(/\/+$/, "")
|
|
17845
|
+
};
|
|
17846
|
+
writePayload(filePath, payload);
|
|
17847
|
+
}
|
|
17848
|
+
};
|
|
17849
|
+
}
|
|
17850
|
+
function configFilePath(env) {
|
|
17851
|
+
if (env.MYSKILLS_CONFIG_FILE) {
|
|
17852
|
+
return path3.resolve(env.MYSKILLS_CONFIG_FILE);
|
|
17853
|
+
}
|
|
17854
|
+
if (env.MYSKILLS_CONFIG_DIR) {
|
|
17855
|
+
return path3.join(path3.resolve(env.MYSKILLS_CONFIG_DIR), "config.json");
|
|
17856
|
+
}
|
|
17857
|
+
const baseDir = env.XDG_CONFIG_HOME ? path3.join(env.XDG_CONFIG_HOME, "myskills-app") : path3.join(os.homedir(), ".config", "myskills-app");
|
|
17858
|
+
return path3.join(baseDir, "config.json");
|
|
17859
|
+
}
|
|
17860
|
+
function readPayload(filePath) {
|
|
17861
|
+
try {
|
|
17862
|
+
return parsePayload(readFileSync(filePath, "utf8"));
|
|
17863
|
+
} catch (error51) {
|
|
17864
|
+
if (isNodeError2(error51) && error51.code === "ENOENT") {
|
|
17865
|
+
return { version: 1 };
|
|
17866
|
+
}
|
|
17867
|
+
throw error51;
|
|
17868
|
+
}
|
|
17869
|
+
}
|
|
17870
|
+
function writePayload(filePath, payload) {
|
|
17871
|
+
mkdirSync(path3.dirname(filePath), { recursive: true, mode: 448 });
|
|
17872
|
+
writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}
|
|
17873
|
+
`, { mode: 384 });
|
|
17874
|
+
chmodSync(filePath, 384);
|
|
17875
|
+
}
|
|
17876
|
+
function parsePayload(raw) {
|
|
17877
|
+
const parsed = JSON.parse(raw);
|
|
17878
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
17879
|
+
throw new Error("Config file must contain a JSON object.");
|
|
17880
|
+
}
|
|
17881
|
+
const record2 = parsed;
|
|
17882
|
+
if (record2.version !== 1) {
|
|
17883
|
+
throw new Error("Config file has an unsupported format.");
|
|
17884
|
+
}
|
|
17885
|
+
return {
|
|
17886
|
+
version: 1,
|
|
17887
|
+
apiUrl: typeof record2.apiUrl === "string" && record2.apiUrl ? record2.apiUrl.replace(/\/+$/, "") : void 0
|
|
17888
|
+
};
|
|
17889
|
+
}
|
|
17890
|
+
function isNodeError2(error51) {
|
|
17891
|
+
return error51 instanceof Error && "code" in error51;
|
|
17892
|
+
}
|
|
17893
|
+
|
|
17894
|
+
// src/token-store.ts
|
|
17895
|
+
import { chmod, mkdir as mkdir2, readFile as readFile3, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
17896
|
+
import os2 from "node:os";
|
|
17897
|
+
import path4 from "node:path";
|
|
17898
|
+
var KEYRING_SERVICE = "ai.jarel.myskills.cli";
|
|
17899
|
+
function createTokenStore(env = process.env) {
|
|
17900
|
+
const fileStore = createFileTokenStore(env);
|
|
17901
|
+
if (env.MYSKILLS_TOKEN_STORE === "file" || env.MYSKILLS_TOKEN_FILE) {
|
|
17902
|
+
return fileStore;
|
|
17903
|
+
}
|
|
17904
|
+
return createKeyringTokenStore(fileStore);
|
|
17905
|
+
}
|
|
17741
17906
|
function createFileTokenStore(env = process.env) {
|
|
17742
17907
|
const filePath = tokenFilePath(env);
|
|
17743
17908
|
return {
|
|
17744
17909
|
async get(apiUrl) {
|
|
17745
|
-
return (await
|
|
17910
|
+
return (await readPayload2(filePath)).tokens[normalizeApiUrl(apiUrl)] ?? null;
|
|
17746
17911
|
},
|
|
17747
17912
|
async set(apiUrl, token) {
|
|
17748
|
-
const payload = await
|
|
17913
|
+
const payload = await readPayload2(filePath);
|
|
17749
17914
|
payload.tokens[normalizeApiUrl(apiUrl)] = token;
|
|
17750
|
-
await
|
|
17915
|
+
await writePayload2(filePath, payload);
|
|
17751
17916
|
},
|
|
17752
17917
|
async delete(apiUrl) {
|
|
17753
|
-
const payload = await
|
|
17918
|
+
const payload = await readPayload2(filePath);
|
|
17754
17919
|
delete payload.tokens[normalizeApiUrl(apiUrl)];
|
|
17755
17920
|
if (Object.keys(payload.tokens).length === 0) {
|
|
17756
17921
|
await rm2(filePath, { force: true });
|
|
17757
17922
|
return;
|
|
17758
17923
|
}
|
|
17759
|
-
await
|
|
17924
|
+
await writePayload2(filePath, payload);
|
|
17925
|
+
}
|
|
17926
|
+
};
|
|
17927
|
+
}
|
|
17928
|
+
function createKeyringTokenStore(fallback) {
|
|
17929
|
+
return {
|
|
17930
|
+
async get(apiUrl) {
|
|
17931
|
+
const keyringToken = await readKeyringToken(apiUrl);
|
|
17932
|
+
return keyringToken ?? await fallback.get(apiUrl);
|
|
17933
|
+
},
|
|
17934
|
+
async set(apiUrl, token) {
|
|
17935
|
+
if (await writeKeyringToken(apiUrl, token)) {
|
|
17936
|
+
return;
|
|
17937
|
+
}
|
|
17938
|
+
await fallback.set(apiUrl, token);
|
|
17939
|
+
},
|
|
17940
|
+
async delete(apiUrl) {
|
|
17941
|
+
await deleteKeyringToken(apiUrl);
|
|
17942
|
+
await fallback.delete(apiUrl);
|
|
17760
17943
|
}
|
|
17761
17944
|
};
|
|
17762
17945
|
}
|
|
17946
|
+
async function readKeyringToken(apiUrl) {
|
|
17947
|
+
try {
|
|
17948
|
+
const { Entry } = await import("@napi-rs/keyring");
|
|
17949
|
+
const raw = new Entry(KEYRING_SERVICE, keyringAccount(apiUrl)).getPassword();
|
|
17950
|
+
return raw ? parseStoredToken(JSON.parse(raw)) : null;
|
|
17951
|
+
} catch {
|
|
17952
|
+
return null;
|
|
17953
|
+
}
|
|
17954
|
+
}
|
|
17955
|
+
async function writeKeyringToken(apiUrl, token) {
|
|
17956
|
+
try {
|
|
17957
|
+
const { Entry } = await import("@napi-rs/keyring");
|
|
17958
|
+
new Entry(KEYRING_SERVICE, keyringAccount(apiUrl)).setPassword(JSON.stringify(token));
|
|
17959
|
+
return true;
|
|
17960
|
+
} catch {
|
|
17961
|
+
return false;
|
|
17962
|
+
}
|
|
17963
|
+
}
|
|
17964
|
+
async function deleteKeyringToken(apiUrl) {
|
|
17965
|
+
try {
|
|
17966
|
+
const { Entry } = await import("@napi-rs/keyring");
|
|
17967
|
+
new Entry(KEYRING_SERVICE, keyringAccount(apiUrl)).deletePassword();
|
|
17968
|
+
} catch {
|
|
17969
|
+
}
|
|
17970
|
+
}
|
|
17763
17971
|
function tokenFilePath(env) {
|
|
17764
17972
|
if (env.MYSKILLS_TOKEN_FILE) {
|
|
17765
|
-
return
|
|
17973
|
+
return path4.resolve(env.MYSKILLS_TOKEN_FILE);
|
|
17766
17974
|
}
|
|
17767
17975
|
if (env.MYSKILLS_CONFIG_DIR) {
|
|
17768
|
-
return
|
|
17976
|
+
return path4.join(path4.resolve(env.MYSKILLS_CONFIG_DIR), "tokens.json");
|
|
17769
17977
|
}
|
|
17770
|
-
const baseDir = env.XDG_CONFIG_HOME ?
|
|
17771
|
-
return
|
|
17978
|
+
const baseDir = env.XDG_CONFIG_HOME ? path4.join(env.XDG_CONFIG_HOME, "myskills-app") : path4.join(os2.homedir(), ".config", "myskills-app");
|
|
17979
|
+
return path4.join(baseDir, "tokens.json");
|
|
17772
17980
|
}
|
|
17773
|
-
async function
|
|
17981
|
+
async function readPayload2(filePath) {
|
|
17774
17982
|
try {
|
|
17775
17983
|
const raw = await readFile3(filePath, "utf8");
|
|
17776
|
-
return
|
|
17984
|
+
return parsePayload2(raw);
|
|
17777
17985
|
} catch (error51) {
|
|
17778
|
-
if (
|
|
17986
|
+
if (isNodeError3(error51) && error51.code === "ENOENT") {
|
|
17779
17987
|
return { version: 1, tokens: {} };
|
|
17780
17988
|
}
|
|
17781
17989
|
throw error51;
|
|
17782
17990
|
}
|
|
17783
17991
|
}
|
|
17784
|
-
async function
|
|
17785
|
-
await mkdir2(
|
|
17992
|
+
async function writePayload2(filePath, payload) {
|
|
17993
|
+
await mkdir2(path4.dirname(filePath), { recursive: true, mode: 448 });
|
|
17786
17994
|
await writeFile2(filePath, `${JSON.stringify(payload, null, 2)}
|
|
17787
17995
|
`, { mode: 384 });
|
|
17788
17996
|
await chmod(filePath, 384);
|
|
17789
17997
|
}
|
|
17790
|
-
function
|
|
17998
|
+
function parsePayload2(raw) {
|
|
17791
17999
|
const parsed = JSON.parse(raw);
|
|
17792
18000
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
17793
18001
|
throw new Error("Token store file must contain a JSON object.");
|
|
@@ -17823,7 +18031,10 @@ function parseStoredToken(input) {
|
|
|
17823
18031
|
function normalizeApiUrl(apiUrl) {
|
|
17824
18032
|
return apiUrl.replace(/\/+$/, "");
|
|
17825
18033
|
}
|
|
17826
|
-
function
|
|
18034
|
+
function keyringAccount(apiUrl) {
|
|
18035
|
+
return `api-url:${Buffer.from(normalizeApiUrl(apiUrl)).toString("base64url")}`;
|
|
18036
|
+
}
|
|
18037
|
+
function isNodeError3(error51) {
|
|
17827
18038
|
return error51 instanceof Error && "code" in error51;
|
|
17828
18039
|
}
|
|
17829
18040
|
|
|
@@ -17943,8 +18154,9 @@ try {
|
|
|
17943
18154
|
const exitCode = await runCli(process.argv.slice(2), {
|
|
17944
18155
|
env: process.env,
|
|
17945
18156
|
fetch: fetchImpl,
|
|
18157
|
+
configStore: createFileConfigStore(process.env),
|
|
17946
18158
|
prompt,
|
|
17947
|
-
tokenStore:
|
|
18159
|
+
tokenStore: createTokenStore(process.env),
|
|
17948
18160
|
io: {
|
|
17949
18161
|
stdout: (line) => console.log(line),
|
|
17950
18162
|
stderr: (line) => console.error(line)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jarel/myskills",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
4
|
"description": "Command-line client for publishing, discovering, installing, updating, and rolling back MySkills packages.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"private": false,
|
|
@@ -44,5 +44,8 @@
|
|
|
44
44
|
"@myskills-app/skill-package": "0.1.0-alpha.0",
|
|
45
45
|
"esbuild": "^0.28.1",
|
|
46
46
|
"tsx": "^4.20.6"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@napi-rs/keyring": "^1.3.0"
|
|
47
50
|
}
|
|
48
51
|
}
|