@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.
@@ -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, or star/unstar a package. 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>".
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, & Stars
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 stars.
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 does NOT yet expose follow, listing comments, replies, editing or
133
- moderating comments, or notifications. If the user asks for any of those, tell
134
- them truthfully that the CLI verb is not implemented yet — do NOT fabricate a
135
- command. For now direct them to `https://kordis.store`.
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
- throw new KordisApiError(res.status, parsed, requestId);
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 Command19 } from "commander";
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 packages (requires login)").option("--limit <n>", "max results per page", (v) => parseInt(v, 10), 20).option("--page <n>", "page number (1-based)", (v) => parseInt(v, 10), 1).option("--sort <mode>", "sort: latest|popular", "latest").action(async (opts) => {
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
- requireAuth(bundle);
4302
- const spin = spinner("Fetching your packages");
4303
- const res = await bundle.authedRequest(
4304
- (api) => api.listMyPackages({ page: opts.page, limit: opts.limit, sort })
4305
- );
4306
- spin.succeed();
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.packages.length === 0) {
4311
- printHuman(c.yellow("(no packages yet \u2014 use `kordis push` to publish your first)"));
4462
+ if (res.results.length === 0) {
4463
+ printHuman(c.yellow(`(no public packages for ${username})`));
4312
4464
  return;
4313
4465
  }
4314
- printHuman(
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(`v${p.current_version}`)} ${c.cyan(`[${p.type}]`)}`
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.page * res.limit < res.total) {
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 Command19("kordis").version(VERSION).description("Kordis \u2014 registry for agentic-coding packages").option("--json", "emit JSON output to stdout (default: human)", false).hook("preAction", (thisCommand) => {
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());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rayul/kordis",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
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",