@rayul/kordis 0.10.3 → 0.11.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/assets/kordis-using/SKILL.md +8 -1
- package/dist/index.js +132 -70
- package/package.json +2 -2
|
@@ -125,12 +125,19 @@ two parents requiring incompatible ranges = hard fail** (exit code
|
|
|
125
125
|
can pin the offending one explicitly. Recommended deps are only
|
|
126
126
|
walked at depth 0 when `--with-recommends` is passed.
|
|
127
127
|
|
|
128
|
+
**Per-version deps (0.11.0+)** — pinning an older version walks the
|
|
129
|
+
dependencies THAT version declared, not the package's current ones:
|
|
130
|
+
`kordis pull user/foo@0.1.0` resolves 0.1.0's graph even if newer
|
|
131
|
+
releases changed or dropped deps.
|
|
132
|
+
|
|
128
133
|
`kordis read @range` resolves to a concrete version (the stderr footer
|
|
129
134
|
shows it: `… @1.4.2 (resolved @^1 → 1.4.2)`) but does NOT walk
|
|
130
135
|
transitive deps — read is a one-shot context inject, no install plan.
|
|
131
136
|
|
|
132
137
|
`kordis info user/foo@^1` shows a `Requested: 1.4.2 (current: 2.0.0)`
|
|
133
|
-
line above `Type` when the pin differs from the registry default.
|
|
138
|
+
line above `Type` when the pin differs from the registry default. Its
|
|
139
|
+
`Depends`/`Recommends` lists reflect the resolved version (0.11.0+), so
|
|
140
|
+
inspecting an old pin shows that version's declared deps.
|
|
134
141
|
|
|
135
142
|
## When a version is yanked (0.10.0+)
|
|
136
143
|
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ var init_primitives = __esm({
|
|
|
20
20
|
UsernameSchema = z.string().min(3).max(32).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "username must be lowercase alphanumeric with single hyphens");
|
|
21
21
|
SlugSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "slug must be lowercase alphanumeric with single hyphens");
|
|
22
22
|
FullSlugSchema = z.string().min(5).max(128).regex(/^[a-z0-9](-?[a-z0-9])*\/[a-z0-9](-?[a-z0-9])*$/, "full_slug must match {username}/{slug}");
|
|
23
|
-
SemverSchema = z.string().regex(
|
|
23
|
+
SemverSchema = z.string().max(32).regex(
|
|
24
24
|
// eslint-disable-next-line no-useless-escape
|
|
25
25
|
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/,
|
|
26
26
|
"must be a valid semver version"
|
|
@@ -478,8 +478,8 @@ var init_auth = __esm({
|
|
|
478
478
|
});
|
|
479
479
|
DeviceCodeResponseSchema = z8.object({
|
|
480
480
|
device_code: z8.string().min(16),
|
|
481
|
-
user_code: z8.string().
|
|
482
|
-
// shown to the user: e.g. "KORDIS-
|
|
481
|
+
user_code: z8.string().regex(/^KORDIS-[0-9A-Z]{4}-[0-9A-Z]{4}$/),
|
|
482
|
+
// shown to the user: e.g. "KORDIS-7QFX-2M9K"
|
|
483
483
|
verification_url: z8.string().url(),
|
|
484
484
|
expires_in: z8.number().int().positive(),
|
|
485
485
|
// seconds
|
|
@@ -1153,7 +1153,7 @@ var init_client = __esm({
|
|
|
1153
1153
|
`[kordis] api: ${method} ${url.pathname}${url.search} \u2192 fetch failed, retrying in 150ms (${err.message})`
|
|
1154
1154
|
);
|
|
1155
1155
|
}
|
|
1156
|
-
await new Promise((
|
|
1156
|
+
await new Promise((resolve3) => setTimeout(resolve3, 150));
|
|
1157
1157
|
continue;
|
|
1158
1158
|
}
|
|
1159
1159
|
}
|
|
@@ -1178,7 +1178,12 @@ var init_client = __esm({
|
|
|
1178
1178
|
}
|
|
1179
1179
|
if (res.status === 204) return void 0;
|
|
1180
1180
|
const text = await res.text();
|
|
1181
|
-
|
|
1181
|
+
let parsed;
|
|
1182
|
+
try {
|
|
1183
|
+
parsed = text ? JSON.parse(text) : void 0;
|
|
1184
|
+
} catch {
|
|
1185
|
+
parsed = void 0;
|
|
1186
|
+
}
|
|
1182
1187
|
if (!res.ok) {
|
|
1183
1188
|
const envelope = normalizeErrorEnvelope(res.status, parsed, text);
|
|
1184
1189
|
throw new KordisApiError(res.status, envelope, requestId);
|
|
@@ -1919,7 +1924,7 @@ async function reportError(kind, err, extras = {}) {
|
|
|
1919
1924
|
});
|
|
1920
1925
|
await Promise.race([
|
|
1921
1926
|
client.recordTelemetryError(payload),
|
|
1922
|
-
new Promise((
|
|
1927
|
+
new Promise((resolve3) => setTimeout(resolve3, REPORT_TIMEOUT_MS))
|
|
1923
1928
|
]);
|
|
1924
1929
|
} catch {
|
|
1925
1930
|
}
|
|
@@ -2379,30 +2384,13 @@ function searchCommand() {
|
|
|
2379
2384
|
});
|
|
2380
2385
|
}
|
|
2381
2386
|
const queryProvided = query.length > 0;
|
|
2387
|
+
const emptyHint = opts.type ? `no packages of type "${opts.type}" match. Try without --type, or list everything: kordis search --type ${opts.type} --sort recent --limit 25` : queryProvided ? "try `--type skill` (or mcp-server / project-rules / custom-agent / hook) to narrow, or broader keywords" : 'try keywords (e.g. `kordis search "topic"`) or `kordis search --trending`';
|
|
2382
2388
|
printDual({
|
|
2383
|
-
json: res,
|
|
2389
|
+
json: res.results.length === 0 ? { ...res, hint: emptyHint } : res,
|
|
2384
2390
|
human: () => {
|
|
2385
2391
|
if (res.results.length === 0) {
|
|
2386
2392
|
printHuman(c.yellow("(no results)"));
|
|
2387
|
-
|
|
2388
|
-
printHuman(
|
|
2389
|
-
c.gray(
|
|
2390
|
-
` \u2192 no packages of type "${opts.type}" match. Try without --type, or list everything: kordis search --type ${opts.type} --sort recent --limit 25`
|
|
2391
|
-
)
|
|
2392
|
-
);
|
|
2393
|
-
} else if (queryProvided) {
|
|
2394
|
-
printHuman(
|
|
2395
|
-
c.gray(
|
|
2396
|
-
" \u2192 try `--type skill` (or mcp-server / project-rules / custom-agent / hook) to narrow, or broader keywords"
|
|
2397
|
-
)
|
|
2398
|
-
);
|
|
2399
|
-
} else {
|
|
2400
|
-
printHuman(
|
|
2401
|
-
c.gray(
|
|
2402
|
-
' \u2192 try keywords (e.g. `kordis search "topic"`) or `kordis search --trending`'
|
|
2403
|
-
)
|
|
2404
|
-
);
|
|
2405
|
-
}
|
|
2393
|
+
printHuman(c.gray(` \u2192 ${emptyHint}`));
|
|
2406
2394
|
return;
|
|
2407
2395
|
}
|
|
2408
2396
|
if (typeof res.total === "number") {
|
|
@@ -2522,8 +2510,8 @@ function normaliseDep(entry, idx) {
|
|
|
2522
2510
|
);
|
|
2523
2511
|
}
|
|
2524
2512
|
return {
|
|
2525
|
-
full_slug: m[1],
|
|
2526
|
-
version_range: (m[
|
|
2513
|
+
full_slug: FullSlugSchema.parse(m[1]),
|
|
2514
|
+
version_range: SemverRangeSchema.parse((m[4] ?? "*").trim())
|
|
2527
2515
|
};
|
|
2528
2516
|
}
|
|
2529
2517
|
if (typeof entry === "object" && entry !== null) {
|
|
@@ -2979,7 +2967,7 @@ var ASSET_MAX_COUNT = 50;
|
|
|
2979
2967
|
var SAFE_PATH_RE = /^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/;
|
|
2980
2968
|
function isAssetPathSafe(p) {
|
|
2981
2969
|
if (!SAFE_PATH_RE.test(p)) return false;
|
|
2982
|
-
return
|
|
2970
|
+
return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
|
|
2983
2971
|
}
|
|
2984
2972
|
function validateAssets(assets) {
|
|
2985
2973
|
const flags = [];
|
|
@@ -3272,7 +3260,11 @@ async function bundleSkillZip(zipPath) {
|
|
|
3272
3260
|
const buf = await readFile6(zipPath);
|
|
3273
3261
|
const zip = await JSZip.loadAsync(buf);
|
|
3274
3262
|
const entries = Object.keys(zip.files).filter((k) => !zip.files[k].dir);
|
|
3275
|
-
if (entries.length === 0)
|
|
3263
|
+
if (entries.length === 0) {
|
|
3264
|
+
const e = new Error(".skill file is empty");
|
|
3265
|
+
e.hint = "The .skill archive contains no files. Re-export the .skill from skill-creator, or push the unpacked skill directory instead (the dir containing SKILL.md).";
|
|
3266
|
+
throw e;
|
|
3267
|
+
}
|
|
3276
3268
|
const firstParts = entries[0].split("/");
|
|
3277
3269
|
const prefix = firstParts.length > 1 ? firstParts[0] + "/" : "";
|
|
3278
3270
|
const stripped = entries.map((k) => ({
|
|
@@ -3281,7 +3273,11 @@ async function bundleSkillZip(zipPath) {
|
|
|
3281
3273
|
})).filter((f) => f.rel.length > 0 && !shouldExclude(f.rel, false));
|
|
3282
3274
|
const skillEntry = stripped.find((f) => f.rel === "SKILL.md");
|
|
3283
3275
|
if (!skillEntry) {
|
|
3284
|
-
|
|
3276
|
+
const e = new Error(
|
|
3277
|
+
`.skill ZIP has no SKILL.md at its root (prefix="${prefix}")`
|
|
3278
|
+
);
|
|
3279
|
+
e.hint = "SKILL.md must sit at <skill-name>/SKILL.md at the archive root. Re-package so the single top-level directory contains SKILL.md (skill-creator does this by default).";
|
|
3280
|
+
throw e;
|
|
3285
3281
|
}
|
|
3286
3282
|
const content = await zip.files[skillEntry.zipKey].async("string");
|
|
3287
3283
|
const assets = [];
|
|
@@ -3304,7 +3300,7 @@ async function bundleSkillZip(zipPath) {
|
|
|
3304
3300
|
// src/commands/pull.ts
|
|
3305
3301
|
import { Command as Command5 } from "commander";
|
|
3306
3302
|
import { mkdir as mkdir6, writeFile as writeFile6 } from "fs/promises";
|
|
3307
|
-
import { dirname as dirname2, join as join9 } from "path";
|
|
3303
|
+
import { dirname as dirname2, join as join9, resolve as resolve2, sep as sep2 } from "path";
|
|
3308
3304
|
|
|
3309
3305
|
// ../../packages/translators/src/skill/identity.ts
|
|
3310
3306
|
function buildSkillFile(input, outputPath) {
|
|
@@ -3548,13 +3544,13 @@ var mcpToContinue = (input) => {
|
|
|
3548
3544
|
}
|
|
3549
3545
|
if (t.env && Object.keys(t.env).length > 0) {
|
|
3550
3546
|
lines.push("env:");
|
|
3551
|
-
for (const [k, v] of Object.entries(t.env)) lines.push(` ${k}: ${JSON.stringify(v)}`);
|
|
3547
|
+
for (const [k, v] of Object.entries(t.env)) lines.push(` ${JSON.stringify(k)}: ${JSON.stringify(v)}`);
|
|
3552
3548
|
}
|
|
3553
3549
|
} else {
|
|
3554
3550
|
lines.push(`type: ${t.type}`, `url: ${JSON.stringify(t.url)}`);
|
|
3555
3551
|
if (t.headers && Object.keys(t.headers).length > 0) {
|
|
3556
3552
|
lines.push("headers:");
|
|
3557
|
-
for (const [k, v] of Object.entries(t.headers)) lines.push(` ${k}: ${JSON.stringify(v)}`);
|
|
3553
|
+
for (const [k, v] of Object.entries(t.headers)) lines.push(` ${JSON.stringify(k)}: ${JSON.stringify(v)}`);
|
|
3558
3554
|
}
|
|
3559
3555
|
}
|
|
3560
3556
|
return {
|
|
@@ -3781,16 +3777,23 @@ async function recordInstalled(fullSlug, entry) {
|
|
|
3781
3777
|
// src/lib/parse-username.ts
|
|
3782
3778
|
init_src();
|
|
3783
3779
|
var SORT_KEYWORDS = ["latest", "popular", "recent", "updated", "stars", "downloads"];
|
|
3780
|
+
function usernameError(message, hint) {
|
|
3781
|
+
const e = new Error(message);
|
|
3782
|
+
e.hint = hint;
|
|
3783
|
+
return e;
|
|
3784
|
+
}
|
|
3784
3785
|
function parseUsername(raw) {
|
|
3785
3786
|
if (SORT_KEYWORDS.includes(raw.toLowerCase())) {
|
|
3786
|
-
throw
|
|
3787
|
-
`"${raw}" is a sort keyword \u2014 did you mean \`--sort ${raw}\`? Username arguments must follow the Kordis username shape (lowercase alphanumeric, hyphens allowed)
|
|
3787
|
+
throw usernameError(
|
|
3788
|
+
`"${raw}" is a sort keyword \u2014 did you mean \`--sort ${raw}\`? Username arguments must follow the Kordis username shape (lowercase alphanumeric, hyphens allowed).`,
|
|
3789
|
+
`Pass it as the sort flag instead, e.g. \`--sort ${raw}\`, or supply an actual username argument.`
|
|
3788
3790
|
);
|
|
3789
3791
|
}
|
|
3790
3792
|
const parsed = UsernameSchema.safeParse(raw);
|
|
3791
3793
|
if (!parsed.success) {
|
|
3792
|
-
throw
|
|
3793
|
-
`invalid username "${raw}" \u2014 must be 3-32 chars, lowercase alphanumeric with single hyphens (e.g. "alice", "my-team")
|
|
3794
|
+
throw usernameError(
|
|
3795
|
+
`invalid username "${raw}" \u2014 must be 3-32 chars, lowercase alphanumeric with single hyphens (e.g. "alice", "my-team")`,
|
|
3796
|
+
"Usernames are lowercase alphanumeric with single hyphens. Browse existing users with `kordis search` or look one up with `kordis profile <username>`."
|
|
3794
3797
|
);
|
|
3795
3798
|
}
|
|
3796
3799
|
return parsed.data;
|
|
@@ -3798,28 +3801,44 @@ function parseUsername(raw) {
|
|
|
3798
3801
|
|
|
3799
3802
|
// src/lib/parse-slug-version.ts
|
|
3800
3803
|
var SUFFIX_CHARS = /[\^~*]/;
|
|
3804
|
+
function slugError(message, hint) {
|
|
3805
|
+
const e = new Error(message);
|
|
3806
|
+
e.hint = hint;
|
|
3807
|
+
return e;
|
|
3808
|
+
}
|
|
3801
3809
|
function parseSlugAtVersion(raw) {
|
|
3802
3810
|
if (!raw.includes("/")) {
|
|
3811
|
+
let username2;
|
|
3803
3812
|
try {
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3813
|
+
username2 = parseUsername(raw);
|
|
3814
|
+
} catch {
|
|
3815
|
+
username2 = void 0;
|
|
3816
|
+
}
|
|
3817
|
+
if (username2) {
|
|
3818
|
+
throw slugError(
|
|
3819
|
+
`"${raw}" looks like a username, not a package`,
|
|
3820
|
+
`Try \`kordis profile ${username2}\` for user details, or pass a full slug like "{username}/{slug}".`
|
|
3807
3821
|
);
|
|
3808
|
-
} catch (err) {
|
|
3809
|
-
if (err instanceof Error) throw err;
|
|
3810
|
-
throw new Error(`"${raw}" is missing the "{username}/{slug}" form`);
|
|
3811
3822
|
}
|
|
3823
|
+
throw slugError(
|
|
3824
|
+
`"${raw}" is missing the "{username}/{slug}" form`,
|
|
3825
|
+
'Use the form "{username}/{slug}", e.g. "alice/json-pretty".'
|
|
3826
|
+
);
|
|
3812
3827
|
}
|
|
3813
3828
|
const atIdx = raw.indexOf("@");
|
|
3814
3829
|
const slugPart = atIdx === -1 ? raw : raw.slice(0, atIdx);
|
|
3815
3830
|
const versionPart = atIdx === -1 ? "" : raw.slice(atIdx + 1);
|
|
3816
3831
|
const [username, slug] = slugPart.split("/");
|
|
3817
3832
|
if (!username || !slug) {
|
|
3818
|
-
throw
|
|
3833
|
+
throw slugError(
|
|
3834
|
+
'full_slug must be "{username}/{slug}"',
|
|
3835
|
+
'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.'
|
|
3836
|
+
);
|
|
3819
3837
|
}
|
|
3820
3838
|
if (atIdx !== -1 && versionPart.length === 0) {
|
|
3821
|
-
throw
|
|
3822
|
-
`"${raw}" has a trailing "@" with no version
|
|
3839
|
+
throw slugError(
|
|
3840
|
+
`"${raw}" has a trailing "@" with no version`,
|
|
3841
|
+
`Drop the "@" or append a version/range, e.g. "${slugPart}@1.0.0" or "${slugPart}@^1".`
|
|
3823
3842
|
);
|
|
3824
3843
|
}
|
|
3825
3844
|
const out = {
|
|
@@ -3840,7 +3859,11 @@ function parseSlugAtVersion(raw) {
|
|
|
3840
3859
|
// src/commands/pull.ts
|
|
3841
3860
|
async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
|
|
3842
3861
|
const [username, slug] = fullSlug.split("/");
|
|
3843
|
-
if (!username || !slug)
|
|
3862
|
+
if (!username || !slug) {
|
|
3863
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
3864
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
3865
|
+
throw e;
|
|
3866
|
+
}
|
|
3844
3867
|
const bundle = await useClient();
|
|
3845
3868
|
const pullRes = await bundle.api.pull(username, slug, {
|
|
3846
3869
|
...opts.version ? { version: opts.version } : {},
|
|
@@ -3855,7 +3878,9 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
|
|
|
3855
3878
|
});
|
|
3856
3879
|
const parsed = validatePackageSource(contentRes.content);
|
|
3857
3880
|
if (!parsed.ok || !parsed.type || !parsed.frontmatter) {
|
|
3858
|
-
|
|
3881
|
+
const e = new Error("package stored content is not currently valid");
|
|
3882
|
+
e.hint = `This version's stored bytes no longer pass validation. Try another version via \`kordis versions ${fullSlug}\` (then pull a specific \`@<version>\`), or re-push a corrected version if you authored it.`;
|
|
3883
|
+
throw e;
|
|
3859
3884
|
}
|
|
3860
3885
|
const written = [];
|
|
3861
3886
|
const notes = [];
|
|
@@ -3870,7 +3895,7 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
|
|
|
3870
3895
|
assets: contentRes.assets
|
|
3871
3896
|
});
|
|
3872
3897
|
for (const f of result.files) {
|
|
3873
|
-
const abs =
|
|
3898
|
+
const abs = safeInstallPath(installRoot, f.path);
|
|
3874
3899
|
await mkdir6(dirname2(abs), { recursive: true });
|
|
3875
3900
|
await writeEmittedFile(abs, f.content, f.encoding);
|
|
3876
3901
|
written.push(abs);
|
|
@@ -3890,7 +3915,7 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
|
|
|
3890
3915
|
`Tool "${format}" is not supported for type "${parsed.type}"; installed canonical form instead.`
|
|
3891
3916
|
);
|
|
3892
3917
|
for (const f of result.files) {
|
|
3893
|
-
const abs =
|
|
3918
|
+
const abs = safeInstallPath(installRoot, f.path);
|
|
3894
3919
|
await mkdir6(dirname2(abs), { recursive: true });
|
|
3895
3920
|
await writeEmittedFile(abs, f.content, f.encoding);
|
|
3896
3921
|
written.push(abs);
|
|
@@ -4053,6 +4078,26 @@ async function detectTool(root) {
|
|
|
4053
4078
|
}
|
|
4054
4079
|
return null;
|
|
4055
4080
|
}
|
|
4081
|
+
function safeInstallPath(installRoot, relPath) {
|
|
4082
|
+
if (relPath.startsWith("/") || resolve2(relPath) === relPath) {
|
|
4083
|
+
const e = new Error(
|
|
4084
|
+
`refusing to write absolute path "${relPath}" outside the install root`
|
|
4085
|
+
);
|
|
4086
|
+
e.hint = "Package emitted an absolute file path. Re-push the package with relative paths only; report it to the author if you did not publish it.";
|
|
4087
|
+
throw e;
|
|
4088
|
+
}
|
|
4089
|
+
const abs = join9(installRoot, relPath);
|
|
4090
|
+
const resolvedRoot = resolve2(installRoot);
|
|
4091
|
+
const resolvedAbs = resolve2(abs);
|
|
4092
|
+
if (resolvedAbs !== resolvedRoot && !resolvedAbs.startsWith(resolvedRoot + sep2)) {
|
|
4093
|
+
const e = new Error(
|
|
4094
|
+
`refusing to write "${relPath}" \u2014 resolves outside the install root`
|
|
4095
|
+
);
|
|
4096
|
+
e.hint = "Package tried to write outside the install directory (path traversal). Re-push the package with relative in-tree paths only; report it to the author if you did not publish it.";
|
|
4097
|
+
throw e;
|
|
4098
|
+
}
|
|
4099
|
+
return abs;
|
|
4100
|
+
}
|
|
4056
4101
|
async function writeEmittedFile(abs, content, encoding) {
|
|
4057
4102
|
if (encoding === "base64") {
|
|
4058
4103
|
await writeFile6(abs, Buffer.from(content, "base64"));
|
|
@@ -4550,7 +4595,9 @@ function commentsCommand() {
|
|
|
4550
4595
|
async (rawSlug, opts) => {
|
|
4551
4596
|
const [username, slug] = rawSlug.split("/");
|
|
4552
4597
|
if (!username || !slug) {
|
|
4553
|
-
|
|
4598
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
4599
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
4600
|
+
throw e;
|
|
4554
4601
|
}
|
|
4555
4602
|
if (opts.kind && !KINDS.includes(opts.kind)) {
|
|
4556
4603
|
throw new Error(`--kind must be one of ${KINDS.join(", ")}`);
|
|
@@ -4565,14 +4612,13 @@ function commentsCommand() {
|
|
|
4565
4612
|
...opts.kind ? { kind: opts.kind } : {},
|
|
4566
4613
|
...opts.sort ? { sort: opts.sort } : {}
|
|
4567
4614
|
});
|
|
4615
|
+
const emptyHint = "no comments yet \u2014 be the first: kordis comment " + rawSlug + ' --body "..."';
|
|
4568
4616
|
printDual({
|
|
4569
|
-
json: res,
|
|
4617
|
+
json: res.results.length === 0 ? { ...res, hint: emptyHint } : res,
|
|
4570
4618
|
human: () => {
|
|
4571
4619
|
if (res.results.length === 0) {
|
|
4572
4620
|
printHuman(c.yellow("(no comments)"));
|
|
4573
|
-
printHuman(
|
|
4574
|
-
c.gray(" \u2192 be the first: kordis comment " + rawSlug + ' --body "..."')
|
|
4575
|
-
);
|
|
4621
|
+
printHuman(c.gray(" \u2192 " + emptyHint));
|
|
4576
4622
|
return;
|
|
4577
4623
|
}
|
|
4578
4624
|
printHuman(
|
|
@@ -4640,7 +4686,9 @@ function starCommand() {
|
|
|
4640
4686
|
return new Command11("star").description("star a package").argument("<full_slug>", "package identifier {username}/{slug}").action(async (rawSlug) => {
|
|
4641
4687
|
const [username, slug] = rawSlug.split("/");
|
|
4642
4688
|
if (!username || !slug) {
|
|
4643
|
-
|
|
4689
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
4690
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
4691
|
+
throw e;
|
|
4644
4692
|
}
|
|
4645
4693
|
const bundle = await useClient();
|
|
4646
4694
|
requireAuth(bundle);
|
|
@@ -4658,7 +4706,9 @@ function unstarCommand() {
|
|
|
4658
4706
|
return new Command11("unstar").description("remove your star from a package").argument("<full_slug>", "package identifier {username}/{slug}").action(async (rawSlug) => {
|
|
4659
4707
|
const [username, slug] = rawSlug.split("/");
|
|
4660
4708
|
if (!username || !slug) {
|
|
4661
|
-
|
|
4709
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
4710
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
4711
|
+
throw e;
|
|
4662
4712
|
}
|
|
4663
4713
|
const bundle = await useClient();
|
|
4664
4714
|
requireAuth(bundle);
|
|
@@ -4682,7 +4732,9 @@ function deleteCommand() {
|
|
|
4682
4732
|
return new Command12("delete").description("permanently delete a package you own").argument("<full_slug>", "package identifier {username}/{slug}").option("--force", "skip the confirmation prompt").action(async (fullSlug, opts) => {
|
|
4683
4733
|
const [username, slug] = fullSlug.split("/");
|
|
4684
4734
|
if (!username || !slug) {
|
|
4685
|
-
|
|
4735
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
4736
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
4737
|
+
throw e;
|
|
4686
4738
|
}
|
|
4687
4739
|
const bundle = await useClient();
|
|
4688
4740
|
const detail = await bundle.api.getPackage(username, slug);
|
|
@@ -4890,11 +4942,13 @@ function listCommand() {
|
|
|
4890
4942
|
sort: publicSort,
|
|
4891
4943
|
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
4892
4944
|
});
|
|
4945
|
+
const emptyHint = `no public packages for ${username} \u2014 verify the username via \`kordis profile ${username}\`, or browse the registry via \`kordis search\``;
|
|
4893
4946
|
printDual({
|
|
4894
|
-
json: res,
|
|
4947
|
+
json: res.results.length === 0 ? { ...res, hint: emptyHint } : res,
|
|
4895
4948
|
human: () => {
|
|
4896
4949
|
if (res.results.length === 0) {
|
|
4897
4950
|
printHuman(c.yellow(`(no public packages for ${username})`));
|
|
4951
|
+
printHuman(c.gray(` \u2192 ${emptyHint}`));
|
|
4898
4952
|
return;
|
|
4899
4953
|
}
|
|
4900
4954
|
for (const p of res.results) {
|
|
@@ -4920,7 +4974,9 @@ function versionsCommand() {
|
|
|
4920
4974
|
return new Command16("versions").description("list all versions of a package").argument("<full_slug>", "package identifier {username}/{slug}").action(async (fullSlug) => {
|
|
4921
4975
|
const [username, slug] = fullSlug.split("/");
|
|
4922
4976
|
if (!username || !slug) {
|
|
4923
|
-
|
|
4977
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
4978
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
4979
|
+
throw e;
|
|
4924
4980
|
}
|
|
4925
4981
|
const bundle = await useClient();
|
|
4926
4982
|
const spin = spinner(`Fetching versions for ${fullSlug}`);
|
|
@@ -5234,7 +5290,9 @@ function diffCommand() {
|
|
|
5234
5290
|
}
|
|
5235
5291
|
const [username, slug] = fullSlug.split("/");
|
|
5236
5292
|
if (!username || !slug) {
|
|
5237
|
-
|
|
5293
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
5294
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
5295
|
+
throw e;
|
|
5238
5296
|
}
|
|
5239
5297
|
const bundle = await useClient();
|
|
5240
5298
|
if (!from || !to) {
|
|
@@ -5327,8 +5385,9 @@ function recommendCommand() {
|
|
|
5327
5385
|
spin.succeed();
|
|
5328
5386
|
await writeCache(res.items, res.generated_at);
|
|
5329
5387
|
await markSeen();
|
|
5388
|
+
const emptyHint = !res.personalized ? "personalization is OFF, so the server ranks on popularity alone \u2014 likely empty pool. Enable for stack-aware matches: `kordis setup --personalization on`, or broaden via `kordis search <topic>` / `kordis search --trending`." : "personalization is ON but no matches for your stack \u2014 registry may be sparse for this profile. Try `kordis search <topic>` or `kordis search --trending`.";
|
|
5330
5389
|
printDual({
|
|
5331
|
-
json: res,
|
|
5390
|
+
json: res.items.length === 0 ? { ...res, hint: emptyHint } : res,
|
|
5332
5391
|
human: () => {
|
|
5333
5392
|
if (res.items.length === 0) {
|
|
5334
5393
|
printHuman(c.yellow("(no recommendations available right now)"));
|
|
@@ -5450,12 +5509,13 @@ function starsCommand() {
|
|
|
5450
5509
|
limit: opts.limit,
|
|
5451
5510
|
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
5452
5511
|
});
|
|
5512
|
+
const emptyHint = `no public stars for ${username} \u2014 they may have none, or keep them private`;
|
|
5453
5513
|
printDual({
|
|
5454
|
-
json: res,
|
|
5514
|
+
json: res.results.length === 0 ? { ...res, hint: emptyHint } : res,
|
|
5455
5515
|
human: () => {
|
|
5456
5516
|
if (res.results.length === 0) {
|
|
5457
5517
|
printHuman(c.yellow(`(no stars yet for ${username})`));
|
|
5458
|
-
printHuman(c.gray(` \u2192
|
|
5518
|
+
printHuman(c.gray(` \u2192 ${emptyHint}`));
|
|
5459
5519
|
return;
|
|
5460
5520
|
}
|
|
5461
5521
|
for (const p of res.results) {
|
|
@@ -5520,12 +5580,14 @@ function makeListing(name) {
|
|
|
5520
5580
|
limit: opts.limit,
|
|
5521
5581
|
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
5522
5582
|
});
|
|
5583
|
+
const emptyHint = name === "followers" ? `no followers yet for ${username} \u2014 verify the username via \`kordis profile ${username}\`, or this account simply has no followers` : `${username} isn't following anyone yet \u2014 verify the username via \`kordis profile ${username}\``;
|
|
5523
5584
|
printDual({
|
|
5524
|
-
json: res,
|
|
5585
|
+
json: res.results.length === 0 ? { ...res, hint: emptyHint } : res,
|
|
5525
5586
|
human: () => {
|
|
5526
5587
|
if (res.results.length === 0) {
|
|
5527
5588
|
const noun = name === "followers" ? "followers yet" : "one yet";
|
|
5528
5589
|
printHuman(c.yellow(`(no ${noun} for ${username})`));
|
|
5590
|
+
printHuman(c.gray(` \u2192 ${emptyHint}`));
|
|
5529
5591
|
return;
|
|
5530
5592
|
}
|
|
5531
5593
|
for (const u of res.results) {
|
|
@@ -5605,10 +5667,10 @@ async function main(argv) {
|
|
|
5605
5667
|
const cleaned = argv.filter((a, i) => !(a === "--" && i >= 2));
|
|
5606
5668
|
await program.parseAsync(cleaned);
|
|
5607
5669
|
} catch (err) {
|
|
5608
|
-
handleError(err);
|
|
5670
|
+
await handleError(err);
|
|
5609
5671
|
}
|
|
5610
5672
|
}
|
|
5611
|
-
function handleError(err) {
|
|
5673
|
+
async function handleError(err) {
|
|
5612
5674
|
if (err instanceof KordisApiError) {
|
|
5613
5675
|
if (isJsonMode()) {
|
|
5614
5676
|
printJson({
|
|
@@ -5655,7 +5717,7 @@ function handleError(err) {
|
|
|
5655
5717
|
printHuman(c.red(`\u2717 ${err.message}`));
|
|
5656
5718
|
for (const line of hint.split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
5657
5719
|
}
|
|
5658
|
-
|
|
5720
|
+
await reportError("network_unreachable", err);
|
|
5659
5721
|
process.exit(err.exitCode());
|
|
5660
5722
|
}
|
|
5661
5723
|
if (err instanceof Error) {
|
|
@@ -5675,7 +5737,7 @@ function handleError(err) {
|
|
|
5675
5737
|
for (const line of hintStr.split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
5676
5738
|
}
|
|
5677
5739
|
}
|
|
5678
|
-
|
|
5740
|
+
await reportError("unhandled_error", err);
|
|
5679
5741
|
process.exit(1);
|
|
5680
5742
|
}
|
|
5681
5743
|
if (isJsonMode()) printJson({ error: { code: "generic_failure", message: String(err) } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rayul/kordis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Command-line client for the Kordis registry — discover, install, and publish agentic-coding packages.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"zod": "^3.23.8"
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
|
-
"keytar": "
|
|
40
|
+
"keytar": "7.9.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@kordis/api-client": "workspace:*",
|