@rayul/kordis 0.0.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/dist/index.js ADDED
@@ -0,0 +1,2483 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // ../../packages/contracts/src/primitives.ts
13
+ import { z } from "zod";
14
+ var UuidSchema, IsoDateTimeSchema, UsernameSchema, SlugSchema, FullSlugSchema, SemverSchema, SemverRangeSchema, CursorSchema, ToolSlugSchema, TagSchema, UrlSchema;
15
+ var init_primitives = __esm({
16
+ "../../packages/contracts/src/primitives.ts"() {
17
+ "use strict";
18
+ UuidSchema = z.string().uuid();
19
+ IsoDateTimeSchema = z.string().datetime({ offset: true });
20
+ UsernameSchema = z.string().min(3).max(32).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "username must be lowercase alphanumeric with single hyphens");
21
+ SlugSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "slug must be lowercase alphanumeric with single hyphens");
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(
24
+ // eslint-disable-next-line no-useless-escape
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
+ "must be a valid semver version"
27
+ );
28
+ SemverRangeSchema = z.string().min(1).max(32);
29
+ CursorSchema = z.string().min(1).max(512);
30
+ ToolSlugSchema = z.enum([
31
+ "claude-code",
32
+ "codex",
33
+ "cursor",
34
+ "antigravity",
35
+ "windsurf",
36
+ "continue",
37
+ "copilot",
38
+ "opencode",
39
+ "gemini-cli"
40
+ ]);
41
+ TagSchema = z.string().min(1).max(32).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/);
42
+ UrlSchema = z.string().url().startsWith("http");
43
+ }
44
+ });
45
+
46
+ // ../../packages/contracts/src/enums.ts
47
+ import { z as z2 } from "zod";
48
+ var HarnessTypeSchema, PHASE_1_TYPES, TierSchema, TIER_OF_TYPE, SortModeSchema, TimeWindowSchema, VisibilitySchema, ReviewStatusSchema, PublishStatusSchema, DependencyKindSchema, OAuthProviderSchema, UserRoleSchema, ReportReasonSchema;
49
+ var init_enums = __esm({
50
+ "../../packages/contracts/src/enums.ts"() {
51
+ "use strict";
52
+ HarnessTypeSchema = z2.enum([
53
+ "mcp-server",
54
+ "project-rules",
55
+ "skill",
56
+ "custom-agent",
57
+ "hook",
58
+ "scoped-rule",
59
+ "mdc-rule",
60
+ "plugin"
61
+ ]);
62
+ PHASE_1_TYPES = [
63
+ "mcp-server",
64
+ "project-rules",
65
+ "skill",
66
+ "custom-agent",
67
+ "hook"
68
+ ];
69
+ TierSchema = z2.enum(["A", "B", "C", "D"]);
70
+ TIER_OF_TYPE = {
71
+ "mcp-server": "A",
72
+ "project-rules": "A",
73
+ skill: "A",
74
+ "custom-agent": "B",
75
+ "scoped-rule": "B",
76
+ hook: "C",
77
+ "mdc-rule": "C",
78
+ plugin: "D"
79
+ };
80
+ SortModeSchema = z2.enum([
81
+ "stars",
82
+ "downloads",
83
+ "recent",
84
+ "updated",
85
+ "trending",
86
+ "rising",
87
+ "relevance"
88
+ ]);
89
+ TimeWindowSchema = z2.enum(["24h", "7d", "30d"]);
90
+ VisibilitySchema = z2.enum(["public", "draft", "unlisted", "archived"]);
91
+ ReviewStatusSchema = z2.enum(["passed", "rejected", "pending_review"]);
92
+ PublishStatusSchema = z2.enum(["published", "rejected", "pending"]);
93
+ DependencyKindSchema = z2.enum(["required", "recommended"]);
94
+ OAuthProviderSchema = z2.enum(["discord", "github", "google"]);
95
+ UserRoleSchema = z2.enum(["user", "moderator", "admin"]);
96
+ ReportReasonSchema = z2.enum([
97
+ "spam",
98
+ "harmful",
99
+ "incorrect",
100
+ "copyright",
101
+ "other"
102
+ ]);
103
+ }
104
+ });
105
+
106
+ // ../../packages/contracts/src/errors.ts
107
+ import { z as z3 } from "zod";
108
+ var ErrorCodeSchema, ERROR_EXIT_CODES, ReviewFlagSchema, ApiErrorSchema;
109
+ var init_errors = __esm({
110
+ "../../packages/contracts/src/errors.ts"() {
111
+ "use strict";
112
+ ErrorCodeSchema = z3.enum([
113
+ "generic_failure",
114
+ // 1
115
+ "usage_error",
116
+ // 2
117
+ "not_authenticated",
118
+ // 10
119
+ "not_authorized",
120
+ // 11
121
+ "network_unreachable",
122
+ // 20
123
+ "validation_failed",
124
+ // 30 (structure stage)
125
+ "rate_limited",
126
+ // 31
127
+ "ai_review_rejected",
128
+ // 32
129
+ /**
130
+ * jit_blocked — `kordis read` (POST /:u/:s/read) refused.
131
+ * Reasons surfaced via `flags`: type ineligible (mcp-server, hook),
132
+ * review not passed (and `--allow-pending` not set). See plan v1.1 JIT Read.
133
+ */
134
+ "jit_blocked",
135
+ // 33
136
+ "conflict",
137
+ // 40 (slug taken, version exists)
138
+ "filesystem_error",
139
+ // 50
140
+ "not_found",
141
+ "internal_error"
142
+ ]);
143
+ ERROR_EXIT_CODES = {
144
+ generic_failure: 1,
145
+ usage_error: 2,
146
+ not_authenticated: 10,
147
+ not_authorized: 11,
148
+ network_unreachable: 20,
149
+ validation_failed: 30,
150
+ rate_limited: 31,
151
+ ai_review_rejected: 32,
152
+ jit_blocked: 33,
153
+ conflict: 40,
154
+ filesystem_error: 50,
155
+ not_found: 1,
156
+ internal_error: 1
157
+ };
158
+ ReviewFlagSchema = z3.object({
159
+ code: z3.string().min(1).max(64),
160
+ message: z3.string().optional(),
161
+ field: z3.string().optional(),
162
+ hint: z3.string().optional()
163
+ });
164
+ ApiErrorSchema = z3.object({
165
+ error: z3.object({
166
+ code: ErrorCodeSchema,
167
+ message: z3.string(),
168
+ flags: z3.array(ReviewFlagSchema).optional(),
169
+ retry_after_seconds: z3.number().int().nonnegative().optional(),
170
+ request_id: z3.string().optional()
171
+ })
172
+ });
173
+ }
174
+ });
175
+
176
+ // ../../packages/contracts/src/pagination.ts
177
+ import { z as z4 } from "zod";
178
+ function paginatedResponseSchema(item) {
179
+ return z4.object({
180
+ results: z4.array(item),
181
+ next_cursor: z4.string().nullable(),
182
+ total: z4.number().int().nonnegative().nullable(),
183
+ sort: SortModeSchema.nullable(),
184
+ window: TimeWindowSchema.nullable()
185
+ });
186
+ }
187
+ var ListQuerySchema;
188
+ var init_pagination = __esm({
189
+ "../../packages/contracts/src/pagination.ts"() {
190
+ "use strict";
191
+ init_primitives();
192
+ init_enums();
193
+ ListQuerySchema = z4.object({
194
+ sort: SortModeSchema.optional(),
195
+ limit: z4.coerce.number().int().min(1).max(100).default(20),
196
+ cursor: CursorSchema.optional()
197
+ });
198
+ }
199
+ });
200
+
201
+ // ../../packages/contracts/src/user.ts
202
+ import { z as z5 } from "zod";
203
+ var PublicUserSchema, SelfUserSchema, UserStatsSchema;
204
+ var init_user = __esm({
205
+ "../../packages/contracts/src/user.ts"() {
206
+ "use strict";
207
+ init_primitives();
208
+ init_enums();
209
+ PublicUserSchema = z5.object({
210
+ id: UuidSchema,
211
+ username: UsernameSchema,
212
+ display_name: z5.string().max(64).nullable(),
213
+ bio: z5.string().max(500).nullable(),
214
+ avatar_url: UrlSchema.nullable(),
215
+ is_verified: z5.boolean(),
216
+ package_count: z5.number().int().nonnegative(),
217
+ follower_count: z5.number().int().nonnegative(),
218
+ created_at: IsoDateTimeSchema
219
+ });
220
+ SelfUserSchema = PublicUserSchema.extend({
221
+ primary_provider: OAuthProviderSchema,
222
+ role: UserRoleSchema,
223
+ following_count: z5.number().int().nonnegative(),
224
+ last_active_at: IsoDateTimeSchema.nullable()
225
+ });
226
+ UserStatsSchema = z5.object({
227
+ username: UsernameSchema,
228
+ package_count: z5.number().int().nonnegative(),
229
+ total_stars: z5.number().int().nonnegative(),
230
+ total_downloads: z5.number().int().nonnegative(),
231
+ total_pulls_7d: z5.number().int().nonnegative(),
232
+ trending_count: z5.number().int().nonnegative(),
233
+ first_published_at: IsoDateTimeSchema.nullable(),
234
+ last_published_at: IsoDateTimeSchema.nullable()
235
+ });
236
+ }
237
+ });
238
+
239
+ // ../../packages/contracts/src/package.ts
240
+ import { z as z6 } from "zod";
241
+ var DependencySpecSchema, PackageFrontmatterSchema, PackageSummarySchema, PackageDetailSchema, PackageVersionSchema, PackageVersionSummarySchema, VersionDiffSchema;
242
+ var init_package = __esm({
243
+ "../../packages/contracts/src/package.ts"() {
244
+ "use strict";
245
+ init_primitives();
246
+ init_enums();
247
+ init_user();
248
+ init_errors();
249
+ DependencySpecSchema = z6.object({
250
+ full_slug: FullSlugSchema,
251
+ version_range: SemverRangeSchema.default("*"),
252
+ kind: DependencyKindSchema
253
+ });
254
+ PackageFrontmatterSchema = z6.object({
255
+ title: z6.string().min(1).max(200),
256
+ description: z6.string().min(1).max(500),
257
+ type: HarnessTypeSchema,
258
+ tools: z6.array(ToolSlugSchema).optional().default([]),
259
+ tags: z6.array(TagSchema).max(16).optional().default([]),
260
+ version: SemverSchema.optional(),
261
+ depends: z6.array(z6.string()).optional().default([]),
262
+ recommends: z6.array(z6.string()).optional().default([]),
263
+ meta: z6.record(z6.unknown()).optional().default({})
264
+ });
265
+ PackageSummarySchema = z6.object({
266
+ full_slug: FullSlugSchema,
267
+ title: z6.string(),
268
+ description: z6.string(),
269
+ type: HarnessTypeSchema,
270
+ tier: TierSchema,
271
+ current_version: SemverSchema,
272
+ author: PublicUserSchema.pick({
273
+ username: true,
274
+ display_name: true,
275
+ avatar_url: true,
276
+ is_verified: true
277
+ }),
278
+ stars: z6.number().int().nonnegative(),
279
+ downloads: z6.number().int().nonnegative(),
280
+ trending_score: z6.number().nullable(),
281
+ tools_compatible: z6.array(ToolSlugSchema),
282
+ tags: z6.array(TagSchema),
283
+ agent_skills_compliant: z6.boolean().optional(),
284
+ review_status: ReviewStatusSchema,
285
+ visibility: VisibilitySchema,
286
+ installed_locally: z6.boolean().optional(),
287
+ // stitched in by CLI, not server
288
+ quick_install: z6.string(),
289
+ url: UrlSchema,
290
+ created_at: IsoDateTimeSchema,
291
+ updated_at: IsoDateTimeSchema
292
+ });
293
+ PackageDetailSchema = PackageSummarySchema.extend({
294
+ id: UuidSchema,
295
+ slug: SlugSchema,
296
+ username: UsernameSchema,
297
+ pulls_7d: z6.number().int().nonnegative(),
298
+ pulls_30d: z6.number().int().nonnegative(),
299
+ comment_count: z6.number().int().nonnegative(),
300
+ rising_delta_ratio: z6.number().nullable(),
301
+ depends: z6.array(DependencySpecSchema),
302
+ recommends: z6.array(DependencySpecSchema),
303
+ categories: z6.array(z6.string()).optional().default([]),
304
+ verified_by_kordis: z6.boolean()
305
+ });
306
+ PackageVersionSchema = z6.object({
307
+ id: UuidSchema,
308
+ package_id: UuidSchema,
309
+ version: SemverSchema,
310
+ content: z6.string(),
311
+ changelog: z6.string().nullable(),
312
+ author: PublicUserSchema.pick({ username: true, display_name: true }),
313
+ is_prerelease: z6.boolean(),
314
+ review_status: ReviewStatusSchema,
315
+ review_score: z6.number().nullable(),
316
+ review_flags: z6.array(ReviewFlagSchema),
317
+ published_at: IsoDateTimeSchema
318
+ });
319
+ PackageVersionSummarySchema = PackageVersionSchema.omit({ content: true });
320
+ VersionDiffSchema = z6.object({
321
+ full_slug: FullSlugSchema,
322
+ from: SemverSchema,
323
+ to: SemverSchema,
324
+ unified_diff: z6.string(),
325
+ stats: z6.object({
326
+ additions: z6.number().int().nonnegative(),
327
+ deletions: z6.number().int().nonnegative(),
328
+ files_changed: z6.number().int().nonnegative()
329
+ })
330
+ });
331
+ }
332
+ });
333
+
334
+ // ../../packages/contracts/src/comment.ts
335
+ import { z as z7 } from "zod";
336
+ var CommentKindSchema, CommentStatusSchema, CommentSchema, CommentTreeSchema, CreateCommentRequestSchema, UpdateCommentRequestSchema, UpdateCommentStatusRequestSchema;
337
+ var init_comment = __esm({
338
+ "../../packages/contracts/src/comment.ts"() {
339
+ "use strict";
340
+ init_primitives();
341
+ init_user();
342
+ CommentKindSchema = z7.enum([
343
+ "comment",
344
+ "tip",
345
+ "report",
346
+ "praise",
347
+ "question"
348
+ ]);
349
+ CommentStatusSchema = z7.enum(["open", "resolved"]);
350
+ CommentSchema = z7.object({
351
+ id: UuidSchema,
352
+ package_id: UuidSchema,
353
+ author: PublicUserSchema.pick({
354
+ username: true,
355
+ display_name: true,
356
+ avatar_url: true
357
+ }),
358
+ kind: CommentKindSchema,
359
+ title: z7.string().min(1).max(140).nullable(),
360
+ status: CommentStatusSchema.nullable(),
361
+ body: z7.string().min(1).max(1e4),
362
+ parent_id: UuidSchema.nullable(),
363
+ depth: z7.number().int().nonnegative(),
364
+ path: z7.string(),
365
+ reply_count: z7.number().int().nonnegative(),
366
+ reaction_count: z7.number().int().nonnegative(),
367
+ is_edited: z7.boolean(),
368
+ edited_at: IsoDateTimeSchema.nullable(),
369
+ is_deleted: z7.boolean(),
370
+ created_at: IsoDateTimeSchema
371
+ });
372
+ CommentTreeSchema = CommentSchema.extend({
373
+ replies: z7.lazy(() => z7.array(CommentTreeSchema))
374
+ });
375
+ CreateCommentRequestSchema = z7.object({
376
+ body: z7.string().min(1).max(1e4),
377
+ parent_id: UuidSchema.nullable().optional(),
378
+ kind: CommentKindSchema.optional().default("comment"),
379
+ title: z7.string().min(1).max(140).nullable().optional()
380
+ });
381
+ UpdateCommentRequestSchema = z7.object({
382
+ body: z7.string().min(1).max(1e4),
383
+ /** Allow editing the headline at the same time. */
384
+ title: z7.string().min(1).max(140).nullable().optional()
385
+ });
386
+ UpdateCommentStatusRequestSchema = z7.object({
387
+ status: CommentStatusSchema
388
+ });
389
+ }
390
+ });
391
+
392
+ // ../../packages/contracts/src/auth.ts
393
+ import { z as z8 } from "zod";
394
+ var PrincipalSchema, OAuthStartQuerySchema, DeviceCodeRequestSchema, DeviceCodeResponseSchema, DeviceTokenRequestSchema, TokenPairSchema, RefreshRequestSchema, SessionInfoSchema;
395
+ var init_auth = __esm({
396
+ "../../packages/contracts/src/auth.ts"() {
397
+ "use strict";
398
+ init_primitives();
399
+ init_enums();
400
+ PrincipalSchema = z8.object({
401
+ user_id: UuidSchema,
402
+ session_id: UuidSchema.nullable(),
403
+ // null when authenticated via API token
404
+ api_token_id: UuidSchema.nullable(),
405
+ role: UserRoleSchema,
406
+ scopes: z8.array(z8.string()).default([])
407
+ });
408
+ OAuthStartQuerySchema = z8.object({
409
+ /**
410
+ * Optional redirect to complete after login. Client-initiated.
411
+ * Must match one of the registered return URLs.
412
+ */
413
+ return_to: z8.string().url().optional(),
414
+ /**
415
+ * Set when the CLI triggers login. The server writes the session into a
416
+ * transient cookie and the CLI polls a device-code endpoint to claim it.
417
+ */
418
+ from: z8.enum(["web", "cli"]).default("web")
419
+ });
420
+ DeviceCodeRequestSchema = z8.object({
421
+ provider: OAuthProviderSchema.default("discord")
422
+ });
423
+ DeviceCodeResponseSchema = z8.object({
424
+ device_code: z8.string().min(16),
425
+ user_code: z8.string().length(8),
426
+ // shown to the user: e.g. "KORDIS-AB12"
427
+ verification_url: z8.string().url(),
428
+ expires_in: z8.number().int().positive(),
429
+ // seconds
430
+ interval: z8.number().int().positive()
431
+ // polling interval hint
432
+ });
433
+ DeviceTokenRequestSchema = z8.object({
434
+ device_code: z8.string().min(16)
435
+ });
436
+ TokenPairSchema = z8.object({
437
+ access_token: z8.string(),
438
+ refresh_token: z8.string(),
439
+ token_type: z8.literal("Bearer"),
440
+ expires_in: z8.number().int().positive(),
441
+ session_id: UuidSchema
442
+ });
443
+ RefreshRequestSchema = z8.object({
444
+ refresh_token: z8.string()
445
+ });
446
+ SessionInfoSchema = z8.object({
447
+ id: UuidSchema,
448
+ provider: OAuthProviderSchema,
449
+ device_name: z8.string().nullable(),
450
+ ip_hash: z8.string().nullable(),
451
+ user_agent: z8.string().nullable(),
452
+ created_at: IsoDateTimeSchema,
453
+ last_seen_at: IsoDateTimeSchema,
454
+ expires_at: IsoDateTimeSchema,
455
+ is_current: z8.boolean()
456
+ });
457
+ }
458
+ });
459
+
460
+ // ../../packages/contracts/src/api.ts
461
+ import { z as z9 } from "zod";
462
+ var SearchQuerySchema, SearchResponseSchema, TrendingQuerySchema, RisingQuerySchema, PublishPackageRequestSchema, PublishWaitQuerySchema, PublishPackageResponseSchema, PackagePathParamsSchema, ContentQuerySchema, PackageContentResponseSchema, JitReadRequestSchema, JitReadResponseSchema, PullPackageResponseSchema, ListVersionsResponseSchema, VersionDiffQuerySchema, ListCommentsQuerySchema, ListCommentsResponseSchema, UserProfileResponseSchema, UserPackagesQuerySchema;
463
+ var init_api = __esm({
464
+ "../../packages/contracts/src/api.ts"() {
465
+ "use strict";
466
+ init_primitives();
467
+ init_enums();
468
+ init_errors();
469
+ init_package();
470
+ init_pagination();
471
+ init_comment();
472
+ init_user();
473
+ SearchQuerySchema = z9.object({
474
+ q: z9.string().max(200).optional(),
475
+ type: z9.union([HarnessTypeSchema, z9.array(HarnessTypeSchema)]).optional().transform((v) => v === void 0 ? void 0 : Array.isArray(v) ? v : [v]),
476
+ tool: z9.union([ToolSlugSchema, z9.array(ToolSlugSchema)]).optional().transform((v) => v === void 0 ? void 0 : Array.isArray(v) ? v : [v]),
477
+ tier: TierSchema.optional(),
478
+ author: UsernameSchema.optional(),
479
+ tag: TagSchema.optional(),
480
+ sort: SortModeSchema.default("relevance"),
481
+ limit: z9.coerce.number().int().min(1).max(100).default(20),
482
+ cursor: CursorSchema.optional()
483
+ });
484
+ SearchResponseSchema = paginatedResponseSchema(PackageSummarySchema);
485
+ TrendingQuerySchema = z9.object({
486
+ window: TimeWindowSchema.default("24h"),
487
+ type: HarnessTypeSchema.optional(),
488
+ limit: z9.coerce.number().int().min(1).max(100).default(20),
489
+ cursor: CursorSchema.optional()
490
+ });
491
+ RisingQuerySchema = z9.object({
492
+ window: z9.enum(["7d"]).default("7d"),
493
+ type: HarnessTypeSchema.optional(),
494
+ limit: z9.coerce.number().int().min(1).max(100).default(20)
495
+ });
496
+ PublishPackageRequestSchema = z9.object({
497
+ /**
498
+ * Raw source content — YAML frontmatter + markdown body. Server parses and
499
+ * dispatches to the per-type validator in `@kordis/harness-schema`.
500
+ */
501
+ content: z9.string().min(50).max(1e5),
502
+ /**
503
+ * Inline overrides for frontmatter fields. Merged into parsed frontmatter
504
+ * server-side; existing frontmatter wins if both present.
505
+ */
506
+ title: z9.string().min(1).max(200).optional(),
507
+ description: z9.string().min(1).max(500).optional(),
508
+ type: HarnessTypeSchema.optional(),
509
+ slug: SlugSchema.optional(),
510
+ /**
511
+ * When updating, the target full_slug. Omitted for new packages (server
512
+ * derives slug from title or accepts inline `slug`).
513
+ */
514
+ target_full_slug: FullSlugSchema.optional(),
515
+ /**
516
+ * Bump strategy when updating. Ignored for first publish.
517
+ */
518
+ bump: z9.enum(["patch", "minor", "major"]).optional(),
519
+ changelog: z9.string().max(2e3).optional(),
520
+ depends: z9.array(z9.string()).optional(),
521
+ recommends: z9.array(z9.string()).optional(),
522
+ visibility: VisibilitySchema.optional(),
523
+ /**
524
+ * Skip draft validation (preview mode) — only allowed in dev builds.
525
+ */
526
+ dry_run: z9.boolean().optional()
527
+ });
528
+ PublishWaitQuerySchema = z9.object({
529
+ /**
530
+ * Synchronous AI-review wait window. 0 = never wait (always 202 + pending).
531
+ * Default 3000ms matches the fail-open plan.
532
+ */
533
+ wait_for_review: z9.coerce.number().int().min(0).max(15e3).default(3e3)
534
+ });
535
+ PublishPackageResponseSchema = z9.object({
536
+ status: PublishStatusSchema,
537
+ full_slug: FullSlugSchema,
538
+ version: SemverSchema,
539
+ tier: TierSchema,
540
+ type: HarnessTypeSchema,
541
+ url: z9.string().url(),
542
+ review: z9.object({
543
+ result: ReviewStatusSchema,
544
+ score: z9.number().nullable(),
545
+ flags: z9.array(ReviewFlagSchema),
546
+ duration_ms: z9.number().int().nonnegative().nullable(),
547
+ prompt_version: z9.string().nullable()
548
+ }),
549
+ diff: z9.object({
550
+ version_from: SemverSchema.nullable(),
551
+ version_to: SemverSchema
552
+ }).nullable(),
553
+ published_at: z9.string().datetime()
554
+ });
555
+ PackagePathParamsSchema = z9.object({
556
+ username: UsernameSchema,
557
+ slug: SlugSchema
558
+ });
559
+ ContentQuerySchema = z9.object({
560
+ /**
561
+ * Output format. `canonical` = Kordis internal form. Tool-specific formats
562
+ * run through `@kordis/translators`.
563
+ */
564
+ format: z9.enum(["canonical", ...ToolSlugSchema.options]).default("canonical"),
565
+ version: SemverSchema.optional()
566
+ });
567
+ PackageContentResponseSchema = z9.object({
568
+ full_slug: FullSlugSchema,
569
+ version: SemverSchema,
570
+ format: z9.string(),
571
+ content: z9.string(),
572
+ notes: z9.array(z9.string()).default([])
573
+ // translator warnings, e.g. ".mdc → Claude: @filepath unsupported"
574
+ });
575
+ JitReadRequestSchema = z9.object({
576
+ /**
577
+ * When true, packages with review_status='pending_review' are still
578
+ * returned. Default false — JIT trust gate is stricter than install
579
+ * because there's no human-inspection moment in the agent flow.
580
+ */
581
+ allow_pending: z9.boolean().optional().default(false),
582
+ /**
583
+ * Pin to a specific version. Bare slug → latest passed.
584
+ */
585
+ version: SemverSchema.optional()
586
+ });
587
+ JitReadResponseSchema = z9.object({
588
+ full_slug: FullSlugSchema,
589
+ version: SemverSchema,
590
+ /** Raw canonical body. Agent inlines this into its context. */
591
+ body: z9.string(),
592
+ type: HarnessTypeSchema,
593
+ tier: TierSchema,
594
+ /**
595
+ * One-line attribution string the agent SHOULD include in its reply when
596
+ * citing this skill, e.g. "following rayul/hwp-reader@0.3.1". Optional —
597
+ * KORDIS.md recommends but does not enforce.
598
+ */
599
+ attribution: z9.string(),
600
+ read_at: z9.string().datetime({ offset: true })
601
+ });
602
+ PullPackageResponseSchema = z9.object({
603
+ status: z9.enum(["installed", "already_installed", "skipped"]),
604
+ full_slug: FullSlugSchema,
605
+ version: SemverSchema,
606
+ installed_paths: z9.array(z9.string()),
607
+ tool_detected: ToolSlugSchema.nullable(),
608
+ format: z9.string(),
609
+ dependencies: z9.object({
610
+ required: z9.array(
611
+ z9.object({
612
+ full_slug: FullSlugSchema,
613
+ version: SemverSchema,
614
+ status: z9.enum(["installed", "already_installed", "skipped", "failed"]),
615
+ paths: z9.array(z9.string()).optional()
616
+ })
617
+ ),
618
+ recommended: z9.array(
619
+ z9.object({
620
+ full_slug: FullSlugSchema,
621
+ version: SemverSchema,
622
+ status: z9.enum(["installed", "skipped"]),
623
+ paths: z9.array(z9.string()).optional()
624
+ })
625
+ )
626
+ }),
627
+ recommends_hint: z9.string().nullable()
628
+ });
629
+ ListVersionsResponseSchema = z9.object({
630
+ full_slug: FullSlugSchema,
631
+ current_version: SemverSchema,
632
+ versions: z9.array(PackageVersionSummarySchema)
633
+ });
634
+ VersionDiffQuerySchema = z9.object({
635
+ from: SemverSchema,
636
+ to: SemverSchema
637
+ });
638
+ ListCommentsQuerySchema = z9.object({
639
+ sort: z9.enum(["recent", "old"]).default("recent"),
640
+ cursor: CursorSchema.optional(),
641
+ limit: z9.coerce.number().int().min(1).max(50).default(20),
642
+ /** Filter top-level posts by kind. Replies always come along with their root. */
643
+ kind: CommentKindSchema.optional()
644
+ });
645
+ ListCommentsResponseSchema = z9.object({
646
+ results: z9.array(CommentTreeSchema),
647
+ next_cursor: z9.string().nullable(),
648
+ total: z9.number().int().nonnegative()
649
+ });
650
+ UserProfileResponseSchema = PublicUserSchema.extend({
651
+ stats: UserStatsSchema
652
+ });
653
+ UserPackagesQuerySchema = z9.object({
654
+ sort: SortModeSchema.default("stars"),
655
+ type: HarnessTypeSchema.optional(),
656
+ limit: z9.coerce.number().int().min(1).max(100).default(20),
657
+ cursor: CursorSchema.optional()
658
+ });
659
+ }
660
+ });
661
+
662
+ // ../../packages/contracts/src/index.ts
663
+ var init_src = __esm({
664
+ "../../packages/contracts/src/index.ts"() {
665
+ "use strict";
666
+ init_primitives();
667
+ init_enums();
668
+ init_errors();
669
+ init_pagination();
670
+ init_user();
671
+ init_package();
672
+ init_comment();
673
+ init_auth();
674
+ init_api();
675
+ }
676
+ });
677
+
678
+ // ../../packages/api-client/src/errors.ts
679
+ var KordisApiError, KordisNetworkError;
680
+ var init_errors2 = __esm({
681
+ "../../packages/api-client/src/errors.ts"() {
682
+ "use strict";
683
+ init_src();
684
+ KordisApiError = class extends Error {
685
+ code;
686
+ status;
687
+ flags;
688
+ retryAfter;
689
+ requestId;
690
+ constructor(status, envelope, requestId) {
691
+ super(envelope.error.message || `api_error(${envelope.error.code})`);
692
+ this.name = "KordisApiError";
693
+ this.code = envelope.error.code;
694
+ this.status = status;
695
+ this.flags = envelope.error.flags ?? [];
696
+ this.retryAfter = envelope.error.retry_after_seconds;
697
+ this.requestId = envelope.error.request_id ?? requestId;
698
+ }
699
+ exitCode() {
700
+ return ERROR_EXIT_CODES[this.code];
701
+ }
702
+ };
703
+ KordisNetworkError = class extends Error {
704
+ code = "network_unreachable";
705
+ constructor(message, cause) {
706
+ super(message, { cause });
707
+ this.name = "KordisNetworkError";
708
+ }
709
+ exitCode() {
710
+ return ERROR_EXIT_CODES.network_unreachable;
711
+ }
712
+ };
713
+ }
714
+ });
715
+
716
+ // ../../packages/api-client/src/client.ts
717
+ var KordisClient;
718
+ var init_client = __esm({
719
+ "../../packages/api-client/src/client.ts"() {
720
+ "use strict";
721
+ init_errors2();
722
+ KordisClient = class {
723
+ baseUrl;
724
+ fetchImpl;
725
+ config;
726
+ constructor(config) {
727
+ this.config = config;
728
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
729
+ this.fetchImpl = config.fetch ?? globalThis.fetch;
730
+ if (!this.fetchImpl) {
731
+ throw new Error("fetch is not available; pass config.fetch or run on Node 20+");
732
+ }
733
+ }
734
+ async authHeaders() {
735
+ const headers = {};
736
+ const token = await this.config.getAccessToken?.();
737
+ if (token) headers.authorization = `Bearer ${token}`;
738
+ if (this.config.userAgent) headers["user-agent"] = this.config.userAgent;
739
+ return headers;
740
+ }
741
+ async request(path, opts = {}) {
742
+ const url = new URL(this.baseUrl + path);
743
+ if (opts.query) {
744
+ for (const [k, v] of Object.entries(opts.query)) {
745
+ if (v === void 0) continue;
746
+ if (Array.isArray(v)) for (const item of v) url.searchParams.append(k, String(item));
747
+ else url.searchParams.append(k, String(v));
748
+ }
749
+ }
750
+ const headers = {
751
+ accept: "application/json",
752
+ ...await this.authHeaders(),
753
+ ...opts.headers ?? {}
754
+ };
755
+ if (opts.body !== void 0) headers["content-type"] = "application/json";
756
+ let res;
757
+ try {
758
+ const init = {
759
+ method: opts.method ?? "GET",
760
+ headers,
761
+ cache: "no-store"
762
+ };
763
+ if (opts.body !== void 0) init.body = JSON.stringify(opts.body);
764
+ res = await this.fetchImpl(url, init);
765
+ } catch (err) {
766
+ throw new KordisNetworkError(
767
+ `cannot reach ${url.hostname}: ${err.message}`,
768
+ err
769
+ );
770
+ }
771
+ const requestId = res.headers.get("x-request-id") ?? void 0;
772
+ if (res.status === 204) return void 0;
773
+ const text = await res.text();
774
+ const parsed = text ? JSON.parse(text) : void 0;
775
+ if (!res.ok) {
776
+ throw new KordisApiError(res.status, parsed, requestId);
777
+ }
778
+ return parsed;
779
+ }
780
+ // ─── auth ──────────────────────────────────────────────────────────────
781
+ startDeviceFlow(provider = "discord") {
782
+ return this.request("/auth/device/code", {
783
+ method: "POST",
784
+ body: { provider }
785
+ });
786
+ }
787
+ pollDeviceFlow(deviceCode) {
788
+ return this.request("/auth/device/token", {
789
+ method: "POST",
790
+ body: { device_code: deviceCode }
791
+ });
792
+ }
793
+ refreshSession(refreshToken) {
794
+ return this.request("/auth/sessions/refresh", {
795
+ method: "POST",
796
+ body: { refresh_token: refreshToken }
797
+ });
798
+ }
799
+ listSessions() {
800
+ return this.request("/auth/sessions");
801
+ }
802
+ revokeSession(id) {
803
+ return this.request(`/auth/sessions/${id}`, { method: "DELETE" });
804
+ }
805
+ revokeAllSessions() {
806
+ return this.request("/auth/sessions/revoke-all", { method: "POST" });
807
+ }
808
+ // ─── users ─────────────────────────────────────────────────────────────
809
+ me() {
810
+ return this.request("/api/me");
811
+ }
812
+ // ─── packages ──────────────────────────────────────────────────────────
813
+ publish(body, opts = {}) {
814
+ return this.request("/api/packages", {
815
+ method: "POST",
816
+ body,
817
+ query: opts.waitForReviewMs !== void 0 ? { wait_for_review: opts.waitForReviewMs } : void 0
818
+ });
819
+ }
820
+ update(username, slug, body) {
821
+ return this.request(`/api/packages/${username}/${slug}`, {
822
+ method: "PUT",
823
+ body
824
+ });
825
+ }
826
+ getPackage(username, slug) {
827
+ return this.request(`/api/packages/${username}/${slug}`);
828
+ }
829
+ getContent(username, slug, opts = {}) {
830
+ return this.request(
831
+ `/api/packages/${username}/${slug}/content`,
832
+ { query: opts }
833
+ );
834
+ }
835
+ pull(username, slug) {
836
+ return this.request(
837
+ `/api/packages/${username}/${slug}/pull`,
838
+ { method: "POST" }
839
+ );
840
+ }
841
+ /**
842
+ * JIT read — fetch the canonical body into agent context. Plan v1.1.
843
+ * Server enforces type eligibility (text-bearing types only) and trust gate
844
+ * (passed-only unless `allow_pending=true`).
845
+ */
846
+ jitRead(username, slug, body = {}) {
847
+ return this.request(
848
+ `/api/packages/${username}/${slug}/read`,
849
+ { method: "POST", body }
850
+ );
851
+ }
852
+ search(query) {
853
+ return this.request("/api/packages/search", {
854
+ query
855
+ });
856
+ }
857
+ recent() {
858
+ return this.request("/api/packages/recent");
859
+ }
860
+ trending(window = "24h") {
861
+ return this.request("/api/packages/trending", { query: { window } });
862
+ }
863
+ popular() {
864
+ return this.request("/api/packages/popular");
865
+ }
866
+ versions(username, slug) {
867
+ return this.request(
868
+ `/api/packages/${username}/${slug}/versions`
869
+ );
870
+ }
871
+ diff(username, slug, from, to) {
872
+ return this.request(`/api/packages/${username}/${slug}/diff`, {
873
+ query: { from, to }
874
+ });
875
+ }
876
+ // ─── comments ──────────────────────────────────────────────────────────
877
+ listComments(username, slug, query = {}) {
878
+ return this.request(
879
+ `/api/packages/${username}/${slug}/comments`,
880
+ { query }
881
+ );
882
+ }
883
+ createComment(username, slug, body) {
884
+ return this.request(`/api/packages/${username}/${slug}/comments`, {
885
+ method: "POST",
886
+ body
887
+ });
888
+ }
889
+ updateComment(id, body) {
890
+ return this.request(`/api/comments/${id}`, {
891
+ method: "PATCH",
892
+ body
893
+ });
894
+ }
895
+ /**
896
+ * Flip a `report` between `open` and `resolved`. Allowed parties: report
897
+ * author, package owner, moderators. Server returns 422 (`usage`) on a
898
+ * non-report or already-deleted target.
899
+ */
900
+ setCommentStatus(id, body) {
901
+ return this.request(`/api/comments/${id}/status`, {
902
+ method: "PATCH",
903
+ body
904
+ });
905
+ }
906
+ deleteComment(id) {
907
+ return this.request(`/api/comments/${id}`, { method: "DELETE" });
908
+ }
909
+ listReplies(id) {
910
+ return this.request(
911
+ `/api/comments/${id}/replies`
912
+ );
913
+ }
914
+ };
915
+ }
916
+ });
917
+
918
+ // ../../packages/api-client/src/index.ts
919
+ var src_exports = {};
920
+ __export(src_exports, {
921
+ KordisApiError: () => KordisApiError,
922
+ KordisClient: () => KordisClient,
923
+ KordisNetworkError: () => KordisNetworkError
924
+ });
925
+ var init_src2 = __esm({
926
+ "../../packages/api-client/src/index.ts"() {
927
+ "use strict";
928
+ init_client();
929
+ init_errors2();
930
+ }
931
+ });
932
+
933
+ // src/index.ts
934
+ init_src2();
935
+ import { Command as Command10 } from "commander";
936
+
937
+ // src/commands/login.ts
938
+ import { Command } from "commander";
939
+
940
+ // src/client.ts
941
+ init_src2();
942
+
943
+ // src/config.ts
944
+ import { homedir } from "os";
945
+ import { join } from "path";
946
+ import { mkdir, readFile, writeFile } from "fs/promises";
947
+ function configDir() {
948
+ return process.env["KORDIS_HOME"] ?? join(homedir(), ".kordis");
949
+ }
950
+ function configPath() {
951
+ return join(configDir(), "config.json");
952
+ }
953
+ async function loadCliConfig() {
954
+ const apiUrlFromEnv = process.env["KORDIS_API_URL"];
955
+ try {
956
+ const raw = await readFile(configPath(), "utf8");
957
+ const parsed = JSON.parse(raw);
958
+ return {
959
+ apiUrl: apiUrlFromEnv ?? parsed.apiUrl ?? "https://api.kordis.dev",
960
+ ...parsed.username ? { username: parsed.username } : {},
961
+ ...parsed.sessionId ? { sessionId: parsed.sessionId } : {}
962
+ };
963
+ } catch {
964
+ return { apiUrl: apiUrlFromEnv ?? "https://api.kordis.dev" };
965
+ }
966
+ }
967
+ async function saveCliConfig(cfg) {
968
+ await mkdir(configDir(), { recursive: true });
969
+ await writeFile(configPath(), JSON.stringify(cfg, null, 2) + "\n", "utf8");
970
+ }
971
+
972
+ // src/keychain.ts
973
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, unlink } from "fs/promises";
974
+ import { homedir as homedir2 } from "os";
975
+ import { join as join2 } from "path";
976
+ var SERVICE = "kordis";
977
+ async function loadKeytar() {
978
+ try {
979
+ const mod = await import("keytar");
980
+ return mod.default ?? mod;
981
+ } catch {
982
+ return null;
983
+ }
984
+ }
985
+ function fallbackPath() {
986
+ return join2(process.env["KORDIS_HOME"] ?? join2(homedir2(), ".kordis"), ".tokens.json");
987
+ }
988
+ async function createKeychain() {
989
+ const keytar = await loadKeytar();
990
+ if (keytar) {
991
+ return {
992
+ async save(username, tokens) {
993
+ await keytar.setPassword(SERVICE, username, JSON.stringify(tokens));
994
+ },
995
+ async load(username) {
996
+ const str = await keytar.getPassword(SERVICE, username);
997
+ return str ? JSON.parse(str) : null;
998
+ },
999
+ async remove(username) {
1000
+ await keytar.deletePassword(SERVICE, username);
1001
+ }
1002
+ };
1003
+ }
1004
+ const store = await readAll();
1005
+ return {
1006
+ async save(username, tokens) {
1007
+ store[username] = tokens;
1008
+ await mkdir2(join2(fallbackPath(), ".."), { recursive: true });
1009
+ await writeFile2(fallbackPath(), JSON.stringify(store, null, 2), { mode: 384 });
1010
+ },
1011
+ async load(username) {
1012
+ return store[username] ?? null;
1013
+ },
1014
+ async remove(username) {
1015
+ delete store[username];
1016
+ if (Object.keys(store).length === 0) {
1017
+ await unlink(fallbackPath()).catch(() => {
1018
+ });
1019
+ } else {
1020
+ await writeFile2(fallbackPath(), JSON.stringify(store, null, 2), { mode: 384 });
1021
+ }
1022
+ }
1023
+ };
1024
+ }
1025
+ async function readAll() {
1026
+ try {
1027
+ const raw = await readFile2(fallbackPath(), "utf8");
1028
+ return JSON.parse(raw);
1029
+ } catch {
1030
+ return {};
1031
+ }
1032
+ }
1033
+
1034
+ // src/client.ts
1035
+ async function useClient() {
1036
+ const cfg = await loadCliConfig();
1037
+ const keychain = await createKeychain();
1038
+ const username = cfg.username ?? null;
1039
+ let currentTokens = username ? await keychain.load(username) : null;
1040
+ const api = new KordisClient({
1041
+ baseUrl: cfg.apiUrl,
1042
+ userAgent: `kordis-cli/0.0.1 node/${process.version}`,
1043
+ getAccessToken: async () => currentTokens?.access_token
1044
+ });
1045
+ const authedRequest = async (fn) => {
1046
+ try {
1047
+ return await fn(api);
1048
+ } catch (err) {
1049
+ if (err instanceof KordisApiError && err.code === "not_authenticated" && currentTokens && username) {
1050
+ try {
1051
+ const refreshed = await api.refreshSession(currentTokens.refresh_token);
1052
+ currentTokens = {
1053
+ access_token: refreshed.access_token,
1054
+ refresh_token: refreshed.refresh_token,
1055
+ session_id: refreshed.session_id,
1056
+ expires_at_epoch: Math.floor(Date.now() / 1e3) + refreshed.expires_in
1057
+ };
1058
+ await keychain.save(username, currentTokens);
1059
+ await saveCliConfig({ ...cfg, sessionId: refreshed.session_id });
1060
+ return await fn(api);
1061
+ } catch {
1062
+ throw err;
1063
+ }
1064
+ }
1065
+ throw err;
1066
+ }
1067
+ };
1068
+ return { api, keychain, username, apiUrl: cfg.apiUrl, authedRequest };
1069
+ }
1070
+
1071
+ // src/ui.ts
1072
+ import kleur from "kleur";
1073
+ import ora from "ora";
1074
+ var jsonMode = false;
1075
+ function setJsonMode(on) {
1076
+ jsonMode = on;
1077
+ }
1078
+ function isJsonMode() {
1079
+ return jsonMode;
1080
+ }
1081
+ function printHuman(text) {
1082
+ if (!jsonMode) process.stdout.write(text + "\n");
1083
+ }
1084
+ function printJson(obj) {
1085
+ if (jsonMode) process.stdout.write(JSON.stringify(obj) + "\n");
1086
+ }
1087
+ function printDual(args) {
1088
+ if (jsonMode) printJson(args.json);
1089
+ else args.human();
1090
+ }
1091
+ function spinner(text) {
1092
+ if (jsonMode) {
1093
+ return {
1094
+ succeed: () => {
1095
+ },
1096
+ fail: () => {
1097
+ },
1098
+ stop: () => {
1099
+ }
1100
+ };
1101
+ }
1102
+ return ora({ text, color: "cyan" }).start();
1103
+ }
1104
+ var c = kleur;
1105
+
1106
+ // src/commands/login.ts
1107
+ function loginCommand() {
1108
+ return new Command("login").description("authenticate with the Kordis registry").option("--provider <name>", "OAuth provider", "discord").action(async (opts) => {
1109
+ const bundle = await useClient();
1110
+ const started = spinner("Requesting device code");
1111
+ const device = await bundle.api.startDeviceFlow(
1112
+ opts.provider ?? "discord"
1113
+ );
1114
+ started.succeed();
1115
+ printHuman("");
1116
+ printHuman(c.bold("To continue, open this URL in a browser:"));
1117
+ printHuman(" " + c.cyan(device.verification_url));
1118
+ printHuman(c.bold("And confirm the code:"));
1119
+ printHuman(" " + c.yellow(device.user_code));
1120
+ printHuman("");
1121
+ const poll = spinner("Waiting for browser confirmation");
1122
+ const start = Date.now();
1123
+ const deadline = start + device.expires_in * 1e3;
1124
+ while (Date.now() < deadline) {
1125
+ try {
1126
+ const tokens = await bundle.api.pollDeviceFlow(device.device_code);
1127
+ poll.succeed();
1128
+ await bundle.keychain.save(
1129
+ // username set below after /me call
1130
+ "pending",
1131
+ {
1132
+ access_token: tokens.access_token,
1133
+ refresh_token: tokens.refresh_token,
1134
+ session_id: tokens.session_id,
1135
+ expires_at_epoch: Math.floor(Date.now() / 1e3) + tokens.expires_in
1136
+ }
1137
+ );
1138
+ const me = await new (await Promise.resolve().then(() => (init_src2(), src_exports))).KordisClient({
1139
+ baseUrl: bundle.apiUrl,
1140
+ getAccessToken: () => tokens.access_token
1141
+ }).me();
1142
+ await bundle.keychain.remove("pending");
1143
+ await bundle.keychain.save(me.username, {
1144
+ access_token: tokens.access_token,
1145
+ refresh_token: tokens.refresh_token,
1146
+ session_id: tokens.session_id,
1147
+ expires_at_epoch: Math.floor(Date.now() / 1e3) + tokens.expires_in
1148
+ });
1149
+ const cfg = await loadCliConfig();
1150
+ await saveCliConfig({ ...cfg, username: me.username, sessionId: tokens.session_id });
1151
+ printDual({
1152
+ json: { status: "logged_in", username: me.username, session_id: tokens.session_id },
1153
+ human: () => printHuman(c.green(`
1154
+ \u2713 Logged in as ${c.bold(me.username)}.`))
1155
+ });
1156
+ return;
1157
+ } catch (err) {
1158
+ const msg = err.code;
1159
+ if (msg && msg !== "authorization_pending") throw err;
1160
+ await new Promise((r) => setTimeout(r, (device.interval || 5) * 1e3));
1161
+ }
1162
+ }
1163
+ poll.fail("Timed out");
1164
+ throw new Error("device code expired; run `kordis login` again");
1165
+ });
1166
+ }
1167
+
1168
+ // src/commands/whoami.ts
1169
+ import { Command as Command2 } from "commander";
1170
+ function whoamiCommand() {
1171
+ return new Command2("whoami").description("show the currently authenticated user").action(async () => {
1172
+ const bundle = await useClient();
1173
+ if (!bundle.username) {
1174
+ printHuman(c.yellow("Not logged in. Run `kordis login`."));
1175
+ process.exitCode = 10;
1176
+ return;
1177
+ }
1178
+ const me = await bundle.authedRequest((api) => api.me());
1179
+ printDual({
1180
+ json: me,
1181
+ human: () => {
1182
+ printHuman(c.bold(me.username));
1183
+ if (me.display_name) printHuman(` ${me.display_name}`);
1184
+ printHuman(` ${me.package_count} packages \xB7 ${me.follower_count} followers`);
1185
+ }
1186
+ });
1187
+ });
1188
+ }
1189
+
1190
+ // src/commands/search.ts
1191
+ import { Command as Command3 } from "commander";
1192
+ function searchCommand() {
1193
+ return new Command3("search").description("search the Kordis registry").argument("[query...]", "keywords to match against title/description").option("--type <type>", "filter by harness type (skill|mcp-server|project-rules|custom-agent|hook)").option("--tool <tool>", "filter by compatible tool (claude-code|codex|cursor|...)").option("--limit <n>", "max results", (v) => parseInt(v, 10), 20).action(async (query, opts) => {
1194
+ const bundle = await useClient();
1195
+ const res = await bundle.api.search({
1196
+ q: query.join(" ") || void 0,
1197
+ type: opts.type ? [opts.type] : void 0,
1198
+ tool: opts.tool ? [opts.tool] : void 0,
1199
+ limit: opts.limit
1200
+ });
1201
+ printDual({
1202
+ json: res,
1203
+ human: () => {
1204
+ if (res.results.length === 0) {
1205
+ printHuman(c.yellow("(no results)"));
1206
+ return;
1207
+ }
1208
+ for (const p of res.results) {
1209
+ printHuman(
1210
+ `${c.bold(p.full_slug)} ${c.gray(`v${p.current_version}`)} ${c.cyan(`[${p.type}]`)}`
1211
+ );
1212
+ printHuman(` ${p.description}`);
1213
+ printHuman(
1214
+ c.gray(` \u2605 ${p.stars} \u21E3 ${p.downloads} ${p.author.username}${p.author.is_verified ? " \u2713" : ""}`)
1215
+ );
1216
+ printHuman("");
1217
+ }
1218
+ }
1219
+ });
1220
+ });
1221
+ }
1222
+
1223
+ // src/commands/push.ts
1224
+ import { Command as Command4 } from "commander";
1225
+ import { readFile as readFile3 } from "fs/promises";
1226
+ import { resolve } from "path";
1227
+ function pushCommand() {
1228
+ return new Command4("push").description("publish a new package or a new version").argument("<file>", "path to the canonical package source").option("--bump <strategy>", "patch|minor|major (update only)", "patch").option("--slug <slug>", "override slug (for first publish)").option("--type <type>", "override type inference").option("--changelog <text>", "optional changelog entry").option("--dry-run", "validate only; do not publish").option("--no-wait", "return immediately without waiting for review").action(
1229
+ async (file, opts) => {
1230
+ const bundle = await useClient();
1231
+ const abs = resolve(process.cwd(), file);
1232
+ const content = await readFile3(abs, "utf8");
1233
+ const spin = spinner(opts.dryRun ? "Validating" : "Publishing");
1234
+ const res = await bundle.authedRequest(
1235
+ (api) => api.publish(
1236
+ {
1237
+ content,
1238
+ bump: opts.bump,
1239
+ slug: opts.slug,
1240
+ type: opts.type,
1241
+ changelog: opts.changelog,
1242
+ dry_run: !!opts.dryRun
1243
+ },
1244
+ {
1245
+ waitForReviewMs: opts.wait === false ? 0 : 3e3
1246
+ }
1247
+ )
1248
+ );
1249
+ spin.succeed();
1250
+ printDual({
1251
+ json: res,
1252
+ human: () => {
1253
+ printHuman(
1254
+ `${c.bold(res.full_slug)} ${c.gray(`v${res.version}`)} \u2192 ${res.status === "published" ? c.green("published") : res.status === "pending" ? c.yellow("pending review") : c.red("rejected")}`
1255
+ );
1256
+ printHuman(` ${c.cyan(res.url)}`);
1257
+ if (res.review.flags.length > 0) {
1258
+ printHuman(c.yellow(` ${res.review.flags.length} review flag(s):`));
1259
+ for (const f of res.review.flags) {
1260
+ printHuman(c.yellow(` - ${f.code}${f.message ? `: ${f.message}` : ""}`));
1261
+ }
1262
+ }
1263
+ }
1264
+ });
1265
+ }
1266
+ );
1267
+ }
1268
+
1269
+ // src/commands/pull.ts
1270
+ import { Command as Command5 } from "commander";
1271
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
1272
+ import { dirname, join as join3 } from "path";
1273
+
1274
+ // ../../packages/harness-schema/src/parser.ts
1275
+ import { parse as parseYaml } from "yaml";
1276
+ var FRONTMATTER_FENCE_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
1277
+ function parseFrontmatter(source) {
1278
+ const stripped = source.replace(/^\uFEFF/, "");
1279
+ const match = FRONTMATTER_FENCE_RE.exec(stripped);
1280
+ if (!match) {
1281
+ return { frontmatter: null, body: stripped, bodyOffset: 0 };
1282
+ }
1283
+ const raw = match[1] ?? "";
1284
+ let parsed;
1285
+ try {
1286
+ parsed = parseYaml(raw);
1287
+ } catch (err) {
1288
+ throw new FrontmatterParseError(
1289
+ `YAML frontmatter is malformed: ${err.message}`,
1290
+ { cause: err }
1291
+ );
1292
+ }
1293
+ if (parsed === null || parsed === void 0) {
1294
+ return { frontmatter: {}, body: stripped.slice(match[0].length), bodyOffset: match[0].length };
1295
+ }
1296
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
1297
+ throw new FrontmatterParseError(
1298
+ "YAML frontmatter must decode to an object, got " + typeof parsed
1299
+ );
1300
+ }
1301
+ return {
1302
+ frontmatter: parsed,
1303
+ body: stripped.slice(match[0].length),
1304
+ bodyOffset: match[0].length
1305
+ };
1306
+ }
1307
+ var FrontmatterParseError = class extends Error {
1308
+ constructor(message, options) {
1309
+ super(message, options);
1310
+ this.name = "FrontmatterParseError";
1311
+ }
1312
+ };
1313
+
1314
+ // ../../packages/harness-schema/src/deps.ts
1315
+ init_src();
1316
+ import { z as z10 } from "zod";
1317
+ var StringDepRe = /^([a-z0-9](-?[a-z0-9])*\/[a-z0-9](-?[a-z0-9])*)(?:@(.+))?$/;
1318
+ var ObjectDepSchema = z10.object({
1319
+ full_slug: FullSlugSchema,
1320
+ version_range: SemverRangeSchema.default("*")
1321
+ });
1322
+ function normaliseDepList(list) {
1323
+ if (list === void 0 || list === null) return [];
1324
+ if (!Array.isArray(list)) {
1325
+ throw new Error("depends/recommends must be an array");
1326
+ }
1327
+ return list.map((entry, idx) => normaliseDep(entry, idx));
1328
+ }
1329
+ function normaliseDep(entry, idx) {
1330
+ if (typeof entry === "string") {
1331
+ const m = StringDepRe.exec(entry);
1332
+ if (!m) {
1333
+ throw new Error(
1334
+ `entry #${idx + 1} "${entry}" must be "{username}/{slug}" optionally followed by "@<range>"`
1335
+ );
1336
+ }
1337
+ return {
1338
+ full_slug: m[1],
1339
+ version_range: (m[3] ?? "*").trim()
1340
+ };
1341
+ }
1342
+ if (typeof entry === "object" && entry !== null) {
1343
+ const parsed = ObjectDepSchema.parse(entry);
1344
+ return parsed;
1345
+ }
1346
+ throw new Error(`entry #${idx + 1} must be a string or object, got ${typeof entry}`);
1347
+ }
1348
+
1349
+ // ../../packages/harness-schema/src/validator.ts
1350
+ init_src();
1351
+
1352
+ // ../../packages/harness-schema/src/types/skill.ts
1353
+ init_src();
1354
+ import { z as z11 } from "zod";
1355
+ var SkillFrontmatterSchema = z11.object({
1356
+ name: z11.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "name must be lowercase-with-hyphens (Agent Skills spec)"),
1357
+ description: z11.string().min(1).max(500),
1358
+ version: SemverSchema.optional(),
1359
+ // Agent Skills optional metadata
1360
+ license: z11.string().max(64).optional(),
1361
+ homepage: z11.string().url().optional(),
1362
+ model: z11.string().optional(),
1363
+ // model override hint
1364
+ "allowed-tools": z11.array(z11.string()).optional(),
1365
+ "disable-model-invocation": z11.boolean().optional(),
1366
+ // Kordis-forwarded cross-type fields (kept minimal to not pollute the spec)
1367
+ title: z11.string().min(1).max(200).optional(),
1368
+ tags: z11.array(TagSchema).max(16).optional(),
1369
+ tools: z11.array(ToolSlugSchema).optional()
1370
+ }).strict();
1371
+
1372
+ // ../../packages/harness-schema/src/types/mcp-server.ts
1373
+ init_src();
1374
+ import { z as z12 } from "zod";
1375
+ var McpTransportSchema = z12.union([
1376
+ z12.object({
1377
+ type: z12.literal("stdio"),
1378
+ command: z12.string().min(1),
1379
+ args: z12.array(z12.string()).optional().default([]),
1380
+ env: z12.record(z12.string()).optional().default({})
1381
+ }),
1382
+ z12.object({
1383
+ type: z12.enum(["http", "sse"]),
1384
+ url: z12.string().url(),
1385
+ headers: z12.record(z12.string()).optional().default({})
1386
+ })
1387
+ ]);
1388
+ var McpCapabilitiesSchema = z12.object({
1389
+ tools: z12.array(z12.string()).optional().default([]),
1390
+ resources: z12.array(z12.string()).optional().default([]),
1391
+ prompts: z12.array(z12.string()).optional().default([])
1392
+ });
1393
+ var McpServerFrontmatterSchema = z12.object({
1394
+ name: z12.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
1395
+ description: z12.string().min(1).max(500),
1396
+ version: SemverSchema,
1397
+ mcp_spec_version: z12.string().default("2026-03"),
1398
+ transport: McpTransportSchema,
1399
+ capabilities: McpCapabilitiesSchema.optional().default({
1400
+ tools: [],
1401
+ resources: [],
1402
+ prompts: []
1403
+ }),
1404
+ /** Human-readable setup / auth instructions. Surfaced in `kordis info`. */
1405
+ auth_notes: z12.string().optional(),
1406
+ /** Required env vars with description + whether they must be secret. */
1407
+ env_schema: z12.record(
1408
+ z12.object({
1409
+ description: z12.string(),
1410
+ required: z12.boolean().default(true),
1411
+ secret: z12.boolean().default(false)
1412
+ })
1413
+ ).optional(),
1414
+ homepage: z12.string().url().optional(),
1415
+ repository: z12.string().url().optional(),
1416
+ license: z12.string().max(64).optional(),
1417
+ title: z12.string().min(1).max(200).optional()
1418
+ }).strict();
1419
+
1420
+ // ../../packages/harness-schema/src/types/project-rules.ts
1421
+ init_src();
1422
+ import { z as z13 } from "zod";
1423
+ var ProjectRulesFrontmatterSchema = z13.object({
1424
+ title: z13.string().min(1).max(200),
1425
+ description: z13.string().min(1).max(500),
1426
+ version: SemverSchema.optional(),
1427
+ scope: z13.enum(["root", "nested", "globs"]).default("root"),
1428
+ target_path: z13.string().optional(),
1429
+ globs: z13.array(z13.string()).optional(),
1430
+ target_tools: z13.array(ToolSlugSchema).optional().default([]),
1431
+ /** When true, installation is idempotent via `<!-- kordis:... -->` markers. */
1432
+ mergeable: z13.boolean().default(true),
1433
+ license: z13.string().max(64).optional()
1434
+ }).strict().refine(
1435
+ (v) => v.scope !== "nested" || !!v.target_path,
1436
+ { message: 'scope="nested" requires target_path' }
1437
+ ).refine(
1438
+ (v) => v.scope !== "globs" || v.globs && v.globs.length > 0,
1439
+ { message: 'scope="globs" requires a non-empty globs list' }
1440
+ );
1441
+
1442
+ // ../../packages/harness-schema/src/types/custom-agent.ts
1443
+ init_src();
1444
+ import { z as z14 } from "zod";
1445
+ var CustomAgentFrontmatterSchema = z14.object({
1446
+ name: z14.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
1447
+ description: z14.string().min(1).max(500),
1448
+ title: z14.string().min(1).max(200).optional(),
1449
+ version: SemverSchema.optional(),
1450
+ /** System prompt that defines the agent's persona + rules. Required. */
1451
+ system_prompt: z14.string().min(1).max(5e4),
1452
+ /** Optional model override — string passed verbatim to the host. */
1453
+ model: z14.string().optional(),
1454
+ /** Tools this agent can call. Host-specific names (no uniform namespace). */
1455
+ allowed_tools: z14.array(z14.string()).optional().default([]),
1456
+ disallowed_tools: z14.array(z14.string()).optional().default([]),
1457
+ /** Fork/context behaviour hint (Claude: `context: fork` etc.). */
1458
+ context: z14.enum(["inherit", "fork", "isolated"]).optional(),
1459
+ /** Known-good tool runtimes. Empty = untested everywhere. */
1460
+ tool_compat: z14.array(ToolSlugSchema).optional().default([]),
1461
+ /** Pre-load skills when this agent activates. */
1462
+ preload_skills: z14.array(z14.string()).optional().default([]),
1463
+ license: z14.string().max(64).optional(),
1464
+ homepage: z14.string().url().optional()
1465
+ }).strict();
1466
+
1467
+ // ../../packages/harness-schema/src/types/hook.ts
1468
+ init_src();
1469
+ import { z as z15 } from "zod";
1470
+ var HookEventSchema = z15.enum([
1471
+ "PreToolUse",
1472
+ "PostToolUse",
1473
+ "UserPromptSubmit",
1474
+ "PreCompact",
1475
+ "Stop",
1476
+ "SessionStart",
1477
+ "SessionEnd",
1478
+ "SubagentStart",
1479
+ "SubagentStop",
1480
+ "Background"
1481
+ ]);
1482
+ var HookActionSchema = z15.union([
1483
+ z15.object({
1484
+ type: z15.literal("command"),
1485
+ command: z15.string().min(1),
1486
+ timeout_ms: z15.number().int().positive().max(6e5).optional(),
1487
+ blocking: z15.boolean().default(false)
1488
+ }),
1489
+ z15.object({
1490
+ type: z15.literal("http"),
1491
+ url: z15.string().url(),
1492
+ method: z15.enum(["GET", "POST", "PUT", "PATCH"]).default("POST"),
1493
+ headers: z15.record(z15.string()).optional().default({}),
1494
+ timeout_ms: z15.number().int().positive().max(6e5).optional(),
1495
+ blocking: z15.boolean().default(false)
1496
+ })
1497
+ ]);
1498
+ var HookBindingSchema = z15.object({
1499
+ event: HookEventSchema,
1500
+ matcher: z15.string().optional(),
1501
+ action: HookActionSchema
1502
+ });
1503
+ var HookFrontmatterSchema = z15.object({
1504
+ name: z15.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
1505
+ description: z15.string().min(1).max(500),
1506
+ title: z15.string().min(1).max(200).optional(),
1507
+ version: SemverSchema.optional(),
1508
+ /** One or more hook bindings. */
1509
+ hooks: z15.array(HookBindingSchema).min(1),
1510
+ /** Declared to surface in search filters — always "claude-code" for now. */
1511
+ tool_compat: z15.array(z15.enum(["claude-code"])).default(["claude-code"]),
1512
+ license: z15.string().max(64).optional()
1513
+ }).strict();
1514
+
1515
+ // ../../packages/harness-schema/src/validator.ts
1516
+ function flag(code, message, extra = {}) {
1517
+ return { code, message, ...extra };
1518
+ }
1519
+ function validatePackageSource(source) {
1520
+ const flags = [];
1521
+ let body = source;
1522
+ let rawFrontmatter = null;
1523
+ try {
1524
+ const parsed = parseFrontmatter(source);
1525
+ body = parsed.body;
1526
+ rawFrontmatter = parsed.frontmatter;
1527
+ } catch (err) {
1528
+ if (err instanceof FrontmatterParseError) {
1529
+ return earlyExit(flag("malformed_frontmatter", err.message));
1530
+ }
1531
+ throw err;
1532
+ }
1533
+ if (!rawFrontmatter) {
1534
+ return earlyExit(flag("missing_frontmatter", "package must start with a YAML frontmatter block"));
1535
+ }
1536
+ const typeValue = rawFrontmatter["type"];
1537
+ if (typeof typeValue !== "string") {
1538
+ return earlyExit(
1539
+ flag("missing_required_frontmatter", "`type` is required in frontmatter", { field: "type" })
1540
+ );
1541
+ }
1542
+ const typeParse = HarnessTypeSchema.safeParse(typeValue);
1543
+ if (!typeParse.success) {
1544
+ return earlyExit(
1545
+ flag(
1546
+ "unknown_type",
1547
+ `type "${typeValue}" is not a recognised Harness type`,
1548
+ { field: "type" }
1549
+ )
1550
+ );
1551
+ }
1552
+ const type = typeParse.data;
1553
+ if (!PHASE_1_TYPES.includes(type)) {
1554
+ flags.push(
1555
+ flag(
1556
+ "type_not_phase1",
1557
+ `type "${type}" is planned for Phase 2+ and is not yet accepted`,
1558
+ { field: "type" }
1559
+ )
1560
+ );
1561
+ return earlyExit(null, { flags, body });
1562
+ }
1563
+ const {
1564
+ type: _discard_type,
1565
+ depends: _discard_depends,
1566
+ recommends: _discard_recommends,
1567
+ ...typeSpecific
1568
+ } = rawFrontmatter;
1569
+ void _discard_type;
1570
+ void _discard_depends;
1571
+ void _discard_recommends;
1572
+ const result = dispatchValidation(type, typeSpecific);
1573
+ if (!result.ok) {
1574
+ flags.push(...result.flags);
1575
+ return earlyExit(null, { flags, body });
1576
+ }
1577
+ const common = {
1578
+ title: typeof rawFrontmatter["title"] === "string" ? rawFrontmatter["title"] : typeof rawFrontmatter["name"] === "string" ? rawFrontmatter["name"] : null,
1579
+ description: typeof rawFrontmatter["description"] === "string" ? rawFrontmatter["description"] : null
1580
+ };
1581
+ if (!common.title) flags.push(flag("missing_title", "title (or name) is required"));
1582
+ if (!common.description) flags.push(flag("missing_description", "description is required"));
1583
+ let depends = [];
1584
+ let recommends = [];
1585
+ try {
1586
+ depends = normaliseDepList(rawFrontmatter["depends"]).map((d) => ({
1587
+ full_slug: d.full_slug,
1588
+ version_range: d.version_range,
1589
+ kind: DependencyKindSchema.parse("required")
1590
+ }));
1591
+ } catch (err) {
1592
+ flags.push(flag("invalid_depends", err.message, { field: "depends" }));
1593
+ }
1594
+ try {
1595
+ recommends = normaliseDepList(rawFrontmatter["recommends"]).map((d) => ({
1596
+ full_slug: d.full_slug,
1597
+ version_range: d.version_range,
1598
+ kind: DependencyKindSchema.parse("recommended")
1599
+ }));
1600
+ } catch (err) {
1601
+ flags.push(flag("invalid_recommends", err.message, { field: "recommends" }));
1602
+ }
1603
+ if (body.trim().length < 50) {
1604
+ flags.push(flag("content_too_short", "body must be at least 50 non-whitespace characters"));
1605
+ }
1606
+ if (body.length > 1e5) {
1607
+ flags.push(flag("content_too_long", "body exceeds 100 kB cap"));
1608
+ }
1609
+ const ok = flags.every((f) => !BLOCKING_CODES.has(f.code));
1610
+ return {
1611
+ ok,
1612
+ type,
1613
+ tier: TIER_OF_TYPE[type],
1614
+ frontmatter: result.frontmatter,
1615
+ body,
1616
+ common,
1617
+ depends,
1618
+ recommends,
1619
+ flags
1620
+ };
1621
+ }
1622
+ var BLOCKING_CODES = /* @__PURE__ */ new Set([
1623
+ "missing_frontmatter",
1624
+ "malformed_frontmatter",
1625
+ "missing_required_frontmatter",
1626
+ "unknown_type",
1627
+ "type_not_phase1",
1628
+ "missing_title",
1629
+ "missing_description",
1630
+ "invalid_frontmatter",
1631
+ "content_too_short",
1632
+ "content_too_long"
1633
+ ]);
1634
+ function earlyExit(firstFlag, override = {}) {
1635
+ return {
1636
+ ok: false,
1637
+ type: null,
1638
+ tier: null,
1639
+ frontmatter: null,
1640
+ body: "",
1641
+ common: { title: null, description: null },
1642
+ depends: [],
1643
+ recommends: [],
1644
+ flags: firstFlag ? [firstFlag] : [],
1645
+ ...override
1646
+ };
1647
+ }
1648
+ function dispatchValidation(type, raw) {
1649
+ switch (type) {
1650
+ case "skill": {
1651
+ const r = SkillFrontmatterSchema.safeParse(raw);
1652
+ if (!r.success) return zodFail(r.error.issues);
1653
+ return { ok: true, frontmatter: { type, data: r.data }, flags: [] };
1654
+ }
1655
+ case "mcp-server": {
1656
+ const r = McpServerFrontmatterSchema.safeParse(raw);
1657
+ if (!r.success) return zodFail(r.error.issues);
1658
+ return { ok: true, frontmatter: { type, data: r.data }, flags: [] };
1659
+ }
1660
+ case "project-rules": {
1661
+ const r = ProjectRulesFrontmatterSchema.safeParse(raw);
1662
+ if (!r.success) return zodFail(r.error.issues);
1663
+ return { ok: true, frontmatter: { type, data: r.data }, flags: [] };
1664
+ }
1665
+ case "custom-agent": {
1666
+ const r = CustomAgentFrontmatterSchema.safeParse(raw);
1667
+ if (!r.success) return zodFail(r.error.issues);
1668
+ return { ok: true, frontmatter: { type, data: r.data }, flags: [] };
1669
+ }
1670
+ case "hook": {
1671
+ const r = HookFrontmatterSchema.safeParse(raw);
1672
+ if (!r.success) return zodFail(r.error.issues);
1673
+ return { ok: true, frontmatter: { type, data: r.data }, flags: [] };
1674
+ }
1675
+ default:
1676
+ return {
1677
+ ok: false,
1678
+ frontmatter: null,
1679
+ flags: [
1680
+ flag("type_not_phase1", `type "${type}" not accepted in Phase 1`, { field: "type" })
1681
+ ]
1682
+ };
1683
+ }
1684
+ }
1685
+ function zodFail(issues) {
1686
+ return {
1687
+ ok: false,
1688
+ frontmatter: null,
1689
+ flags: issues.map(
1690
+ (i) => flag("invalid_frontmatter", i.message, { field: i.path.join(".") || void 0 })
1691
+ )
1692
+ };
1693
+ }
1694
+
1695
+ // ../../packages/translators/src/skill/identity.ts
1696
+ function buildSkillFile(input, outputPath) {
1697
+ const body = input.body.trimStart();
1698
+ const fm = input.frontmatter;
1699
+ const name = typeof fm["name"] === "string" ? fm["name"] : input.fullSlug.split("/")[1];
1700
+ const frontmatter = ["---"];
1701
+ for (const [k, v] of Object.entries(fm)) {
1702
+ if (v === void 0 || v === null) continue;
1703
+ if (Array.isArray(v)) {
1704
+ if (v.length === 0) continue;
1705
+ frontmatter.push(`${k}:`);
1706
+ for (const item of v) frontmatter.push(` - ${JSON.stringify(item)}`);
1707
+ } else if (typeof v === "object") {
1708
+ frontmatter.push(`${k}: ${JSON.stringify(v)}`);
1709
+ } else {
1710
+ frontmatter.push(`${k}: ${JSON.stringify(v)}`);
1711
+ }
1712
+ }
1713
+ frontmatter.push("---");
1714
+ const content = frontmatter.join("\n") + "\n\n" + body;
1715
+ return {
1716
+ files: [
1717
+ {
1718
+ path: outputPath.replace("{name}", String(name)),
1719
+ content,
1720
+ mergeable: false,
1721
+ format: "markdown"
1722
+ }
1723
+ ],
1724
+ notes: [],
1725
+ format: "agent-skills"
1726
+ };
1727
+ }
1728
+ var skillToClaude = (input) => buildSkillFile(input, ".claude/skills/{name}/SKILL.md");
1729
+ var skillToCodex = (input) => buildSkillFile(input, ".codex/skills/{name}.md");
1730
+ var skillToCursor = (input) => buildSkillFile(input, ".cursor/skills/{name}.md");
1731
+ var skillToWindsurf = (input) => buildSkillFile(input, ".windsurf/skills/{name}.md");
1732
+ var skillToContinue = (input) => buildSkillFile(input, ".continue/skills/{name}.md");
1733
+ var skillToCopilot = (input) => buildSkillFile(input, ".github/skills/{name}.md");
1734
+ var skillToGeminiCli = (input) => buildSkillFile(input, ".gemini/skills/{name}.md");
1735
+ var skillToOpenCode = (input) => buildSkillFile(input, ".opencode/skills/{name}.md");
1736
+ var skillToAntigravity = (input) => buildSkillFile(input, ".antigravity/skills/{name}.md");
1737
+ var skillCanonical = (input) => {
1738
+ const res = buildSkillFile(input, "SKILL.md");
1739
+ res.format = "agent-skills-canonical";
1740
+ return res;
1741
+ };
1742
+
1743
+ // ../../packages/translators/src/project-rules/all.ts
1744
+ function fence(input) {
1745
+ const body = input.body.trimEnd();
1746
+ return `<!-- kordis:${input.fullSlug}:${input.version}:start -->
1747
+ ` + body + `
1748
+ <!-- kordis:${input.fullSlug}:end -->
1749
+ `;
1750
+ }
1751
+ var TOOL_FILE_SPEC = {
1752
+ claude: { path: "CLAUDE.md", format: "markdown" },
1753
+ codex: { path: "AGENTS.md", format: "markdown" },
1754
+ gemini: { path: "GEMINI.md", format: "markdown" },
1755
+ cursor: {
1756
+ path: ".cursorrules",
1757
+ format: "plain",
1758
+ notes: [
1759
+ "Cursor .cursorrules is a flat text file; fence markers are preserved as HTML comments."
1760
+ ]
1761
+ },
1762
+ windsurf: { path: ".windsurfrules", format: "plain" },
1763
+ copilot: {
1764
+ path: ".github/copilot-instructions.md",
1765
+ format: "markdown",
1766
+ notes: [
1767
+ "Copilot reads .github/copilot-instructions.md for project-level guidance (2026 spec)."
1768
+ ]
1769
+ },
1770
+ continue: {
1771
+ path: ".continue/rules/project.md",
1772
+ format: "markdown",
1773
+ notes: ["Continue supports per-rule files under .continue/rules/ as of v0.12."]
1774
+ },
1775
+ antigravity: {
1776
+ path: ".antigravity/rules/project.md",
1777
+ format: "markdown"
1778
+ },
1779
+ "gemini-cli": {
1780
+ path: ".gemini/rules/project.md",
1781
+ format: "markdown"
1782
+ },
1783
+ opencode: { path: ".opencode/rules/project.md", format: "markdown" }
1784
+ };
1785
+ function build(spec, input, formatName) {
1786
+ return {
1787
+ files: [
1788
+ {
1789
+ path: spec.path,
1790
+ content: fence(input),
1791
+ mergeable: true,
1792
+ format: spec.format
1793
+ }
1794
+ ],
1795
+ notes: spec.notes ?? [],
1796
+ format: formatName
1797
+ };
1798
+ }
1799
+ var projectRulesToClaude = (input) => build(TOOL_FILE_SPEC["claude"], input, "claude-md");
1800
+ var projectRulesToCodex = (input) => build(TOOL_FILE_SPEC["codex"], input, "agents-md");
1801
+ var projectRulesToGemini = (input) => build(TOOL_FILE_SPEC["gemini"], input, "gemini-md");
1802
+ var projectRulesToCursor = (input) => build(TOOL_FILE_SPEC["cursor"], input, "cursorrules");
1803
+ var projectRulesToWindsurf = (input) => build(TOOL_FILE_SPEC["windsurf"], input, "windsurfrules");
1804
+ var projectRulesToCopilot = (input) => build(TOOL_FILE_SPEC["copilot"], input, "copilot-instructions-md");
1805
+ var projectRulesToContinue = (input) => build(TOOL_FILE_SPEC["continue"], input, "continue-rules-md");
1806
+ var projectRulesToAntigravity = (input) => build(TOOL_FILE_SPEC["antigravity"], input, "antigravity-rules-md");
1807
+ var projectRulesToGeminiCli = (input) => build(TOOL_FILE_SPEC["gemini-cli"], input, "gemini-cli-rules-md");
1808
+ var projectRulesToOpenCode = (input) => build(TOOL_FILE_SPEC["opencode"], input, "opencode-rules-md");
1809
+ var projectRulesCanonical = (input) => ({
1810
+ files: [
1811
+ {
1812
+ path: `kordis/project-rules/${input.fullSlug}.md`,
1813
+ content: fence(input),
1814
+ mergeable: true,
1815
+ format: "markdown"
1816
+ }
1817
+ ],
1818
+ notes: [],
1819
+ format: "kordis-project-rules"
1820
+ });
1821
+
1822
+ // ../../packages/translators/src/mcp-server/all.ts
1823
+ import tomlLib from "@iarna/toml";
1824
+ function getName(input) {
1825
+ const n = input.frontmatter["name"];
1826
+ return typeof n === "string" ? n : input.fullSlug.split("/")[1] ?? input.fullSlug;
1827
+ }
1828
+ function getTransport(input) {
1829
+ const t = input.frontmatter["transport"];
1830
+ if (!t || typeof t !== "object") {
1831
+ throw new Error("mcp-server frontmatter missing `transport`");
1832
+ }
1833
+ return t;
1834
+ }
1835
+ var mcpToClaude = (input) => {
1836
+ const name = getName(input);
1837
+ const t = getTransport(input);
1838
+ const servers = {};
1839
+ if (t.type === "stdio") {
1840
+ servers[name] = {
1841
+ command: t.command,
1842
+ args: t.args ?? [],
1843
+ env: t.env ?? {}
1844
+ };
1845
+ } else {
1846
+ servers[name] = {
1847
+ url: t.url,
1848
+ transport: t.type,
1849
+ headers: t.headers ?? {}
1850
+ };
1851
+ }
1852
+ const doc = { mcpServers: servers };
1853
+ return {
1854
+ files: [
1855
+ {
1856
+ path: ".mcp.json",
1857
+ content: JSON.stringify(doc, null, 2) + "\n",
1858
+ mergeable: true,
1859
+ format: "json"
1860
+ }
1861
+ ],
1862
+ notes: [],
1863
+ format: "claude-mcp-json"
1864
+ };
1865
+ };
1866
+ var mcpToCodex = (input) => {
1867
+ const name = getName(input);
1868
+ const t = getTransport(input);
1869
+ const notes = [];
1870
+ const body = {};
1871
+ if (t.type === "stdio") {
1872
+ body.command = t.command;
1873
+ body.args = t.args ?? [];
1874
+ if (t.env && Object.keys(t.env).length > 0) body.env = t.env;
1875
+ } else {
1876
+ notes.push(
1877
+ "Codex does not yet support HTTP/SSE MCP transports; emitting placeholder. Re-pull after Codex 2026-Q2 adds support."
1878
+ );
1879
+ body.url = t.url;
1880
+ body.transport = t.type;
1881
+ }
1882
+ const doc = { mcp_servers: { [name]: body } };
1883
+ return {
1884
+ files: [
1885
+ {
1886
+ path: ".codex/config.toml",
1887
+ content: tomlLib.stringify(doc),
1888
+ mergeable: true,
1889
+ format: "toml"
1890
+ }
1891
+ ],
1892
+ notes,
1893
+ format: "codex-toml"
1894
+ };
1895
+ };
1896
+ var mcpToContinue = (input) => {
1897
+ const name = getName(input);
1898
+ const t = getTransport(input);
1899
+ const lines = [`name: ${name}`];
1900
+ if (t.type === "stdio") {
1901
+ lines.push("type: stdio", `command: ${JSON.stringify(t.command)}`);
1902
+ if (t.args && t.args.length > 0) {
1903
+ lines.push("args:");
1904
+ for (const a of t.args) lines.push(` - ${JSON.stringify(a)}`);
1905
+ }
1906
+ if (t.env && Object.keys(t.env).length > 0) {
1907
+ lines.push("env:");
1908
+ for (const [k, v] of Object.entries(t.env)) lines.push(` ${k}: ${JSON.stringify(v)}`);
1909
+ }
1910
+ } else {
1911
+ lines.push(`type: ${t.type}`, `url: ${JSON.stringify(t.url)}`);
1912
+ if (t.headers && Object.keys(t.headers).length > 0) {
1913
+ lines.push("headers:");
1914
+ for (const [k, v] of Object.entries(t.headers)) lines.push(` ${k}: ${JSON.stringify(v)}`);
1915
+ }
1916
+ }
1917
+ return {
1918
+ files: [
1919
+ {
1920
+ path: `.continue/mcpServers/${name}.yaml`,
1921
+ content: lines.join("\n") + "\n",
1922
+ mergeable: false,
1923
+ format: "yaml"
1924
+ }
1925
+ ],
1926
+ notes: [],
1927
+ format: "continue-mcp-yaml"
1928
+ };
1929
+ };
1930
+ var mcpCanonical = (input) => {
1931
+ const name = getName(input);
1932
+ const t = getTransport(input);
1933
+ const doc = { name, transport: t, version: input.version };
1934
+ return {
1935
+ files: [
1936
+ {
1937
+ path: `kordis/mcp-servers/${name}.json`,
1938
+ content: JSON.stringify(doc, null, 2) + "\n",
1939
+ mergeable: false,
1940
+ format: "json"
1941
+ }
1942
+ ],
1943
+ notes: [],
1944
+ format: "kordis-mcp-manifest"
1945
+ };
1946
+ };
1947
+
1948
+ // ../../packages/translators/src/custom-agent/claude.ts
1949
+ var customAgentToClaude = (input) => {
1950
+ const fm = input.frontmatter;
1951
+ const name = typeof fm["name"] === "string" ? fm["name"] : input.fullSlug.split("/")[1] ?? input.fullSlug;
1952
+ const description = typeof fm["description"] === "string" ? fm["description"] : "";
1953
+ const systemPrompt = typeof fm["system_prompt"] === "string" ? fm["system_prompt"] : "";
1954
+ const model = typeof fm["model"] === "string" ? fm["model"] : null;
1955
+ const context = typeof fm["context"] === "string" ? fm["context"] : null;
1956
+ const allowed = Array.isArray(fm["allowed_tools"]) ? fm["allowed_tools"] : [];
1957
+ const disallowed = Array.isArray(fm["disallowed_tools"]) ? fm["disallowed_tools"] : [];
1958
+ const lines = ["---", `name: ${name}`, `description: ${JSON.stringify(description)}`];
1959
+ if (model) lines.push(`model: ${JSON.stringify(model)}`);
1960
+ if (context) lines.push(`context: ${context}`);
1961
+ if (allowed.length > 0) {
1962
+ lines.push("allowed_tools:");
1963
+ for (const t of allowed) lines.push(` - ${JSON.stringify(t)}`);
1964
+ }
1965
+ if (disallowed.length > 0) {
1966
+ lines.push("disallowed_tools:");
1967
+ for (const t of disallowed) lines.push(` - ${JSON.stringify(t)}`);
1968
+ }
1969
+ lines.push("---", "", systemPrompt.trimEnd(), "");
1970
+ if (input.body.trim().length > 0) {
1971
+ lines.push("", "<!-- body (README-style) -->", input.body.trimStart());
1972
+ }
1973
+ return {
1974
+ files: [
1975
+ {
1976
+ path: `.claude/agents/${name}.md`,
1977
+ content: lines.join("\n"),
1978
+ mergeable: false,
1979
+ format: "markdown"
1980
+ }
1981
+ ],
1982
+ notes: [],
1983
+ format: "claude-agent-md"
1984
+ };
1985
+ };
1986
+ var customAgentCanonical = (input) => {
1987
+ const res = customAgentToClaude(input);
1988
+ if (res.files[0]) {
1989
+ res.files[0].path = `kordis/custom-agents/${input.fullSlug.replace("/", "__")}.md`;
1990
+ }
1991
+ res.format = "kordis-custom-agent";
1992
+ return res;
1993
+ };
1994
+
1995
+ // ../../packages/translators/src/hook/claude.ts
1996
+ var hookToClaude = (input) => {
1997
+ const hooks = input.frontmatter["hooks"];
1998
+ if (!Array.isArray(hooks)) {
1999
+ throw new Error("hook frontmatter missing `hooks` array");
2000
+ }
2001
+ const byEvent = {};
2002
+ for (const raw of hooks) {
2003
+ if (!raw || typeof raw !== "object") continue;
2004
+ const list = byEvent[raw.event] ?? (byEvent[raw.event] = []);
2005
+ const entry = {};
2006
+ if (raw.matcher) entry.matcher = raw.matcher;
2007
+ const hookSpec = { type: raw.action.type };
2008
+ if (raw.action.type === "command") {
2009
+ hookSpec.command = raw.action.command;
2010
+ } else {
2011
+ hookSpec.url = raw.action.url;
2012
+ hookSpec.method = raw.action.method ?? "POST";
2013
+ if (raw.action.headers) hookSpec.headers = raw.action.headers;
2014
+ }
2015
+ if (raw.action.timeout_ms !== void 0) hookSpec.timeout = raw.action.timeout_ms;
2016
+ if (raw.action.blocking) hookSpec.blocking = true;
2017
+ entry.hooks = [hookSpec];
2018
+ list.push(entry);
2019
+ }
2020
+ const doc = { hooks: byEvent };
2021
+ return {
2022
+ files: [
2023
+ {
2024
+ path: ".claude/settings.json",
2025
+ content: JSON.stringify(doc, null, 2) + "\n",
2026
+ mergeable: true,
2027
+ format: "json"
2028
+ }
2029
+ ],
2030
+ notes: [],
2031
+ format: "claude-hooks-json"
2032
+ };
2033
+ };
2034
+ var hookCanonical = (input) => {
2035
+ const res = hookToClaude(input);
2036
+ if (res.files[0]) {
2037
+ res.files[0].path = `kordis/hooks/${input.fullSlug.replace("/", "__")}.json`;
2038
+ }
2039
+ res.format = "kordis-hook";
2040
+ return res;
2041
+ };
2042
+
2043
+ // ../../packages/translators/src/registry.ts
2044
+ var REGISTRY = {
2045
+ skill: {
2046
+ canonical: skillCanonical,
2047
+ "claude-code": skillToClaude,
2048
+ codex: skillToCodex,
2049
+ cursor: skillToCursor,
2050
+ windsurf: skillToWindsurf,
2051
+ continue: skillToContinue,
2052
+ copilot: skillToCopilot,
2053
+ "gemini-cli": skillToGeminiCli,
2054
+ opencode: skillToOpenCode,
2055
+ antigravity: skillToAntigravity
2056
+ },
2057
+ "project-rules": {
2058
+ canonical: projectRulesCanonical,
2059
+ "claude-code": projectRulesToClaude,
2060
+ codex: projectRulesToCodex,
2061
+ cursor: projectRulesToCursor,
2062
+ windsurf: projectRulesToWindsurf,
2063
+ continue: projectRulesToContinue,
2064
+ copilot: projectRulesToCopilot,
2065
+ "gemini-cli": projectRulesToGeminiCli,
2066
+ opencode: projectRulesToOpenCode,
2067
+ antigravity: projectRulesToAntigravity
2068
+ // `gemini` CLI vs codex's GEMINI.md — codex reads both; we map "gemini-cli"
2069
+ // to its own dir and "codex" to AGENTS.md. GEMINI.md alias below.
2070
+ },
2071
+ "mcp-server": {
2072
+ canonical: mcpCanonical,
2073
+ "claude-code": mcpToClaude,
2074
+ codex: mcpToCodex,
2075
+ continue: mcpToContinue
2076
+ },
2077
+ "custom-agent": {
2078
+ canonical: customAgentCanonical,
2079
+ "claude-code": customAgentToClaude
2080
+ },
2081
+ hook: {
2082
+ canonical: hookCanonical,
2083
+ "claude-code": hookToClaude
2084
+ }
2085
+ };
2086
+ REGISTRY["project-rules"]["gemini-cli"] = projectRulesToGemini;
2087
+ var TranslatorNotSupportedError = class extends Error {
2088
+ type;
2089
+ format;
2090
+ constructor(type, format) {
2091
+ super(
2092
+ `No translator registered for type="${type}" format="${format}". Fall back to "canonical" or choose a supported tool for this type.`
2093
+ );
2094
+ this.name = "TranslatorNotSupportedError";
2095
+ this.type = type;
2096
+ this.format = format;
2097
+ }
2098
+ };
2099
+ function getTranslator(type, format) {
2100
+ const fn = REGISTRY[type]?.[format];
2101
+ if (!fn) throw new TranslatorNotSupportedError(type, format);
2102
+ return fn;
2103
+ }
2104
+
2105
+ // src/commands/pull.ts
2106
+ function pullCommand() {
2107
+ return new Command5("pull").description("install a package into the current project").argument("<full_slug>", "package identifier {username}/{slug}").option("--tool <name>", "target tool format (default: auto-detect)").option("--target <path>", "install into a subdirectory (default: cwd)").option("--with-recommends", "also install recommended dependencies", false).action(
2108
+ async (fullSlug, opts) => {
2109
+ const [username, slug] = fullSlug.split("/");
2110
+ if (!username || !slug) {
2111
+ throw new Error('full_slug must be "{username}/{slug}"');
2112
+ }
2113
+ const bundle = await useClient();
2114
+ const userCwd = process.env["INIT_CWD"] ?? process.cwd();
2115
+ const installRoot = opts.target ?? userCwd;
2116
+ const detected = opts.tool ?? await detectTool(installRoot);
2117
+ const spin = spinner(`Pulling ${fullSlug}`);
2118
+ const detail = await bundle.api.getPackage(username, slug);
2119
+ await bundle.api.pull(username, slug);
2120
+ const contentRes = await bundle.api.getContent(username, slug, {
2121
+ format: "canonical"
2122
+ });
2123
+ spin.succeed();
2124
+ const parsed = validatePackageSource(contentRes.content);
2125
+ if (!parsed.ok || !parsed.type || !parsed.frontmatter) {
2126
+ throw new Error("package stored content is not currently valid");
2127
+ }
2128
+ const written = [];
2129
+ const notes = [];
2130
+ const format = detected ?? "canonical";
2131
+ try {
2132
+ const translator = getTranslator(parsed.type, format);
2133
+ const result = translator({
2134
+ fullSlug,
2135
+ frontmatter: parsed.frontmatter.data,
2136
+ body: parsed.body,
2137
+ version: contentRes.version
2138
+ });
2139
+ for (const f of result.files) {
2140
+ const abs = join3(installRoot, f.path);
2141
+ await mkdir3(dirname(abs), { recursive: true });
2142
+ await writeFile3(abs, f.content, "utf8");
2143
+ written.push(abs);
2144
+ }
2145
+ notes.push(...result.notes);
2146
+ } catch (err) {
2147
+ if (err instanceof TranslatorNotSupportedError) {
2148
+ const canonical = getTranslator(parsed.type, "canonical");
2149
+ const result = canonical({
2150
+ fullSlug,
2151
+ frontmatter: parsed.frontmatter.data,
2152
+ body: parsed.body,
2153
+ version: contentRes.version
2154
+ });
2155
+ notes.push(
2156
+ `Tool "${format}" is not supported for type "${parsed.type}"; installed canonical form instead.`
2157
+ );
2158
+ for (const f of result.files) {
2159
+ const abs = join3(installRoot, f.path);
2160
+ await mkdir3(dirname(abs), { recursive: true });
2161
+ await writeFile3(abs, f.content, "utf8");
2162
+ written.push(abs);
2163
+ }
2164
+ } else {
2165
+ throw err;
2166
+ }
2167
+ }
2168
+ printDual({
2169
+ json: {
2170
+ status: "installed",
2171
+ full_slug: fullSlug,
2172
+ version: detail.current_version,
2173
+ tool_detected: detected ?? null,
2174
+ installed_paths: written,
2175
+ notes
2176
+ },
2177
+ human: () => {
2178
+ printHuman(
2179
+ c.green(`\u2713 Installed ${c.bold(fullSlug)} v${detail.current_version}`)
2180
+ );
2181
+ for (const w of written) printHuman(` ${c.gray("wrote")} ${w}`);
2182
+ for (const n of notes) printHuman(c.yellow(` ! ${n}`));
2183
+ }
2184
+ });
2185
+ }
2186
+ );
2187
+ }
2188
+ async function detectTool(root) {
2189
+ const { stat } = await import("fs/promises");
2190
+ const checks = [
2191
+ [".claude", "claude-code"],
2192
+ [".cursor", "cursor"],
2193
+ [".continue", "continue"],
2194
+ [".windsurf", "windsurf"],
2195
+ [".antigravity", "antigravity"],
2196
+ [".codex", "codex"],
2197
+ ["AGENTS.md", "codex"],
2198
+ ["CLAUDE.md", "claude-code"],
2199
+ [".cursorrules", "cursor"],
2200
+ [".windsurfrules", "windsurf"],
2201
+ [".github/copilot-instructions.md", "copilot"]
2202
+ ];
2203
+ for (const [path, tool] of checks) {
2204
+ try {
2205
+ await stat(join3(root, path));
2206
+ return tool;
2207
+ } catch {
2208
+ }
2209
+ }
2210
+ return null;
2211
+ }
2212
+
2213
+ // src/commands/read.ts
2214
+ import { Command as Command6 } from "commander";
2215
+ function readCommand() {
2216
+ return new Command6("read").description("fetch a package body into the agent context (no install)").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").option("--allow-pending", "allow versions whose AI review has not passed", false).option("--version <semver>", "pin to an exact version (overrides @suffix)").action(
2217
+ async (rawSlug, opts) => {
2218
+ const atIdx = rawSlug.lastIndexOf("@");
2219
+ const fullSlug = atIdx > 0 ? rawSlug.slice(0, atIdx) : rawSlug;
2220
+ const inlineVersion = atIdx > 0 ? rawSlug.slice(atIdx + 1) : void 0;
2221
+ const version = opts.version ?? inlineVersion;
2222
+ const [username, slug] = fullSlug.split("/");
2223
+ if (!username || !slug) {
2224
+ throw new Error('full_slug must be "{username}/{slug}" (optional "@version")');
2225
+ }
2226
+ const bundle = await useClient();
2227
+ const res = await bundle.api.jitRead(username, slug, {
2228
+ allow_pending: opts.allowPending,
2229
+ ...version ? { version } : {}
2230
+ });
2231
+ if (isJsonMode()) {
2232
+ printJson(res);
2233
+ return;
2234
+ }
2235
+ process.stdout.write(res.body);
2236
+ if (!res.body.endsWith("\n")) process.stdout.write("\n");
2237
+ process.stderr.write(
2238
+ `${c.gray("\u2014")} ${c.bold(res.full_slug)}${c.gray("@")}${res.version} ${c.gray("\xB7")} ${res.type} (tier ${res.tier}) ${c.gray("\xB7")} ${c.cyan(res.attribution)}
2239
+ `
2240
+ );
2241
+ printHuman("");
2242
+ }
2243
+ );
2244
+ }
2245
+
2246
+ // src/commands/info.ts
2247
+ import { Command as Command7 } from "commander";
2248
+ function infoCommand() {
2249
+ return new Command7("info").description("show details about a package").argument("<full_slug>", "package identifier {username}/{slug}").action(async (fullSlug) => {
2250
+ const [username, slug] = fullSlug.split("/");
2251
+ if (!username || !slug) throw new Error('full_slug must be "{username}/{slug}"');
2252
+ const bundle = await useClient();
2253
+ const detail = await bundle.api.getPackage(username, slug);
2254
+ printDual({
2255
+ json: detail,
2256
+ human: () => {
2257
+ printHuman(c.bold(detail.full_slug) + " " + c.gray(`v${detail.current_version}`));
2258
+ printHuman(" " + detail.description);
2259
+ printHuman("");
2260
+ printHuman(` ${c.cyan("Type")}: ${detail.type} (tier ${detail.tier})`);
2261
+ printHuman(` ${c.cyan("Author")}: ${detail.author.username}`);
2262
+ printHuman(` ${c.cyan("Stats")}: \u2605 ${detail.stars} \xB7 \u21E3 ${detail.downloads}`);
2263
+ if (detail.tools_compatible.length > 0) {
2264
+ printHuman(` ${c.cyan("Tools")}: ${detail.tools_compatible.join(", ")}`);
2265
+ }
2266
+ if (detail.depends.length > 0) {
2267
+ printHuman(` ${c.cyan("Depends")}:`);
2268
+ for (const d of detail.depends) {
2269
+ printHuman(` - ${d.full_slug} ${d.version_range}`);
2270
+ }
2271
+ }
2272
+ if (detail.recommends.length > 0) {
2273
+ printHuman(` ${c.cyan("Recommends")}:`);
2274
+ for (const d of detail.recommends) {
2275
+ printHuman(` - ${d.full_slug} ${d.version_range}`);
2276
+ }
2277
+ }
2278
+ }
2279
+ });
2280
+ });
2281
+ }
2282
+
2283
+ // src/commands/logout.ts
2284
+ import { Command as Command8 } from "commander";
2285
+ function logoutCommand() {
2286
+ return new Command8("logout").description("revoke the current session and clear local credentials").action(async () => {
2287
+ const bundle = await useClient();
2288
+ if (!bundle.username) {
2289
+ printHuman(c.yellow("Not logged in; nothing to do."));
2290
+ return;
2291
+ }
2292
+ try {
2293
+ await bundle.authedRequest((api) => api.revokeAllSessions());
2294
+ } catch {
2295
+ }
2296
+ await bundle.keychain.remove(bundle.username);
2297
+ const cfg = await loadCliConfig();
2298
+ delete cfg.username;
2299
+ delete cfg.sessionId;
2300
+ await saveCliConfig(cfg);
2301
+ printDual({
2302
+ json: { status: "logged_out" },
2303
+ human: () => printHuman(c.green("\u2713 Logged out."))
2304
+ });
2305
+ });
2306
+ }
2307
+
2308
+ // src/commands/setup.ts
2309
+ import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4, access } from "fs/promises";
2310
+ import { homedir as homedir3 } from "os";
2311
+ import { join as join4, dirname as dirname2 } from "path";
2312
+ import { fileURLToPath } from "url";
2313
+ import { Command as Command9 } from "commander";
2314
+ var MARKER_START = "<!-- kordis-start -->";
2315
+ var MARKER_END = "<!-- kordis-end -->";
2316
+ var TARGETS = [
2317
+ { tool: "Claude Code", dir: ".claude", file: ".claude/CLAUDE.md" },
2318
+ { tool: "Codex CLI", dir: ".codex", file: ".codex/AGENTS.md" },
2319
+ { tool: "Gemini CLI", dir: ".gemini", file: ".gemini/GEMINI.md" }
2320
+ ];
2321
+ async function exists(path) {
2322
+ try {
2323
+ await access(path);
2324
+ return true;
2325
+ } catch {
2326
+ return false;
2327
+ }
2328
+ }
2329
+ async function loadGuideContent() {
2330
+ const __dir = dirname2(fileURLToPath(import.meta.url));
2331
+ const bundled = join4(__dir, "..", "assets", "KORDIS.md");
2332
+ const source = join4(__dir, "..", "..", "..", "..", "packages", "skills", "KORDIS.md");
2333
+ const path = await exists(bundled) ? bundled : source;
2334
+ const raw = await readFile4(path, "utf-8");
2335
+ if (raw.startsWith("---")) {
2336
+ const end = raw.indexOf("\n---", 4);
2337
+ if (end !== -1) return raw.slice(end + 4).trimStart();
2338
+ }
2339
+ return raw;
2340
+ }
2341
+ async function injectBlock(filePath, content, dryRun) {
2342
+ const block = `${MARKER_START}
2343
+ ${content.trimEnd()}
2344
+ ${MARKER_END}`;
2345
+ let existing = "";
2346
+ let fileExists = true;
2347
+ try {
2348
+ existing = await readFile4(filePath, "utf-8");
2349
+ } catch {
2350
+ fileExists = false;
2351
+ }
2352
+ if (!fileExists) {
2353
+ if (!dryRun) {
2354
+ await mkdir4(dirname2(filePath), { recursive: true });
2355
+ await writeFile4(filePath, block + "\n", "utf-8");
2356
+ }
2357
+ return "injected";
2358
+ }
2359
+ const startIdx = existing.indexOf(MARKER_START);
2360
+ const endIdx = existing.indexOf(MARKER_END);
2361
+ if (startIdx !== -1 && endIdx !== -1) {
2362
+ const current = existing.slice(startIdx, endIdx + MARKER_END.length);
2363
+ if (current === block) return "unchanged";
2364
+ if (!dryRun) {
2365
+ const updated = existing.slice(0, startIdx) + block + existing.slice(endIdx + MARKER_END.length);
2366
+ await writeFile4(filePath, updated, "utf-8");
2367
+ }
2368
+ return "updated";
2369
+ }
2370
+ if (!dryRun) {
2371
+ await writeFile4(filePath, existing.trimEnd() + "\n\n" + block + "\n", "utf-8");
2372
+ }
2373
+ return "injected";
2374
+ }
2375
+ function setupCommand() {
2376
+ return new Command9("setup").description("inject kordis usage guide into global agent instruction files").option("--silent", "suppress errors \u2014 safe for postinstall").option("--dry-run", "detect installed tools without writing any files").action(async (opts) => {
2377
+ const home = homedir3();
2378
+ const dryRun = opts.dryRun ?? false;
2379
+ let guideContent;
2380
+ try {
2381
+ guideContent = await loadGuideContent();
2382
+ } catch (err) {
2383
+ if (opts.silent) return;
2384
+ throw err;
2385
+ }
2386
+ let anyFound = false;
2387
+ for (const target of TARGETS) {
2388
+ const dirPath = join4(home, target.dir);
2389
+ const filePath = join4(home, target.file);
2390
+ if (!await exists(dirPath)) {
2391
+ if (!opts.silent) {
2392
+ printHuman(c.gray(` - ${target.tool.padEnd(12)} not installed, skipped`));
2393
+ }
2394
+ continue;
2395
+ }
2396
+ anyFound = true;
2397
+ try {
2398
+ const result = await injectBlock(filePath, guideContent, dryRun);
2399
+ const label = `${target.tool.padEnd(12)}`;
2400
+ const dest = `~/${target.file}`;
2401
+ if (result === "unchanged") {
2402
+ printHuman(c.gray(` \u2713 ${label} already up-to-date`));
2403
+ } else if (dryRun) {
2404
+ printHuman(` ~ ${label} ${c.cyan(dest)} (dry-run: would ${result})`);
2405
+ } else {
2406
+ printHuman(c.green(` \u2713 ${label} \u2192 ${dest}`));
2407
+ }
2408
+ } catch (err) {
2409
+ if (opts.silent) continue;
2410
+ printHuman(c.red(` \u2717 ${target.tool}: ${err.message}`));
2411
+ }
2412
+ }
2413
+ if (!anyFound && !opts.silent) {
2414
+ printHuman(c.gray("No supported agent tools detected."));
2415
+ printHuman(c.gray("Run `kordis setup` again after installing Claude Code, Codex, or Gemini CLI."));
2416
+ }
2417
+ });
2418
+ }
2419
+
2420
+ // src/index.ts
2421
+ var VERSION = "0.0.1";
2422
+ function buildProgram() {
2423
+ const program = new Command10("kordis").version(VERSION).description("Kordis \u2014 registry for agentic-coding packages").option("--json", "emit JSON output to stdout (default: human)", false).hook("preAction", (thisCommand) => {
2424
+ const opts = thisCommand.optsWithGlobals();
2425
+ if (opts.json) setJsonMode(true);
2426
+ });
2427
+ program.addCommand(loginCommand());
2428
+ program.addCommand(logoutCommand());
2429
+ program.addCommand(whoamiCommand());
2430
+ program.addCommand(searchCommand());
2431
+ program.addCommand(pushCommand());
2432
+ program.addCommand(pullCommand());
2433
+ program.addCommand(readCommand());
2434
+ program.addCommand(infoCommand());
2435
+ program.addCommand(setupCommand());
2436
+ return program;
2437
+ }
2438
+ async function main(argv) {
2439
+ const program = buildProgram();
2440
+ try {
2441
+ const cleaned = argv.filter((a, i) => !(a === "--" && i >= 2));
2442
+ await program.parseAsync(cleaned);
2443
+ } catch (err) {
2444
+ handleError(err);
2445
+ }
2446
+ }
2447
+ function handleError(err) {
2448
+ if (err instanceof KordisApiError) {
2449
+ if (isJsonMode()) {
2450
+ printJson({
2451
+ error: {
2452
+ code: err.code,
2453
+ message: err.message,
2454
+ flags: err.flags,
2455
+ request_id: err.requestId
2456
+ }
2457
+ });
2458
+ } else {
2459
+ printHuman(c.red(`\u2717 ${err.code}: ${err.message}`));
2460
+ for (const f of err.flags) {
2461
+ printHuman(c.red(` - ${f.code}${f.message ? `: ${f.message}` : ""}`));
2462
+ }
2463
+ if (err.requestId) {
2464
+ printHuman(c.gray(` (request-id: ${err.requestId})`));
2465
+ }
2466
+ }
2467
+ process.exit(err.exitCode());
2468
+ }
2469
+ if (err instanceof KordisNetworkError) {
2470
+ if (isJsonMode()) printJson({ error: { code: "network_unreachable", message: err.message } });
2471
+ else printHuman(c.red(`\u2717 ${err.message}`));
2472
+ process.exit(err.exitCode());
2473
+ }
2474
+ if (err instanceof Error) {
2475
+ if (isJsonMode()) printJson({ error: { code: "generic_failure", message: err.message } });
2476
+ else printHuman(c.red(`\u2717 ${err.message}`));
2477
+ process.exit(1);
2478
+ }
2479
+ if (isJsonMode()) printJson({ error: { code: "generic_failure", message: String(err) } });
2480
+ else printHuman(c.red(`\u2717 ${String(err)}`));
2481
+ process.exit(1);
2482
+ }
2483
+ main(process.argv).catch(handleError);