@rayul/kordis 0.7.1 → 0.8.0
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-social/SKILL.md +70 -7
- package/dist/index.js +328 -30
- package/package.json +1 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: kordis-social
|
|
3
|
-
description: Use whenever the user wants to log into Kordis, log out, check identity, post a typed comment on a Kordis package,
|
|
3
|
+
description: Use whenever the user wants to log into Kordis, log out, check identity, post a typed comment on a Kordis package, star/unstar a package, view a user's public profile, list their packages or stars, or follow/unfollow another user. Triggers include "log into Kordis", "kordis login", "who am I logged in as on Kordis", "log me out of Kordis", "leave a tip on <slug>", "report a bug on <slug>", "comment on <slug>", "star <slug>", "unstar <slug>", "favourite <slug>", "follow <username>", "unfollow <username>", "팔로우", "팔로워", "show me <username>'s profile", "프로필", "what has <username> starred", "who follows <username>", "who does <username> follow".
|
|
4
4
|
type: skill
|
|
5
5
|
allowed-tools: ["Bash(kordis:*)"]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Kordis: Identity, Comments, &
|
|
8
|
+
# Kordis: Identity, Comments, Stars, & Social
|
|
9
9
|
|
|
10
10
|
## Goal
|
|
11
11
|
Drive the user's login state and the social actions Kordis exposes — typed
|
|
12
|
-
comments and
|
|
12
|
+
comments, stars, public profiles, and follow/unfollow (0.8.0+).
|
|
13
13
|
|
|
14
14
|
## Identity commands
|
|
15
15
|
|
|
@@ -127,9 +127,72 @@ User: "unstar foo/bar"
|
|
|
127
127
|
- show updated count
|
|
128
128
|
</example>
|
|
129
129
|
|
|
130
|
+
## Profile & follow commands (0.8.0+)
|
|
131
|
+
|
|
132
|
+
### `kordis profile <username>`
|
|
133
|
+
Anonymous-OK. Shows display name, bio, follower count, package count,
|
|
134
|
+
total stars, total downloads, first/last published timestamps.
|
|
135
|
+
|
|
136
|
+
### `kordis list <username>` (extended)
|
|
137
|
+
With no argument → your own packages (auth required, offset pagination).
|
|
138
|
+
With a username → public packages of that user (anonymous-OK, cursor
|
|
139
|
+
pagination via `--cursor`).
|
|
140
|
+
|
|
141
|
+
### `kordis stars <username>`
|
|
142
|
+
Anonymous-OK. Lists packages this user has starred. Cursor-paginated.
|
|
143
|
+
|
|
144
|
+
### `kordis follow <username>` / `kordis unfollow <username>`
|
|
145
|
+
Auth required. Idempotent (re-following is a no-op, no double-count).
|
|
146
|
+
Self-follow is rejected with `usage_error`. Response carries the
|
|
147
|
+
post-mutation `follower_count` so you don't need a separate GET.
|
|
148
|
+
|
|
149
|
+
### `kordis followers <username>` / `kordis following <username>`
|
|
150
|
+
Anonymous-OK, cursor-paginated. Returns `PublicUser` rows.
|
|
151
|
+
|
|
152
|
+
<example>
|
|
153
|
+
User: "show me alice's profile"
|
|
154
|
+
- kordis profile alice --json
|
|
155
|
+
- render display_name, bio, package_count, follower_count, joined date
|
|
156
|
+
</example>
|
|
157
|
+
|
|
158
|
+
<example>
|
|
159
|
+
User: "follow alice"
|
|
160
|
+
- ALWAYS check the user is logged in first (`kordis whoami --json`); if
|
|
161
|
+
not authenticated, prompt them to run `kordis login --emit-only`
|
|
162
|
+
- kordis follow alice --json
|
|
163
|
+
- report the updated follower_count from the response
|
|
164
|
+
</example>
|
|
165
|
+
|
|
166
|
+
<example>
|
|
167
|
+
User: "what has bob starred?" / "bob이 별 누른거 보여줘"
|
|
168
|
+
- kordis stars bob --json
|
|
169
|
+
- render top results; offer `--cursor` for the next page if `next_cursor` is non-null
|
|
170
|
+
</example>
|
|
171
|
+
|
|
172
|
+
<example>
|
|
173
|
+
User: "who follows alice?"
|
|
174
|
+
- kordis followers alice --json
|
|
175
|
+
- list users; for each, display username + display_name + follower_count
|
|
176
|
+
</example>
|
|
177
|
+
|
|
178
|
+
<example>
|
|
179
|
+
User: "i don't want to follow alice anymore"
|
|
180
|
+
- ALWAYS confirm the user intent before destructive social actions (unfollow IS reversible — re-following is one command — but a courtesy confirm avoids surprises)
|
|
181
|
+
- kordis unfollow alice --json
|
|
182
|
+
- report the new follower_count
|
|
183
|
+
</example>
|
|
184
|
+
|
|
185
|
+
<example>
|
|
186
|
+
User: "kordis list popular"
|
|
187
|
+
- this is a sort-keyword collision — `popular` is NOT a username
|
|
188
|
+
- the CLI catches this client-side and points the user at `--sort popular`
|
|
189
|
+
- show the error message verbatim; don't try to "fix" it by guessing
|
|
190
|
+
</example>
|
|
191
|
+
|
|
130
192
|
## Limitations
|
|
131
193
|
|
|
132
|
-
The CLI
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
194
|
+
The CLI now exposes follow/unfollow + profile + public list/stars
|
|
195
|
+
(0.8.0+). Still NOT exposed: listing comments per user (use `kordis
|
|
196
|
+
comments <full_slug>` for per-package), per-user blocklists,
|
|
197
|
+
notifications, friend recommendations. For those direct the user to
|
|
198
|
+
`https://kordis.store` or wait for the next minor.
|
package/dist/index.js
CHANGED
|
@@ -486,7 +486,7 @@ var init_auth = __esm({
|
|
|
486
486
|
|
|
487
487
|
// ../../packages/contracts/src/api.ts
|
|
488
488
|
import { z as z9 } from "zod";
|
|
489
|
-
var SearchQuerySchema, SearchResponseSchema, TrendingQuerySchema, RisingQuerySchema, StarResponseSchema, AssetSchema, PublishPackageRequestSchema, PublishWaitQuerySchema, PublishPackageResponseSchema, PackagePathParamsSchema, ContentQuerySchema, PackageContentResponseSchema, JitReadRequestSchema, JitReadResponseSchema, PullPackageResponseSchema, ListVersionsResponseSchema, VersionDiffQuerySchema, ListCommentsQuerySchema, ListCommentsResponseSchema, UserProfileResponseSchema, UserPackagesQuerySchema;
|
|
489
|
+
var SearchQuerySchema, SearchResponseSchema, TrendingQuerySchema, RisingQuerySchema, StarResponseSchema, AssetSchema, PublishPackageRequestSchema, PublishWaitQuerySchema, PublishPackageResponseSchema, PackagePathParamsSchema, ContentQuerySchema, PackageContentResponseSchema, JitReadRequestSchema, JitReadResponseSchema, PullPackageResponseSchema, ListVersionsResponseSchema, VersionDiffQuerySchema, ListCommentsQuerySchema, ListCommentsResponseSchema, UserProfileResponseSchema, UserPackagesQuerySchema, ListUserPackagesResponseSchema, ListUserStarsResponseSchema, FollowResponseSchema, ListFollowersQuerySchema, ListFollowersResponseSchema;
|
|
490
490
|
var init_api = __esm({
|
|
491
491
|
"../../packages/contracts/src/api.ts"() {
|
|
492
492
|
"use strict";
|
|
@@ -709,6 +709,26 @@ var init_api = __esm({
|
|
|
709
709
|
limit: z9.coerce.number().int().min(1).max(100).default(20),
|
|
710
710
|
cursor: CursorSchema.optional()
|
|
711
711
|
});
|
|
712
|
+
ListUserPackagesResponseSchema = z9.object({
|
|
713
|
+
results: PackageSummarySchema.array(),
|
|
714
|
+
next_cursor: z9.string().nullable()
|
|
715
|
+
});
|
|
716
|
+
ListUserStarsResponseSchema = z9.object({
|
|
717
|
+
results: PackageSummarySchema.array(),
|
|
718
|
+
next_cursor: z9.string().nullable()
|
|
719
|
+
});
|
|
720
|
+
FollowResponseSchema = z9.object({
|
|
721
|
+
following: z9.boolean(),
|
|
722
|
+
follower_count: z9.number().int().nonnegative()
|
|
723
|
+
});
|
|
724
|
+
ListFollowersQuerySchema = z9.object({
|
|
725
|
+
limit: z9.coerce.number().int().min(1).max(100).default(50),
|
|
726
|
+
cursor: CursorSchema.optional()
|
|
727
|
+
});
|
|
728
|
+
ListFollowersResponseSchema = z9.object({
|
|
729
|
+
results: PublicUserSchema.array(),
|
|
730
|
+
next_cursor: z9.string().nullable()
|
|
731
|
+
});
|
|
712
732
|
}
|
|
713
733
|
});
|
|
714
734
|
|
|
@@ -912,6 +932,34 @@ var init_src = __esm({
|
|
|
912
932
|
});
|
|
913
933
|
|
|
914
934
|
// ../../packages/api-client/src/errors.ts
|
|
935
|
+
function httpStatusToCode(status) {
|
|
936
|
+
if (status === 401) return "not_authenticated";
|
|
937
|
+
if (status === 403) return "not_authorized";
|
|
938
|
+
if (status === 404) return "not_found";
|
|
939
|
+
if (status === 409) return "conflict";
|
|
940
|
+
if (status === 429) return "rate_limited";
|
|
941
|
+
if (status >= 500) return "internal_error";
|
|
942
|
+
if (status >= 400) return "usage_error";
|
|
943
|
+
return "generic_failure";
|
|
944
|
+
}
|
|
945
|
+
function normalizeErrorEnvelope(status, parsed, fallbackText) {
|
|
946
|
+
const isObject = (v) => typeof v === "object" && v !== null;
|
|
947
|
+
if (isObject(parsed) && isObject(parsed.error) && typeof parsed.error.code === "string" && typeof parsed.error.message === "string") {
|
|
948
|
+
return parsed;
|
|
949
|
+
}
|
|
950
|
+
let message;
|
|
951
|
+
if (isObject(parsed) && typeof parsed.message === "string") {
|
|
952
|
+
message = parsed.message;
|
|
953
|
+
} else if (fallbackText) {
|
|
954
|
+
message = fallbackText.slice(0, 500);
|
|
955
|
+
}
|
|
956
|
+
return {
|
|
957
|
+
error: {
|
|
958
|
+
code: httpStatusToCode(status),
|
|
959
|
+
message: message ?? `HTTP ${status}`
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
}
|
|
915
963
|
var KordisApiError, KordisNetworkError;
|
|
916
964
|
var init_errors2 = __esm({
|
|
917
965
|
"../../packages/api-client/src/errors.ts"() {
|
|
@@ -1027,7 +1075,8 @@ var init_client = __esm({
|
|
|
1027
1075
|
const text = await res.text();
|
|
1028
1076
|
const parsed = text ? JSON.parse(text) : void 0;
|
|
1029
1077
|
if (!res.ok) {
|
|
1030
|
-
|
|
1078
|
+
const envelope = normalizeErrorEnvelope(res.status, parsed, text);
|
|
1079
|
+
throw new KordisApiError(res.status, envelope, requestId);
|
|
1031
1080
|
}
|
|
1032
1081
|
return parsed;
|
|
1033
1082
|
}
|
|
@@ -1233,6 +1282,51 @@ var init_client = __esm({
|
|
|
1233
1282
|
body: payload
|
|
1234
1283
|
});
|
|
1235
1284
|
}
|
|
1285
|
+
// ─── social (0.8.0+) ───────────────────────────────────────────────────
|
|
1286
|
+
/** Public profile + aggregated stats. Anonymous-OK. */
|
|
1287
|
+
getUserProfile(username) {
|
|
1288
|
+
return this.request(`/api/users/${encodeURIComponent(username)}`);
|
|
1289
|
+
}
|
|
1290
|
+
/** Cursor-paginated public package list authored by `username`. */
|
|
1291
|
+
listUserPackages(username, query = {}) {
|
|
1292
|
+
return this.request(
|
|
1293
|
+
`/api/users/${encodeURIComponent(username)}/packages`,
|
|
1294
|
+
{ query }
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
/** Cursor-paginated packages that `username` has starred. */
|
|
1298
|
+
listUserStars(username, query = {}) {
|
|
1299
|
+
return this.request(
|
|
1300
|
+
`/api/users/${encodeURIComponent(username)}/stars`,
|
|
1301
|
+
{ query }
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
/** Auth-required. Idempotent (re-follow is a no-op). Returns post-mutation counter. */
|
|
1305
|
+
followUser(username) {
|
|
1306
|
+
return this.request(`/api/users/${encodeURIComponent(username)}/follow`, {
|
|
1307
|
+
method: "POST"
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
/** Auth-required. Idempotent (unfollow of non-edge is a no-op). */
|
|
1311
|
+
unfollowUser(username) {
|
|
1312
|
+
return this.request(`/api/users/${encodeURIComponent(username)}/follow`, {
|
|
1313
|
+
method: "DELETE"
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
/** Cursor-paginated followers of `username`. Anonymous-OK. */
|
|
1317
|
+
listUserFollowers(username, query = {}) {
|
|
1318
|
+
return this.request(
|
|
1319
|
+
`/api/users/${encodeURIComponent(username)}/followers`,
|
|
1320
|
+
{ query }
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
/** Cursor-paginated users that `username` follows. Anonymous-OK. */
|
|
1324
|
+
listUserFollowing(username, query = {}) {
|
|
1325
|
+
return this.request(
|
|
1326
|
+
`/api/users/${encodeURIComponent(username)}/following`,
|
|
1327
|
+
{ query }
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1236
1330
|
};
|
|
1237
1331
|
}
|
|
1238
1332
|
});
|
|
@@ -1242,7 +1336,8 @@ var src_exports = {};
|
|
|
1242
1336
|
__export(src_exports, {
|
|
1243
1337
|
KordisApiError: () => KordisApiError,
|
|
1244
1338
|
KordisClient: () => KordisClient,
|
|
1245
|
-
KordisNetworkError: () => KordisNetworkError
|
|
1339
|
+
KordisNetworkError: () => KordisNetworkError,
|
|
1340
|
+
normalizeErrorEnvelope: () => normalizeErrorEnvelope
|
|
1246
1341
|
});
|
|
1247
1342
|
var init_src2 = __esm({
|
|
1248
1343
|
"../../packages/api-client/src/index.ts"() {
|
|
@@ -1254,7 +1349,7 @@ var init_src2 = __esm({
|
|
|
1254
1349
|
|
|
1255
1350
|
// src/index.ts
|
|
1256
1351
|
init_src2();
|
|
1257
|
-
import { Command as
|
|
1352
|
+
import { Command as Command23 } from "commander";
|
|
1258
1353
|
|
|
1259
1354
|
// src/commands/login.ts
|
|
1260
1355
|
import { Command } from "commander";
|
|
@@ -4294,44 +4389,91 @@ function deleteCommand() {
|
|
|
4294
4389
|
|
|
4295
4390
|
// src/commands/list.ts
|
|
4296
4391
|
import { Command as Command13 } from "commander";
|
|
4392
|
+
|
|
4393
|
+
// src/lib/parse-username.ts
|
|
4394
|
+
init_src();
|
|
4395
|
+
var SORT_KEYWORDS = ["latest", "popular", "recent", "updated", "stars", "downloads"];
|
|
4396
|
+
function parseUsername(raw) {
|
|
4397
|
+
if (SORT_KEYWORDS.includes(raw.toLowerCase())) {
|
|
4398
|
+
throw new Error(
|
|
4399
|
+
`"${raw}" is a sort keyword \u2014 did you mean \`--sort ${raw}\`? Username arguments must follow the Kordis username shape (lowercase alphanumeric, hyphens allowed).`
|
|
4400
|
+
);
|
|
4401
|
+
}
|
|
4402
|
+
const parsed = UsernameSchema.safeParse(raw);
|
|
4403
|
+
if (!parsed.success) {
|
|
4404
|
+
throw new Error(
|
|
4405
|
+
`invalid username "${raw}" \u2014 must be 3-32 chars, lowercase alphanumeric with single hyphens (e.g. "alice", "my-team")`
|
|
4406
|
+
);
|
|
4407
|
+
}
|
|
4408
|
+
return parsed.data;
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4411
|
+
// src/commands/list.ts
|
|
4297
4412
|
function listCommand() {
|
|
4298
|
-
return new Command13("list").description("list your own
|
|
4299
|
-
const sort = opts.sort === "popular" ? "popular" : "latest";
|
|
4413
|
+
return new Command13("list").description("list packages \u2014 your own (no arg) or a public user\u2019s (with <username>)").argument("[username]", "kordis username to list (omit for self)").option("--limit <n>", "max results per page", (v) => parseInt(v, 10), 20).option("--page <n>", "self-mode page number (1-based)", (v) => parseInt(v, 10), 1).option("--cursor <token>", "public-mode pagination cursor").option("--sort <mode>", "sort: latest|popular (self) or stars|downloads|recent|updated (public)", "latest").action(async (rawUsername, opts) => {
|
|
4300
4414
|
const bundle = await useClient();
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4415
|
+
if (rawUsername === void 0) {
|
|
4416
|
+
requireAuth(bundle);
|
|
4417
|
+
const sort = opts.sort === "popular" ? "popular" : "latest";
|
|
4418
|
+
const spin = spinner("Fetching your packages");
|
|
4419
|
+
const res2 = await bundle.authedRequest(
|
|
4420
|
+
(api) => api.listMyPackages({ page: opts.page, limit: opts.limit, sort })
|
|
4421
|
+
);
|
|
4422
|
+
spin.succeed();
|
|
4423
|
+
printDual({
|
|
4424
|
+
json: res2,
|
|
4425
|
+
human: () => {
|
|
4426
|
+
if (res2.packages.length === 0) {
|
|
4427
|
+
printHuman(c.yellow("(no packages yet \u2014 use `kordis push` to publish your first)"));
|
|
4428
|
+
return;
|
|
4429
|
+
}
|
|
4430
|
+
printHuman(
|
|
4431
|
+
c.gray(
|
|
4432
|
+
`Showing ${res2.packages.length} of ${res2.total} package${res2.total !== 1 ? "s" : ""} (page ${res2.page})`
|
|
4433
|
+
)
|
|
4434
|
+
);
|
|
4435
|
+
printHuman("");
|
|
4436
|
+
for (const p of res2.packages) {
|
|
4437
|
+
const desc = p.description.length > 60 ? p.description.slice(0, 57) + "..." : p.description;
|
|
4438
|
+
printHuman(
|
|
4439
|
+
`${c.bold(p.full_slug)} ${c.gray(`v${p.current_version}`)} ${c.cyan(`[${p.type}]`)}`
|
|
4440
|
+
);
|
|
4441
|
+
printHuman(` ${desc}`);
|
|
4442
|
+
printHuman(c.gray(` \u2605 ${p.stars} \u21E3 ${p.downloads} ${p.visibility}`));
|
|
4443
|
+
printHuman("");
|
|
4444
|
+
}
|
|
4445
|
+
if (res2.page * res2.limit < res2.total) {
|
|
4446
|
+
printHuman(c.gray(`(more available \u2014 pass --page ${res2.page + 1})`));
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
});
|
|
4450
|
+
return;
|
|
4451
|
+
}
|
|
4452
|
+
const username = parseUsername(rawUsername);
|
|
4453
|
+
const publicSort = ["stars", "downloads", "recent", "updated"].includes(opts.sort) ? opts.sort : "stars";
|
|
4454
|
+
const res = await bundle.api.listUserPackages(username, {
|
|
4455
|
+
limit: opts.limit,
|
|
4456
|
+
sort: publicSort,
|
|
4457
|
+
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
4458
|
+
});
|
|
4307
4459
|
printDual({
|
|
4308
4460
|
json: res,
|
|
4309
4461
|
human: () => {
|
|
4310
|
-
if (res.
|
|
4311
|
-
printHuman(c.yellow(
|
|
4462
|
+
if (res.results.length === 0) {
|
|
4463
|
+
printHuman(c.yellow(`(no public packages for ${username})`));
|
|
4312
4464
|
return;
|
|
4313
4465
|
}
|
|
4314
|
-
|
|
4315
|
-
c.gray(
|
|
4316
|
-
`Showing ${res.packages.length} of ${res.total} package${res.total !== 1 ? "s" : ""} (page ${res.page})`
|
|
4317
|
-
)
|
|
4318
|
-
);
|
|
4319
|
-
printHuman("");
|
|
4320
|
-
for (const p of res.packages) {
|
|
4466
|
+
for (const p of res.results) {
|
|
4321
4467
|
const desc = p.description.length > 60 ? p.description.slice(0, 57) + "..." : p.description;
|
|
4322
4468
|
printHuman(
|
|
4323
|
-
`${c.bold(p.full_slug)} ${c.gray(
|
|
4469
|
+
`${c.bold(p.full_slug)} ${c.gray("v" + p.current_version)} ${c.cyan("[" + p.type + "]")}`
|
|
4324
4470
|
);
|
|
4325
4471
|
printHuman(` ${desc}`);
|
|
4326
|
-
printHuman(
|
|
4327
|
-
c.gray(` \u2605 ${p.stars} \u21E3 ${p.downloads} ${p.visibility}`)
|
|
4328
|
-
);
|
|
4472
|
+
printHuman(c.gray(` \u2605 ${p.stars} \u21E3 ${p.downloads}`));
|
|
4329
4473
|
printHuman("");
|
|
4330
4474
|
}
|
|
4331
|
-
if (res.
|
|
4332
|
-
printHuman(
|
|
4333
|
-
c.gray(`(more available \u2014 pass --page ${res.page + 1})`)
|
|
4334
|
-
);
|
|
4475
|
+
if (res.next_cursor) {
|
|
4476
|
+
printHuman(c.gray(`(more available \u2014 kordis list ${username} --cursor ${res.next_cursor})`));
|
|
4335
4477
|
}
|
|
4336
4478
|
}
|
|
4337
4479
|
});
|
|
@@ -4793,9 +4935,159 @@ function renderItem(idx, it, verbose) {
|
|
|
4793
4935
|
printHuman("");
|
|
4794
4936
|
}
|
|
4795
4937
|
|
|
4938
|
+
// src/commands/profile.ts
|
|
4939
|
+
import { Command as Command19 } from "commander";
|
|
4940
|
+
function profileCommand() {
|
|
4941
|
+
return new Command19("profile").description("inspect a user\u2019s public profile").argument("<username>", "kordis username").action(async (raw) => {
|
|
4942
|
+
const username = parseUsername(raw);
|
|
4943
|
+
const bundle = await useClient();
|
|
4944
|
+
const profile = await bundle.api.getUserProfile(username);
|
|
4945
|
+
printDual({
|
|
4946
|
+
json: profile,
|
|
4947
|
+
human: () => {
|
|
4948
|
+
const verified = profile.is_verified ? c.cyan(" \u2713") : "";
|
|
4949
|
+
const displayLine = profile.display_name ? `${c.bold(profile.username)}${verified} ${c.gray("(" + profile.display_name + ")")}` : `${c.bold(profile.username)}${verified}`;
|
|
4950
|
+
printHuman(displayLine);
|
|
4951
|
+
if (profile.bio) printHuman(c.gray(profile.bio));
|
|
4952
|
+
printHuman("");
|
|
4953
|
+
printHuman(
|
|
4954
|
+
` ${c.bold(String(profile.stats.package_count))} packages \xB7 ${c.bold(String(profile.stats.total_stars))} \u2605 across all \xB7 ${c.bold(String(profile.stats.total_downloads))} downloads`
|
|
4955
|
+
);
|
|
4956
|
+
printHuman(
|
|
4957
|
+
` ${c.bold(String(profile.follower_count))} follower${profile.follower_count === 1 ? "" : "s"} \xB7 joined ${profile.created_at.split("T")[0]}`
|
|
4958
|
+
);
|
|
4959
|
+
if (profile.stats.last_published_at) {
|
|
4960
|
+
printHuman(
|
|
4961
|
+
c.gray(
|
|
4962
|
+
` last published: ${profile.stats.last_published_at.split("T")[0]}`
|
|
4963
|
+
)
|
|
4964
|
+
);
|
|
4965
|
+
}
|
|
4966
|
+
printHuman("");
|
|
4967
|
+
printHuman(c.gray(` \u2192 list packages: kordis list ${profile.username}`));
|
|
4968
|
+
printHuman(c.gray(` \u2192 list stars: kordis stars ${profile.username}`));
|
|
4969
|
+
printHuman(c.gray(` \u2192 follow: kordis follow ${profile.username}`));
|
|
4970
|
+
}
|
|
4971
|
+
});
|
|
4972
|
+
});
|
|
4973
|
+
}
|
|
4974
|
+
|
|
4975
|
+
// src/commands/stars.ts
|
|
4976
|
+
import { Command as Command20 } from "commander";
|
|
4977
|
+
function starsCommand() {
|
|
4978
|
+
return new Command20("stars").description("list packages a user has starred").argument("<username>", "kordis username").option("--limit <n>", "max results per page (1-100, default 50)", (v) => parseInt(v, 10), 50).option("--cursor <token>", "pagination cursor returned by a prior response").action(async (raw, opts) => {
|
|
4979
|
+
const username = parseUsername(raw);
|
|
4980
|
+
const bundle = await useClient();
|
|
4981
|
+
const res = await bundle.api.listUserStars(username, {
|
|
4982
|
+
limit: opts.limit,
|
|
4983
|
+
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
4984
|
+
});
|
|
4985
|
+
printDual({
|
|
4986
|
+
json: res,
|
|
4987
|
+
human: () => {
|
|
4988
|
+
if (res.results.length === 0) {
|
|
4989
|
+
printHuman(c.yellow(`(no stars yet for ${username})`));
|
|
4990
|
+
printHuman(c.gray(` \u2192 they may not have starred any public packages, or have set stars to private`));
|
|
4991
|
+
return;
|
|
4992
|
+
}
|
|
4993
|
+
for (const p of res.results) {
|
|
4994
|
+
printHuman(
|
|
4995
|
+
`${c.bold(p.full_slug)} ${c.gray("v" + p.current_version)} ${c.cyan("[" + p.type + "]")}`
|
|
4996
|
+
);
|
|
4997
|
+
printHuman(` ${p.description}`);
|
|
4998
|
+
printHuman(c.gray(` \u2605 ${p.stars} \xB7 \u21E3 ${p.downloads}`));
|
|
4999
|
+
printHuman("");
|
|
5000
|
+
}
|
|
5001
|
+
if (res.next_cursor) {
|
|
5002
|
+
printHuman(c.gray(`(more available \u2014 kordis stars ${username} --cursor ${res.next_cursor})`));
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
});
|
|
5006
|
+
});
|
|
5007
|
+
}
|
|
5008
|
+
|
|
5009
|
+
// src/commands/follow.ts
|
|
5010
|
+
import { Command as Command21 } from "commander";
|
|
5011
|
+
function followCommand() {
|
|
5012
|
+
return new Command21("follow").description("follow a kordis user").argument("<username>", "kordis username").action(async (raw) => {
|
|
5013
|
+
const username = parseUsername(raw);
|
|
5014
|
+
const bundle = await useClient();
|
|
5015
|
+
requireAuth(bundle);
|
|
5016
|
+
const res = await bundle.authedRequest((api) => api.followUser(username));
|
|
5017
|
+
printDual({
|
|
5018
|
+
json: res,
|
|
5019
|
+
human: () => {
|
|
5020
|
+
printHuman(c.green(`\u2713 following ${username}`));
|
|
5021
|
+
printHuman(c.gray(` total followers: ${res.follower_count}`));
|
|
5022
|
+
}
|
|
5023
|
+
});
|
|
5024
|
+
});
|
|
5025
|
+
}
|
|
5026
|
+
function unfollowCommand() {
|
|
5027
|
+
return new Command21("unfollow").description("stop following a kordis user").argument("<username>", "kordis username").action(async (raw) => {
|
|
5028
|
+
const username = parseUsername(raw);
|
|
5029
|
+
const bundle = await useClient();
|
|
5030
|
+
requireAuth(bundle);
|
|
5031
|
+
const res = await bundle.authedRequest((api) => api.unfollowUser(username));
|
|
5032
|
+
printDual({
|
|
5033
|
+
json: res,
|
|
5034
|
+
human: () => {
|
|
5035
|
+
printHuman(c.gray(`\u2717 unfollowed ${username}`));
|
|
5036
|
+
printHuman(c.gray(` total followers: ${res.follower_count}`));
|
|
5037
|
+
}
|
|
5038
|
+
});
|
|
5039
|
+
});
|
|
5040
|
+
}
|
|
5041
|
+
|
|
5042
|
+
// src/commands/followers.ts
|
|
5043
|
+
import { Command as Command22 } from "commander";
|
|
5044
|
+
function makeListing(name) {
|
|
5045
|
+
return new Command22(name).description(
|
|
5046
|
+
name === "followers" ? "list users following <username>" : "list users that <username> follows"
|
|
5047
|
+
).argument("<username>", "kordis username").option("--limit <n>", "max results per page (1-100, default 50)", (v) => parseInt(v, 10), 50).option("--cursor <token>", "pagination cursor returned by a prior response").action(async (raw, opts) => {
|
|
5048
|
+
const username = parseUsername(raw);
|
|
5049
|
+
const bundle = await useClient();
|
|
5050
|
+
const fn = name === "followers" ? bundle.api.listUserFollowers.bind(bundle.api) : bundle.api.listUserFollowing.bind(bundle.api);
|
|
5051
|
+
const res = await fn(username, {
|
|
5052
|
+
limit: opts.limit,
|
|
5053
|
+
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
5054
|
+
});
|
|
5055
|
+
printDual({
|
|
5056
|
+
json: res,
|
|
5057
|
+
human: () => {
|
|
5058
|
+
if (res.results.length === 0) {
|
|
5059
|
+
const noun = name === "followers" ? "followers yet" : "one yet";
|
|
5060
|
+
printHuman(c.yellow(`(no ${noun} for ${username})`));
|
|
5061
|
+
return;
|
|
5062
|
+
}
|
|
5063
|
+
for (const u of res.results) {
|
|
5064
|
+
const verified = u.is_verified ? c.cyan(" \u2713") : "";
|
|
5065
|
+
const display = u.display_name ? ` ${c.gray("(" + u.display_name + ")")}` : "";
|
|
5066
|
+
printHuman(`${c.bold(u.username)}${verified}${display}`);
|
|
5067
|
+
printHuman(
|
|
5068
|
+
c.gray(
|
|
5069
|
+
` ${u.follower_count} follower${u.follower_count === 1 ? "" : "s"} \xB7 joined ${u.created_at.split("T")[0]}`
|
|
5070
|
+
)
|
|
5071
|
+
);
|
|
5072
|
+
printHuman("");
|
|
5073
|
+
}
|
|
5074
|
+
if (res.next_cursor) {
|
|
5075
|
+
printHuman(c.gray(`(more available \u2014 kordis ${name} ${username} --cursor ${res.next_cursor})`));
|
|
5076
|
+
}
|
|
5077
|
+
}
|
|
5078
|
+
});
|
|
5079
|
+
});
|
|
5080
|
+
}
|
|
5081
|
+
function followersCommand() {
|
|
5082
|
+
return makeListing("followers");
|
|
5083
|
+
}
|
|
5084
|
+
function followingCommand() {
|
|
5085
|
+
return makeListing("following");
|
|
5086
|
+
}
|
|
5087
|
+
|
|
4796
5088
|
// src/index.ts
|
|
4797
5089
|
function buildProgram() {
|
|
4798
|
-
const program = new
|
|
5090
|
+
const program = new Command23("kordis").version(VERSION).description("Kordis \u2014 registry for agentic-coding packages").option("--json", "emit JSON output to stdout (default: human)", false).hook("preAction", (thisCommand) => {
|
|
4799
5091
|
const opts = thisCommand.optsWithGlobals();
|
|
4800
5092
|
if (opts.json) setJsonMode(true);
|
|
4801
5093
|
}).addHelpText("after", `
|
|
@@ -4821,6 +5113,12 @@ Environment variables (see README.md "Privacy & environment variables"):
|
|
|
4821
5113
|
program.addCommand(uncommentCommand());
|
|
4822
5114
|
program.addCommand(starCommand());
|
|
4823
5115
|
program.addCommand(unstarCommand());
|
|
5116
|
+
program.addCommand(profileCommand());
|
|
5117
|
+
program.addCommand(starsCommand());
|
|
5118
|
+
program.addCommand(followCommand());
|
|
5119
|
+
program.addCommand(unfollowCommand());
|
|
5120
|
+
program.addCommand(followersCommand());
|
|
5121
|
+
program.addCommand(followingCommand());
|
|
4824
5122
|
program.addCommand(deleteCommand());
|
|
4825
5123
|
program.addCommand(listCommand());
|
|
4826
5124
|
program.addCommand(versionsCommand());
|