@rayul/kordis 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -19
- package/assets/KORDIS.md +19 -8
- package/assets/kordis-publishing/SKILL.md +85 -14
- package/assets/kordis-search/SKILL.md +38 -15
- package/assets/kordis-social/SKILL.md +23 -7
- package/assets/kordis-using/SKILL.md +94 -34
- package/dist/index.js +939 -255
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ var init_primitives = __esm({
|
|
|
20
20
|
UsernameSchema = z.string().min(3).max(32).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "username must be lowercase alphanumeric with single hyphens");
|
|
21
21
|
SlugSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "slug must be lowercase alphanumeric with single hyphens");
|
|
22
22
|
FullSlugSchema = z.string().min(5).max(128).regex(/^[a-z0-9](-?[a-z0-9])*\/[a-z0-9](-?[a-z0-9])*$/, "full_slug must match {username}/{slug}");
|
|
23
|
-
SemverSchema = z.string().regex(
|
|
23
|
+
SemverSchema = z.string().max(32).regex(
|
|
24
24
|
// eslint-disable-next-line no-useless-escape
|
|
25
25
|
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/,
|
|
26
26
|
"must be a valid semver version"
|
|
@@ -45,7 +45,7 @@ var init_primitives = __esm({
|
|
|
45
45
|
|
|
46
46
|
// ../../packages/contracts/src/enums.ts
|
|
47
47
|
import { z as z2 } from "zod";
|
|
48
|
-
var HarnessTypeSchema, PHASE_1_TYPES, TierSchema, TIER_OF_TYPE, CAPABILITIES_OF_TYPE, SortModeSchema, TimeWindowSchema, VisibilitySchema, ReviewStatusSchema, PublishStatusSchema, DependencyKindSchema, OAuthProviderSchema, UserRoleSchema, ReportReasonSchema;
|
|
48
|
+
var HarnessTypeSchema, PHASE_1_TYPES, TierSchema, TIER_OF_TYPE, CAPABILITIES_OF_TYPE, FamilySchema, LineageKindSchema, TrustTierSchema, SortModeSchema, TimeWindowSchema, VisibilitySchema, ReviewStatusSchema, PublishStatusSchema, DependencyKindSchema, OAuthProviderSchema, UserRoleSchema, ReportReasonSchema;
|
|
49
49
|
var init_enums = __esm({
|
|
50
50
|
"../../packages/contracts/src/enums.ts"() {
|
|
51
51
|
"use strict";
|
|
@@ -55,6 +55,7 @@ var init_enums = __esm({
|
|
|
55
55
|
"skill",
|
|
56
56
|
"custom-agent",
|
|
57
57
|
"hook",
|
|
58
|
+
"judgment-pattern",
|
|
58
59
|
"scoped-rule",
|
|
59
60
|
"mdc-rule",
|
|
60
61
|
"plugin"
|
|
@@ -64,13 +65,17 @@ var init_enums = __esm({
|
|
|
64
65
|
"project-rules",
|
|
65
66
|
"skill",
|
|
66
67
|
"custom-agent",
|
|
67
|
-
"hook"
|
|
68
|
+
"hook",
|
|
69
|
+
"judgment-pattern"
|
|
68
70
|
];
|
|
69
71
|
TierSchema = z2.enum(["A", "B", "C", "D"]);
|
|
70
72
|
TIER_OF_TYPE = {
|
|
71
73
|
"mcp-server": "A",
|
|
72
74
|
"project-rules": "A",
|
|
73
75
|
skill: "A",
|
|
76
|
+
// Judgment is the most portable unit of all — it carries no tool-specific
|
|
77
|
+
// wiring, only reasoning. Tier A so it surfaces in every read/search gate.
|
|
78
|
+
"judgment-pattern": "A",
|
|
74
79
|
"custom-agent": "B",
|
|
75
80
|
"scoped-rule": "B",
|
|
76
81
|
hook: "C",
|
|
@@ -80,6 +85,8 @@ var init_enums = __esm({
|
|
|
80
85
|
CAPABILITIES_OF_TYPE = {
|
|
81
86
|
skill: { jit_eligible: true, executable: false },
|
|
82
87
|
"project-rules": { jit_eligible: true, executable: false },
|
|
88
|
+
// A judgment is pure text-to-read — never installs or runs anything.
|
|
89
|
+
"judgment-pattern": { jit_eligible: true, executable: false },
|
|
83
90
|
"custom-agent": { jit_eligible: true, executable: false },
|
|
84
91
|
"scoped-rule": { jit_eligible: true, executable: false },
|
|
85
92
|
"mdc-rule": { jit_eligible: true, executable: false },
|
|
@@ -87,6 +94,14 @@ var init_enums = __esm({
|
|
|
87
94
|
hook: { jit_eligible: false, executable: true },
|
|
88
95
|
plugin: { jit_eligible: false, executable: true }
|
|
89
96
|
};
|
|
97
|
+
FamilySchema = z2.enum(["component", "judgment"]);
|
|
98
|
+
LineageKindSchema = z2.enum(["adaptation", "fork", "absorption", "remix"]);
|
|
99
|
+
TrustTierSchema = z2.enum([
|
|
100
|
+
"re-verified",
|
|
101
|
+
"community-adapted",
|
|
102
|
+
"author-reported",
|
|
103
|
+
"unsubstantiated"
|
|
104
|
+
]);
|
|
90
105
|
SortModeSchema = z2.enum([
|
|
91
106
|
"stars",
|
|
92
107
|
"downloads",
|
|
@@ -103,13 +118,7 @@ var init_enums = __esm({
|
|
|
103
118
|
DependencyKindSchema = z2.enum(["required", "recommended"]);
|
|
104
119
|
OAuthProviderSchema = z2.enum(["discord", "github", "google"]);
|
|
105
120
|
UserRoleSchema = z2.enum(["user", "moderator", "admin"]);
|
|
106
|
-
ReportReasonSchema = z2.enum([
|
|
107
|
-
"spam",
|
|
108
|
-
"harmful",
|
|
109
|
-
"incorrect",
|
|
110
|
-
"copyright",
|
|
111
|
-
"other"
|
|
112
|
-
]);
|
|
121
|
+
ReportReasonSchema = z2.enum(["spam", "harmful", "incorrect", "copyright", "other"]);
|
|
113
122
|
}
|
|
114
123
|
});
|
|
115
124
|
|
|
@@ -329,6 +338,14 @@ var init_package = __esm({
|
|
|
329
338
|
recommends: z6.array(DependencySpecSchema),
|
|
330
339
|
categories: z6.array(z6.string()).optional().default([]),
|
|
331
340
|
verified_by_kordis: z6.boolean(),
|
|
341
|
+
/**
|
|
342
|
+
* v2.1+ — derived evidence trust tier. Non-null only for the `judgment`
|
|
343
|
+
* family (a judgment's value rests on "this works"); null for component
|
|
344
|
+
* types. Strongest-wins over {verified_by_kordis, distinct-author lineage
|
|
345
|
+
* adopters, presence of an `evidence` frontmatter field}. See TrustTierSchema.
|
|
346
|
+
* Optional so pre-0.12 responses parse cleanly.
|
|
347
|
+
*/
|
|
348
|
+
trust_tier: TrustTierSchema.nullable().optional(),
|
|
332
349
|
/**
|
|
333
350
|
* 0.9.0+ — when the caller pinned a `@version` or `@range`, the
|
|
334
351
|
* concrete resolved version. Absent for bare-slug calls (where the
|
|
@@ -387,13 +404,7 @@ var init_comment = __esm({
|
|
|
387
404
|
"use strict";
|
|
388
405
|
init_primitives();
|
|
389
406
|
init_user();
|
|
390
|
-
CommentKindSchema = z7.enum([
|
|
391
|
-
"comment",
|
|
392
|
-
"tip",
|
|
393
|
-
"report",
|
|
394
|
-
"praise",
|
|
395
|
-
"question"
|
|
396
|
-
]);
|
|
407
|
+
CommentKindSchema = z7.enum(["comment", "tip", "report", "praise", "question"]);
|
|
397
408
|
CommentStatusSchema = z7.enum(["open", "resolved"]);
|
|
398
409
|
CommentSchema = z7.object({
|
|
399
410
|
id: UuidSchema,
|
|
@@ -417,7 +428,7 @@ var init_comment = __esm({
|
|
|
417
428
|
is_deleted: z7.boolean(),
|
|
418
429
|
created_at: IsoDateTimeSchema,
|
|
419
430
|
/**
|
|
420
|
-
* Web permalink (`https://kordis.store
|
|
431
|
+
* Web permalink (`https://kordis.store/<full_slug>#comment-<id>`). Server
|
|
421
432
|
* populates whenever it has the package slug at serialization time —
|
|
422
433
|
* always on create + list paths; absent on update/status paths that don't
|
|
423
434
|
* carry the slug. Old API responses (pre-0.6.4) omit it; new clients use
|
|
@@ -474,12 +485,12 @@ var init_auth = __esm({
|
|
|
474
485
|
from: z8.enum(["web", "cli"]).default("web")
|
|
475
486
|
});
|
|
476
487
|
DeviceCodeRequestSchema = z8.object({
|
|
477
|
-
provider: OAuthProviderSchema.default("
|
|
488
|
+
provider: OAuthProviderSchema.default("google")
|
|
478
489
|
});
|
|
479
490
|
DeviceCodeResponseSchema = z8.object({
|
|
480
491
|
device_code: z8.string().min(16),
|
|
481
|
-
user_code: z8.string().
|
|
482
|
-
// shown to the user: e.g. "KORDIS-
|
|
492
|
+
user_code: z8.string().regex(/^KORDIS-[0-9A-Z]{4}-[0-9A-Z]{4}$/),
|
|
493
|
+
// shown to the user: e.g. "KORDIS-7QFX-2M9K"
|
|
483
494
|
verification_url: z8.string().url(),
|
|
484
495
|
expires_in: z8.number().int().positive(),
|
|
485
496
|
// seconds
|
|
@@ -515,7 +526,7 @@ var init_auth = __esm({
|
|
|
515
526
|
|
|
516
527
|
// ../../packages/contracts/src/api.ts
|
|
517
528
|
import { z as z9 } from "zod";
|
|
518
|
-
var SearchQuerySchema, SearchResponseSchema, TrendingQuerySchema, RisingQuerySchema, StarResponseSchema, AssetSchema, PublishPackageRequestSchema, PublishWaitQuerySchema, PublishPackageResponseSchema, PackagePathParamsSchema, ContentQuerySchema, PackageContentResponseSchema, JitReadRequestSchema, JitReadResponseSchema, PullPackageRequestSchema, PullPackageResponseSchema, YankRequestSchema, YankResponseSchema, ResolveVersionQuerySchema, ResolveVersionResponseSchema, GetPackageQuerySchema, ListVersionsResponseSchema, VersionDiffQuerySchema, ListCommentsQuerySchema, ListCommentsResponseSchema, UserProfileResponseSchema, UserPackagesQuerySchema, ListUserPackagesResponseSchema, ListUserStarsResponseSchema, FollowResponseSchema, ListFollowersQuerySchema, ListFollowersResponseSchema;
|
|
529
|
+
var SearchQuerySchema, SearchResponseSchema, TrendingQuerySchema, RisingQuerySchema, StarResponseSchema, AssetSchema, LineageEdgeInputSchema, LineageRelativeSchema, LineageStatsSchema, LineageGraphResponseSchema, PublishPackageRequestSchema, PublishWaitQuerySchema, PublishPackageResponseSchema, PackagePathParamsSchema, ContentQuerySchema, PackageContentResponseSchema, JitReadRequestSchema, JitReadResponseSchema, PullPackageRequestSchema, PullPackageResponseSchema, YankRequestSchema, YankResponseSchema, ResolveVersionQuerySchema, ResolveVersionResponseSchema, GetPackageQuerySchema, ListVersionsResponseSchema, VersionDiffQuerySchema, ListCommentsQuerySchema, ListCommentsResponseSchema, UserProfileResponseSchema, UserPackagesQuerySchema, ListUserPackagesResponseSchema, ListUserStarsResponseSchema, FollowResponseSchema, ListFollowersQuerySchema, ListFollowersResponseSchema;
|
|
519
530
|
var init_api = __esm({
|
|
520
531
|
"../../packages/contracts/src/api.ts"() {
|
|
521
532
|
"use strict";
|
|
@@ -556,9 +567,41 @@ var init_api = __esm({
|
|
|
556
567
|
});
|
|
557
568
|
AssetSchema = z9.object({
|
|
558
569
|
path: z9.string().min(1).max(255),
|
|
559
|
-
|
|
570
|
+
// Schema-level backstop only: publish-service enforces the real 2 MB
|
|
571
|
+
// BYTE cap (utf8 chars can be multi-byte, base64 inflates 4/3). The
|
|
572
|
+
// char ceiling here just keeps non-core-api consumers of the contract
|
|
573
|
+
// from accepting unbounded strings.
|
|
574
|
+
content: z9.string().max(3e6),
|
|
560
575
|
encoding: z9.enum(["utf8", "base64"]).default("utf8")
|
|
561
576
|
});
|
|
577
|
+
LineageEdgeInputSchema = z9.object({
|
|
578
|
+
to_full_slug: FullSlugSchema,
|
|
579
|
+
to_version: SemverSchema.optional(),
|
|
580
|
+
kind: LineageKindSchema
|
|
581
|
+
});
|
|
582
|
+
LineageRelativeSchema = z9.object({
|
|
583
|
+
full_slug: FullSlugSchema,
|
|
584
|
+
title: z9.string(),
|
|
585
|
+
type: HarnessTypeSchema,
|
|
586
|
+
kind: LineageKindSchema,
|
|
587
|
+
/** Version snapshot recorded on the edge (the side facing this relative). */
|
|
588
|
+
version: SemverSchema.nullable(),
|
|
589
|
+
declared: z9.boolean()
|
|
590
|
+
});
|
|
591
|
+
LineageStatsSchema = z9.object({
|
|
592
|
+
/** Longest ancestor chain depth (capped traversal). */
|
|
593
|
+
lineage_depth: z9.number().int(),
|
|
594
|
+
/** Distinct packages derived FROM this one (the trust count). */
|
|
595
|
+
descendant_count: z9.number().int(),
|
|
596
|
+
/** Descendants whose edge kind is `absorption`. */
|
|
597
|
+
absorption_count: z9.number().int()
|
|
598
|
+
});
|
|
599
|
+
LineageGraphResponseSchema = z9.object({
|
|
600
|
+
full_slug: FullSlugSchema,
|
|
601
|
+
ancestors: z9.array(LineageRelativeSchema),
|
|
602
|
+
descendants: z9.array(LineageRelativeSchema),
|
|
603
|
+
stats: LineageStatsSchema
|
|
604
|
+
});
|
|
562
605
|
PublishPackageRequestSchema = z9.object({
|
|
563
606
|
/**
|
|
564
607
|
* Raw source content — YAML frontmatter + markdown body. Server parses and
|
|
@@ -585,6 +628,12 @@ var init_api = __esm({
|
|
|
585
628
|
changelog: z9.string().max(2e3).optional(),
|
|
586
629
|
depends: z9.array(z9.string()).optional(),
|
|
587
630
|
recommends: z9.array(z9.string()).optional(),
|
|
631
|
+
/**
|
|
632
|
+
* Declared provenance: sources this package was adapted / forked / absorbed /
|
|
633
|
+
* remixed from (`kordis push --adapted-from`). Recorded as `lineage_edges`;
|
|
634
|
+
* unresolved sources are skipped fail-open, never blocking the publish.
|
|
635
|
+
*/
|
|
636
|
+
lineage: z9.array(LineageEdgeInputSchema).max(20).optional(),
|
|
588
637
|
visibility: VisibilitySchema.optional(),
|
|
589
638
|
/**
|
|
590
639
|
* Skip draft validation (preview mode) — only allowed in dev builds.
|
|
@@ -621,6 +670,15 @@ var init_api = __esm({
|
|
|
621
670
|
version_from: SemverSchema.nullable(),
|
|
622
671
|
version_to: SemverSchema
|
|
623
672
|
}).nullable(),
|
|
673
|
+
/**
|
|
674
|
+
* Declared-lineage outcome, present only when the publish supplied `lineage`.
|
|
675
|
+
* `skipped` lists sources that could not be resolved (recorded fail-open, not
|
|
676
|
+
* an error) so the agent can fix a typo or publish the source first.
|
|
677
|
+
*/
|
|
678
|
+
lineage: z9.object({
|
|
679
|
+
recorded: z9.number().int().nonnegative(),
|
|
680
|
+
skipped: z9.array(FullSlugSchema)
|
|
681
|
+
}).optional(),
|
|
624
682
|
published_at: z9.string().datetime()
|
|
625
683
|
});
|
|
626
684
|
PackagePathParamsSchema = z9.object({
|
|
@@ -679,7 +737,23 @@ var init_api = __esm({
|
|
|
679
737
|
* the schema for backwards compatibility with pre-0.2.3 servers; the
|
|
680
738
|
* current server always populates it (empty array when no assets).
|
|
681
739
|
*/
|
|
682
|
-
bundled_assets: z9.array(z9.string()).optional()
|
|
740
|
+
bundled_assets: z9.array(z9.string()).optional(),
|
|
741
|
+
/**
|
|
742
|
+
* 0.11.x+ — soft-yank signal. An explicitly pinned yanked version is
|
|
743
|
+
* still served (reproducibility, mirroring pull), but the reading agent
|
|
744
|
+
* must be told so it can decide whether to trust the content. Optional
|
|
745
|
+
* for backwards compatibility with older servers; the current server
|
|
746
|
+
* always populates `yanked`.
|
|
747
|
+
*/
|
|
748
|
+
yanked: z9.boolean().optional(),
|
|
749
|
+
yank_reason: z9.string().nullable().optional(),
|
|
750
|
+
/**
|
|
751
|
+
* v2.1+ — derived evidence trust tier (see TrustTierSchema). Non-null only
|
|
752
|
+
* for the `judgment` family, so a reading agent can weight a self-reported
|
|
753
|
+
* claim differently from a community-corroborated one. Optional for
|
|
754
|
+
* backwards compatibility with pre-0.12 servers.
|
|
755
|
+
*/
|
|
756
|
+
trust_tier: TrustTierSchema.nullable().optional()
|
|
683
757
|
});
|
|
684
758
|
PullPackageRequestSchema = z9.object({
|
|
685
759
|
/**
|
|
@@ -1088,6 +1162,9 @@ var init_errors2 = __esm({
|
|
|
1088
1162
|
});
|
|
1089
1163
|
|
|
1090
1164
|
// ../../packages/api-client/src/client.ts
|
|
1165
|
+
function seg(value) {
|
|
1166
|
+
return encodeURIComponent(value);
|
|
1167
|
+
}
|
|
1091
1168
|
var KordisClient;
|
|
1092
1169
|
var init_client = __esm({
|
|
1093
1170
|
"../../packages/api-client/src/client.ts"() {
|
|
@@ -1139,10 +1216,11 @@ var init_client = __esm({
|
|
|
1139
1216
|
const retryable = method === "GET";
|
|
1140
1217
|
const debug = typeof process !== "undefined" && process.env && (process.env["KORDIS_DEBUG"] === "1" || process.env["KORDIS_DEBUG"]?.toLowerCase() === "true");
|
|
1141
1218
|
const debugStart = debug ? Date.now() : 0;
|
|
1219
|
+
const timeoutMs = this.config.requestTimeoutMs ?? 6e4;
|
|
1142
1220
|
let lastErr;
|
|
1143
1221
|
for (let attempt = 0; attempt < (retryable ? 2 : 1); attempt++) {
|
|
1144
1222
|
try {
|
|
1145
|
-
res = await this.fetchImpl(url, init);
|
|
1223
|
+
res = await this.fetchImpl(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
|
|
1146
1224
|
lastErr = void 0;
|
|
1147
1225
|
break;
|
|
1148
1226
|
} catch (err) {
|
|
@@ -1153,7 +1231,7 @@ var init_client = __esm({
|
|
|
1153
1231
|
`[kordis] api: ${method} ${url.pathname}${url.search} \u2192 fetch failed, retrying in 150ms (${err.message})`
|
|
1154
1232
|
);
|
|
1155
1233
|
}
|
|
1156
|
-
await new Promise((
|
|
1234
|
+
await new Promise((resolve3) => setTimeout(resolve3, 150));
|
|
1157
1235
|
continue;
|
|
1158
1236
|
}
|
|
1159
1237
|
}
|
|
@@ -1178,7 +1256,12 @@ var init_client = __esm({
|
|
|
1178
1256
|
}
|
|
1179
1257
|
if (res.status === 204) return void 0;
|
|
1180
1258
|
const text = await res.text();
|
|
1181
|
-
|
|
1259
|
+
let parsed;
|
|
1260
|
+
try {
|
|
1261
|
+
parsed = text ? JSON.parse(text) : void 0;
|
|
1262
|
+
} catch {
|
|
1263
|
+
parsed = void 0;
|
|
1264
|
+
}
|
|
1182
1265
|
if (!res.ok) {
|
|
1183
1266
|
const envelope = normalizeErrorEnvelope(res.status, parsed, text);
|
|
1184
1267
|
throw new KordisApiError(res.status, envelope, requestId);
|
|
@@ -1235,22 +1318,22 @@ var init_client = __esm({
|
|
|
1235
1318
|
});
|
|
1236
1319
|
}
|
|
1237
1320
|
update(username, slug, body) {
|
|
1238
|
-
return this.request(`/api/packages/${username}/${slug}`, {
|
|
1321
|
+
return this.request(`/api/packages/${seg(username)}/${seg(slug)}`, {
|
|
1239
1322
|
method: "PUT",
|
|
1240
1323
|
body
|
|
1241
1324
|
});
|
|
1242
1325
|
}
|
|
1243
1326
|
getPackage(username, slug, opts = {}) {
|
|
1244
|
-
return this.request(`/api/packages/${username}/${slug}`, {
|
|
1327
|
+
return this.request(`/api/packages/${seg(username)}/${seg(slug)}`, {
|
|
1245
1328
|
query: opts
|
|
1246
1329
|
});
|
|
1247
1330
|
}
|
|
1248
1331
|
deletePackage(username, slug) {
|
|
1249
|
-
return this.request(`/api/packages/${username}/${slug}`, { method: "DELETE" });
|
|
1332
|
+
return this.request(`/api/packages/${seg(username)}/${seg(slug)}`, { method: "DELETE" });
|
|
1250
1333
|
}
|
|
1251
1334
|
getContent(username, slug, opts = {}) {
|
|
1252
1335
|
return this.request(
|
|
1253
|
-
`/api/packages/${username}/${slug}/content`,
|
|
1336
|
+
`/api/packages/${seg(username)}/${seg(slug)}/content`,
|
|
1254
1337
|
{ query: opts }
|
|
1255
1338
|
);
|
|
1256
1339
|
}
|
|
@@ -1260,15 +1343,15 @@ var init_client = __esm({
|
|
|
1260
1343
|
*/
|
|
1261
1344
|
resolveVersion(username, slug, range) {
|
|
1262
1345
|
return this.request(
|
|
1263
|
-
`/api/packages/${username}/${slug}/resolve`,
|
|
1346
|
+
`/api/packages/${seg(username)}/${seg(slug)}/resolve`,
|
|
1264
1347
|
{ query: { range } }
|
|
1265
1348
|
);
|
|
1266
1349
|
}
|
|
1267
1350
|
pull(username, slug, opts = {}) {
|
|
1268
|
-
return this.request(
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
);
|
|
1351
|
+
return this.request(`/api/packages/${seg(username)}/${seg(slug)}/pull`, {
|
|
1352
|
+
method: "POST",
|
|
1353
|
+
body: opts
|
|
1354
|
+
});
|
|
1272
1355
|
}
|
|
1273
1356
|
/**
|
|
1274
1357
|
* JIT read — fetch the canonical body into agent context. Plan v1.1.
|
|
@@ -1276,10 +1359,10 @@ var init_client = __esm({
|
|
|
1276
1359
|
* (passed-only unless `allow_pending=true`).
|
|
1277
1360
|
*/
|
|
1278
1361
|
jitRead(username, slug, body = {}) {
|
|
1279
|
-
return this.request(
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
);
|
|
1362
|
+
return this.request(`/api/packages/${seg(username)}/${seg(slug)}/read`, {
|
|
1363
|
+
method: "POST",
|
|
1364
|
+
body
|
|
1365
|
+
});
|
|
1283
1366
|
}
|
|
1284
1367
|
search(query) {
|
|
1285
1368
|
return this.request("/api/packages/search", {
|
|
@@ -1324,18 +1407,24 @@ var init_client = __esm({
|
|
|
1324
1407
|
});
|
|
1325
1408
|
}
|
|
1326
1409
|
star(username, slug) {
|
|
1327
|
-
return this.request(`/api/packages/${username}/${slug}/star`, {
|
|
1410
|
+
return this.request(`/api/packages/${seg(username)}/${seg(slug)}/star`, {
|
|
1328
1411
|
method: "POST"
|
|
1329
1412
|
});
|
|
1330
1413
|
}
|
|
1331
1414
|
unstar(username, slug) {
|
|
1332
|
-
return this.request(`/api/packages/${username}/${slug}/star`, {
|
|
1415
|
+
return this.request(`/api/packages/${seg(username)}/${seg(slug)}/star`, {
|
|
1333
1416
|
method: "DELETE"
|
|
1334
1417
|
});
|
|
1335
1418
|
}
|
|
1336
1419
|
versions(username, slug) {
|
|
1337
1420
|
return this.request(
|
|
1338
|
-
`/api/packages/${username}/${slug}/versions`
|
|
1421
|
+
`/api/packages/${seg(username)}/${seg(slug)}/versions`
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
/** Provenance graph (Phase 2): ancestors + direct descendants + trust stats. */
|
|
1425
|
+
lineage(username, slug) {
|
|
1426
|
+
return this.request(
|
|
1427
|
+
`/api/packages/${seg(username)}/${seg(slug)}/lineage`
|
|
1339
1428
|
);
|
|
1340
1429
|
}
|
|
1341
1430
|
/**
|
|
@@ -1346,31 +1435,31 @@ var init_client = __esm({
|
|
|
1346
1435
|
yankVersion(username, slug, version, body = {}, opts = {}) {
|
|
1347
1436
|
const query = opts.asAdmin ? { as: "admin" } : void 0;
|
|
1348
1437
|
return this.request(
|
|
1349
|
-
`/api/packages/${username}/${slug}/versions/${version}/yank`,
|
|
1438
|
+
`/api/packages/${seg(username)}/${seg(slug)}/versions/${seg(version)}/yank`,
|
|
1350
1439
|
{ method: "POST", body, ...query ? { query } : {} }
|
|
1351
1440
|
);
|
|
1352
1441
|
}
|
|
1353
1442
|
unyankVersion(username, slug, version, opts = {}) {
|
|
1354
1443
|
const query = opts.asAdmin ? { as: "admin" } : void 0;
|
|
1355
1444
|
return this.request(
|
|
1356
|
-
`/api/packages/${username}/${slug}/versions/${version}/yank`,
|
|
1445
|
+
`/api/packages/${seg(username)}/${seg(slug)}/versions/${seg(version)}/yank`,
|
|
1357
1446
|
{ method: "DELETE", ...query ? { query } : {} }
|
|
1358
1447
|
);
|
|
1359
1448
|
}
|
|
1360
1449
|
diff(username, slug, from, to) {
|
|
1361
|
-
return this.request(`/api/packages/${username}/${slug}/diff`, {
|
|
1450
|
+
return this.request(`/api/packages/${seg(username)}/${seg(slug)}/diff`, {
|
|
1362
1451
|
query: { from, to }
|
|
1363
1452
|
});
|
|
1364
1453
|
}
|
|
1365
1454
|
// ─── comments ──────────────────────────────────────────────────────────
|
|
1366
1455
|
listComments(username, slug, query = {}) {
|
|
1367
1456
|
return this.request(
|
|
1368
|
-
`/api/packages/${username}/${slug}/comments`,
|
|
1457
|
+
`/api/packages/${seg(username)}/${seg(slug)}/comments`,
|
|
1369
1458
|
{ query }
|
|
1370
1459
|
);
|
|
1371
1460
|
}
|
|
1372
1461
|
createComment(username, slug, body) {
|
|
1373
|
-
return this.request(`/api/packages/${username}/${slug}/comments`, {
|
|
1462
|
+
return this.request(`/api/packages/${seg(username)}/${seg(slug)}/comments`, {
|
|
1374
1463
|
method: "POST",
|
|
1375
1464
|
body
|
|
1376
1465
|
});
|
|
@@ -1396,9 +1485,7 @@ var init_client = __esm({
|
|
|
1396
1485
|
return this.request(`/api/comments/${id}`, { method: "DELETE" });
|
|
1397
1486
|
}
|
|
1398
1487
|
listReplies(id) {
|
|
1399
|
-
return this.request(
|
|
1400
|
-
`/api/comments/${id}/replies`
|
|
1401
|
-
);
|
|
1488
|
+
return this.request(`/api/comments/${id}/replies`);
|
|
1402
1489
|
}
|
|
1403
1490
|
// ─── telemetry ─────────────────────────────────────────────────────────
|
|
1404
1491
|
/**
|
|
@@ -1432,10 +1519,9 @@ var init_client = __esm({
|
|
|
1432
1519
|
}
|
|
1433
1520
|
/** Cursor-paginated packages that `username` has starred. */
|
|
1434
1521
|
listUserStars(username, query = {}) {
|
|
1435
|
-
return this.request(
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
);
|
|
1522
|
+
return this.request(`/api/users/${encodeURIComponent(username)}/stars`, {
|
|
1523
|
+
query
|
|
1524
|
+
});
|
|
1439
1525
|
}
|
|
1440
1526
|
/** Auth-required. Idempotent (re-follow is a no-op). Returns post-mutation counter. */
|
|
1441
1527
|
followUser(username) {
|
|
@@ -1485,7 +1571,7 @@ var init_src2 = __esm({
|
|
|
1485
1571
|
|
|
1486
1572
|
// src/index.ts
|
|
1487
1573
|
init_src2();
|
|
1488
|
-
import { Command as
|
|
1574
|
+
import { Command as Command26 } from "commander";
|
|
1489
1575
|
|
|
1490
1576
|
// src/commands/login.ts
|
|
1491
1577
|
import { Command } from "commander";
|
|
@@ -1523,7 +1609,7 @@ async function loadCliConfig() {
|
|
|
1523
1609
|
}
|
|
1524
1610
|
async function saveCliConfig(cfg) {
|
|
1525
1611
|
await mkdir(configDir(), { recursive: true });
|
|
1526
|
-
await writeFile(configPath(), JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
1612
|
+
await writeFile(configPath(), JSON.stringify(cfg, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
1527
1613
|
}
|
|
1528
1614
|
|
|
1529
1615
|
// src/keychain.ts
|
|
@@ -1919,7 +2005,7 @@ async function reportError(kind, err, extras = {}) {
|
|
|
1919
2005
|
});
|
|
1920
2006
|
await Promise.race([
|
|
1921
2007
|
client.recordTelemetryError(payload),
|
|
1922
|
-
new Promise((
|
|
2008
|
+
new Promise((resolve3) => setTimeout(resolve3, REPORT_TIMEOUT_MS))
|
|
1923
2009
|
]);
|
|
1924
2010
|
} catch {
|
|
1925
2011
|
}
|
|
@@ -1947,7 +2033,7 @@ async function readPending() {
|
|
|
1947
2033
|
}
|
|
1948
2034
|
async function savePending(file) {
|
|
1949
2035
|
await mkdir3(dir(), { recursive: true });
|
|
1950
|
-
await writeFile3(path(), JSON.stringify(file, null, 2) + "\n", "utf8");
|
|
2036
|
+
await writeFile3(path(), JSON.stringify(file, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
1951
2037
|
}
|
|
1952
2038
|
async function clearPending() {
|
|
1953
2039
|
try {
|
|
@@ -2105,16 +2191,31 @@ function spinner(text) {
|
|
|
2105
2191
|
var c = kleur;
|
|
2106
2192
|
|
|
2107
2193
|
// src/commands/login.ts
|
|
2194
|
+
function assertSafeVerificationUrl(raw) {
|
|
2195
|
+
let url;
|
|
2196
|
+
try {
|
|
2197
|
+
url = new URL(raw);
|
|
2198
|
+
} catch {
|
|
2199
|
+
throw new Error(`registry returned an invalid verification URL: ${JSON.stringify(raw)}`);
|
|
2200
|
+
}
|
|
2201
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
2202
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
2203
|
+
throw new Error(
|
|
2204
|
+
`registry returned a verification URL with unexpected scheme "${url.protocol}" \u2014 refusing to open it. Check KORDIS_API_URL.`
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2108
2208
|
function loginCommand() {
|
|
2109
|
-
return new Command("login").description("authenticate with the Kordis registry").option("--provider <name>", "OAuth provider", "
|
|
2209
|
+
return new Command("login").description("authenticate with the Kordis registry").option("--provider <name>", "OAuth provider", "google").option("--no-browser", "don't auto-open the browser; print the URL instead").option(
|
|
2110
2210
|
"--emit-only",
|
|
2111
2211
|
"start device flow, open browser, save pending state, exit immediately \u2014 for non-interactive (agent) contexts where the 10-min poll would exceed the bash timeout"
|
|
2112
2212
|
).action(async (opts) => {
|
|
2113
2213
|
const bundle = await useClient();
|
|
2114
2214
|
const started = spinner("Requesting device code");
|
|
2115
|
-
const provider = opts.provider ?? "
|
|
2215
|
+
const provider = opts.provider ?? "google";
|
|
2116
2216
|
const device = await bundle.api.startDeviceFlow(provider);
|
|
2117
2217
|
started.succeed();
|
|
2218
|
+
assertSafeVerificationUrl(device.verification_url);
|
|
2118
2219
|
printHuman("");
|
|
2119
2220
|
printHuman(c.bold("Confirm the code in your browser:"));
|
|
2120
2221
|
printHuman(" " + c.yellow(device.user_code));
|
|
@@ -2187,7 +2288,10 @@ function loginCommand() {
|
|
|
2187
2288
|
const cfg = await loadCliConfig();
|
|
2188
2289
|
await saveCliConfig({ ...cfg, username: me.username, sessionId: tokens.session_id });
|
|
2189
2290
|
printDual({
|
|
2190
|
-
|
|
2291
|
+
// session_id intentionally omitted: stdout in JSON mode flows
|
|
2292
|
+
// into scripts, CI logs, and agent transcripts. Sessions stay
|
|
2293
|
+
// enumerable via GET /auth/sessions with the stored token.
|
|
2294
|
+
json: { status: "logged_in", username: me.username },
|
|
2191
2295
|
human: () => printHuman(c.green(`
|
|
2192
2296
|
\u2713 Logged in as ${c.bold(me.username)}.`))
|
|
2193
2297
|
});
|
|
@@ -2328,12 +2432,21 @@ var TOOL_ALIASES = {
|
|
|
2328
2432
|
gemini: "gemini-cli"
|
|
2329
2433
|
};
|
|
2330
2434
|
function searchCommand() {
|
|
2331
|
-
return new Command3("search").description("search the Kordis registry").argument("[query...]", "keywords to match against title/description").option(
|
|
2435
|
+
return new Command3("search").description("search the Kordis registry").argument("[query...]", "keywords to match against title/description").option(
|
|
2436
|
+
"--type <type>",
|
|
2437
|
+
"filter by harness type (skill|mcp-server|project-rules|custom-agent|hook)"
|
|
2438
|
+
).option(
|
|
2332
2439
|
"--tool <tool>",
|
|
2333
2440
|
`filter by compatible tool \u2014 one of: ${TOOL_SLUGS.join("|")} (aliases: ${Object.entries(
|
|
2334
2441
|
TOOL_ALIASES
|
|
2335
2442
|
).map(([alias, canonical]) => `${alias}\u2192${canonical}`).join(", ")})`
|
|
2336
|
-
).option("--tier <tier>", "filter by portability tier (A|B|C|D)").option(
|
|
2443
|
+
).option("--tier <tier>", "filter by portability tier (A|B|C|D)").option(
|
|
2444
|
+
"--tag <slug>",
|
|
2445
|
+
"filter by tag (e.g. aws, fastapi, llm); tags also boost free-text ranking now"
|
|
2446
|
+
).option(
|
|
2447
|
+
"--author <username>",
|
|
2448
|
+
"filter to a single author (server returns exact total when set)"
|
|
2449
|
+
).option(
|
|
2337
2450
|
"--cursor <token>",
|
|
2338
2451
|
"pagination cursor \u2014 pass the next_cursor field from a prior search response to fetch the next page"
|
|
2339
2452
|
).option("--sort <mode>", `sort: ${SORT_MODES.join("|")}`).option("--trending", "list trending packages (overrides --sort)", false).option("--rising", "list rising packages by 7d acceleration (overrides --sort)", false).option("--window <w>", `time window for --trending: ${TIME_WINDOWS.join("|")}`, "24h").option("--limit <n>", "max results", (v) => parseInt(v, 10), 20).action(async (query, opts) => {
|
|
@@ -2372,6 +2485,7 @@ function searchCommand() {
|
|
|
2372
2485
|
type: opts.type ? [opts.type] : void 0,
|
|
2373
2486
|
tool: resolvedTool ? [resolvedTool] : void 0,
|
|
2374
2487
|
tier: opts.tier,
|
|
2488
|
+
...opts.tag ? { tag: opts.tag } : {},
|
|
2375
2489
|
...opts.author ? { author: opts.author } : {},
|
|
2376
2490
|
...opts.cursor ? { cursor: opts.cursor } : {},
|
|
2377
2491
|
sort: opts.sort ?? "relevance",
|
|
@@ -2379,7 +2493,7 @@ function searchCommand() {
|
|
|
2379
2493
|
});
|
|
2380
2494
|
}
|
|
2381
2495
|
const queryProvided = query.length > 0;
|
|
2382
|
-
const emptyHint = opts.type ? `no packages of type "${opts.type}" match. Try without --type, or list everything: kordis search --type ${opts.type} --sort recent --limit 25` : queryProvided ? "
|
|
2496
|
+
const emptyHint = opts.type ? `no packages of type "${opts.type}" match. Try without --type, or list everything: kordis search --type ${opts.type} --sort recent --limit 25` : queryProvided ? "narrow with `--type skill|mcp-server|project-rules|custom-agent|hook|judgment-pattern` or `--tag <topic>` (e.g. aws, fastapi), broaden the keywords, or browse `--sort trending`" : 'try keywords (e.g. `kordis search "topic"`), `--tag <topic>`, or `kordis search --trending`';
|
|
2383
2497
|
printDual({
|
|
2384
2498
|
json: res.results.length === 0 ? { ...res, hint: emptyHint } : res,
|
|
2385
2499
|
human: () => {
|
|
@@ -2415,10 +2529,10 @@ function searchCommand() {
|
|
|
2415
2529
|
if (res.next_cursor) {
|
|
2416
2530
|
printHuman(c.gray(`(more available \u2014 pass --cursor ${res.next_cursor})`));
|
|
2417
2531
|
}
|
|
2418
|
-
if (queryProvided && !opts.type && res.results.length > 3) {
|
|
2532
|
+
if (queryProvided && !opts.type && !opts.tag && res.results.length > 3) {
|
|
2419
2533
|
printHuman(
|
|
2420
2534
|
c.gray(
|
|
2421
|
-
" \u2192 narrow
|
|
2535
|
+
" \u2192 narrow with `--type skill|mcp-server|project-rules|custom-agent|hook|judgment-pattern` or `--tag <topic>`"
|
|
2422
2536
|
)
|
|
2423
2537
|
);
|
|
2424
2538
|
}
|
|
@@ -2505,8 +2619,8 @@ function normaliseDep(entry, idx) {
|
|
|
2505
2619
|
);
|
|
2506
2620
|
}
|
|
2507
2621
|
return {
|
|
2508
|
-
full_slug: m[1],
|
|
2509
|
-
version_range: (m[4] ?? "*").trim()
|
|
2622
|
+
full_slug: FullSlugSchema.parse(m[1]),
|
|
2623
|
+
version_range: SemverRangeSchema.parse((m[4] ?? "*").trim())
|
|
2510
2624
|
};
|
|
2511
2625
|
}
|
|
2512
2626
|
if (typeof entry === "object" && entry !== null) {
|
|
@@ -2682,6 +2796,53 @@ var HookFrontmatterSchema = z18.object({
|
|
|
2682
2796
|
license: z18.string().max(64).optional()
|
|
2683
2797
|
}).strict();
|
|
2684
2798
|
|
|
2799
|
+
// ../../packages/harness-schema/src/types/judgment-pattern.ts
|
|
2800
|
+
init_src();
|
|
2801
|
+
import { z as z19 } from "zod";
|
|
2802
|
+
var FailureModeSchema = z19.object({
|
|
2803
|
+
symptom: z19.string().min(1).max(600).describe("the observable sign the judgment was ignored"),
|
|
2804
|
+
correction: z19.string().min(1).max(1200).describe("what to do once the symptom shows")
|
|
2805
|
+
}).strict();
|
|
2806
|
+
var AdaptHintsSchema = z19.object({
|
|
2807
|
+
/** Parts that transplant verbatim into ANY harness — the invariant core. */
|
|
2808
|
+
universal: z19.array(z19.string().min(1).max(400)).max(20).optional(),
|
|
2809
|
+
/** Parts the adapting agent must re-express in the host's stack/conventions. */
|
|
2810
|
+
context_specific: z19.array(z19.string().min(1).max(400)).max(20).optional()
|
|
2811
|
+
}).strict();
|
|
2812
|
+
var JudgmentPatternFrontmatterSchema = z19.object({
|
|
2813
|
+
name: z19.string().min(1).max(64).regex(
|
|
2814
|
+
/^[a-z0-9]+(-[a-z0-9]+)*$/,
|
|
2815
|
+
'name must be lowercase-with-hyphens, e.g. "additive-only-migrations"'
|
|
2816
|
+
),
|
|
2817
|
+
description: z19.string().min(1).max(2048),
|
|
2818
|
+
version: SemverSchema.optional(),
|
|
2819
|
+
title: z19.string().min(1).max(200).optional(),
|
|
2820
|
+
/** The recurring situation / trap this judgment addresses. */
|
|
2821
|
+
problem: z19.string().min(1).max(4e3),
|
|
2822
|
+
/**
|
|
2823
|
+
* The judgment itself — free-form Markdown. The agent is the interpreter,
|
|
2824
|
+
* so this stays unstructured prose (decision tree, heuristics, rationale).
|
|
2825
|
+
* This is the part that gets transplanted.
|
|
2826
|
+
*/
|
|
2827
|
+
decision_logic: z19.string().min(1).max(2e4),
|
|
2828
|
+
/** Triggers — when this judgment should fire. At least one. */
|
|
2829
|
+
when_to_apply: z19.array(z19.string().min(1).max(400)).min(1).max(12),
|
|
2830
|
+
/** Counter-triggers — when applying it would be wrong. */
|
|
2831
|
+
when_not_to_apply: z19.array(z19.string().min(1).max(400)).max(12).optional(),
|
|
2832
|
+
/** How it fails in practice, and how to recover — the hard-won part. */
|
|
2833
|
+
failure_modes: z19.array(FailureModeSchema).max(12).optional(),
|
|
2834
|
+
/** Transplant steering — the field unique to judgment-pattern. */
|
|
2835
|
+
adapt_hints: AdaptHintsSchema.optional(),
|
|
2836
|
+
/** Where this judgment came from (project, incident, paper). Provenance. */
|
|
2837
|
+
origin: z19.string().min(1).max(600).optional(),
|
|
2838
|
+
/** Proof it works (anti-spam lever for AI review). */
|
|
2839
|
+
evidence: z19.string().min(1).max(2e3).optional(),
|
|
2840
|
+
// Kordis-forwarded cross-type fields (kept minimal, like skill).
|
|
2841
|
+
tags: z19.array(TagSchema).max(16).optional(),
|
|
2842
|
+
tools: z19.array(ToolSlugSchema).optional(),
|
|
2843
|
+
license: z19.string().max(64).optional()
|
|
2844
|
+
}).strict();
|
|
2845
|
+
|
|
2685
2846
|
// ../../packages/harness-schema/src/validator.ts
|
|
2686
2847
|
function flag(code, message, extra = {}) {
|
|
2687
2848
|
return { code, message, ...extra };
|
|
@@ -2713,7 +2874,7 @@ function validatePackageSource(source) {
|
|
|
2713
2874
|
return earlyExit(
|
|
2714
2875
|
flag("missing_required_frontmatter", "`type` is required in frontmatter", {
|
|
2715
2876
|
field: "type",
|
|
2716
|
-
hint: "Add type: skill (or one of: mcp-server, project-rules, custom-agent, hook)"
|
|
2877
|
+
hint: "Add type: skill (or one of: mcp-server, project-rules, custom-agent, hook, judgment-pattern)"
|
|
2717
2878
|
})
|
|
2718
2879
|
);
|
|
2719
2880
|
}
|
|
@@ -2722,7 +2883,7 @@ function validatePackageSource(source) {
|
|
|
2722
2883
|
return earlyExit(
|
|
2723
2884
|
flag("unknown_type", `type "${typeValue}" is not a recognised Harness type`, {
|
|
2724
2885
|
field: "type",
|
|
2725
|
-
hint: "Allowed types: skill, mcp-server, project-rules, custom-agent, hook"
|
|
2886
|
+
hint: "Allowed types: skill, mcp-server, project-rules, custom-agent, hook, judgment-pattern"
|
|
2726
2887
|
})
|
|
2727
2888
|
);
|
|
2728
2889
|
}
|
|
@@ -2731,7 +2892,7 @@ function validatePackageSource(source) {
|
|
|
2731
2892
|
flags.push(
|
|
2732
2893
|
flag("type_not_phase1", `type "${type}" is planned for Phase 2+ and is not yet accepted`, {
|
|
2733
2894
|
field: "type",
|
|
2734
|
-
hint: "Use one of the Phase 1 types: skill, mcp-server, project-rules, custom-agent, hook"
|
|
2895
|
+
hint: "Use one of the Phase 1 types: skill, mcp-server, project-rules, custom-agent, hook, judgment-pattern"
|
|
2735
2896
|
})
|
|
2736
2897
|
);
|
|
2737
2898
|
return earlyExit(null, { flags, body });
|
|
@@ -2920,6 +3081,11 @@ function dispatchValidation(type, raw) {
|
|
|
2920
3081
|
if (!r.success) return zodFail(r.error.issues);
|
|
2921
3082
|
return { ok: true, frontmatter: { type, data: r.data }, flags: [] };
|
|
2922
3083
|
}
|
|
3084
|
+
case "judgment-pattern": {
|
|
3085
|
+
const r = JudgmentPatternFrontmatterSchema.safeParse(raw);
|
|
3086
|
+
if (!r.success) return zodFail(r.error.issues);
|
|
3087
|
+
return { ok: true, frontmatter: { type, data: r.data }, flags: [] };
|
|
3088
|
+
}
|
|
2923
3089
|
default:
|
|
2924
3090
|
return {
|
|
2925
3091
|
ok: false,
|
|
@@ -2927,7 +3093,7 @@ function dispatchValidation(type, raw) {
|
|
|
2927
3093
|
flags: [
|
|
2928
3094
|
flag("type_not_phase1", `type "${type}" not accepted in Phase 1`, {
|
|
2929
3095
|
field: "type",
|
|
2930
|
-
hint: "Use one of the Phase 1 types: skill, mcp-server, project-rules, custom-agent, hook"
|
|
3096
|
+
hint: "Use one of the Phase 1 types: skill, mcp-server, project-rules, custom-agent, hook, judgment-pattern"
|
|
2931
3097
|
})
|
|
2932
3098
|
]
|
|
2933
3099
|
};
|
|
@@ -2962,7 +3128,7 @@ var ASSET_MAX_COUNT = 50;
|
|
|
2962
3128
|
var SAFE_PATH_RE = /^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/;
|
|
2963
3129
|
function isAssetPathSafe(p) {
|
|
2964
3130
|
if (!SAFE_PATH_RE.test(p)) return false;
|
|
2965
|
-
return
|
|
3131
|
+
return p.split("/").every((seg2) => seg2.length > 0 && seg2 !== "." && seg2 !== "..");
|
|
2966
3132
|
}
|
|
2967
3133
|
function validateAssets(assets) {
|
|
2968
3134
|
const flags = [];
|
|
@@ -2987,14 +3153,211 @@ function validateAssets(assets) {
|
|
|
2987
3153
|
return { ok: flags.length === 0, flags };
|
|
2988
3154
|
}
|
|
2989
3155
|
|
|
3156
|
+
// ../../packages/harness-schema/src/sections.ts
|
|
3157
|
+
var JUDGMENT_FIELDS = [
|
|
3158
|
+
{ key: "problem", label: "Problem" },
|
|
3159
|
+
{ key: "decision_logic", label: "Decision logic" },
|
|
3160
|
+
{ key: "when_to_apply", label: "When to apply" },
|
|
3161
|
+
{ key: "when_not_to_apply", label: "When NOT to apply" },
|
|
3162
|
+
{ key: "failure_modes", label: "Failure modes" },
|
|
3163
|
+
{ key: "adapt_hints", label: "Adapt hints" },
|
|
3164
|
+
{ key: "origin", label: "Origin" },
|
|
3165
|
+
{ key: "evidence", label: "Evidence" }
|
|
3166
|
+
];
|
|
3167
|
+
function slugifyHeading(h) {
|
|
3168
|
+
return h.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3169
|
+
}
|
|
3170
|
+
function fieldToMarkdown(key, value) {
|
|
3171
|
+
if (value === null || value === void 0) return null;
|
|
3172
|
+
if (typeof value === "string") return value.trim() || null;
|
|
3173
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
3174
|
+
if (Array.isArray(value)) {
|
|
3175
|
+
if (value.length === 0) return null;
|
|
3176
|
+
if (key === "failure_modes") {
|
|
3177
|
+
return value.map((fm) => {
|
|
3178
|
+
const o = fm ?? {};
|
|
3179
|
+
const symptom = typeof o["symptom"] === "string" ? o["symptom"] : "";
|
|
3180
|
+
const correction = typeof o["correction"] === "string" ? o["correction"] : "";
|
|
3181
|
+
return `- **${symptom}**
|
|
3182
|
+
\u2192 ${correction}`;
|
|
3183
|
+
}).join("\n");
|
|
3184
|
+
}
|
|
3185
|
+
return value.map((v) => `- ${String(v)}`).join("\n");
|
|
3186
|
+
}
|
|
3187
|
+
if (typeof value === "object") {
|
|
3188
|
+
const o = value;
|
|
3189
|
+
const parts = [];
|
|
3190
|
+
if (Array.isArray(o["universal"]) && o["universal"].length > 0) {
|
|
3191
|
+
parts.push("**Universal**\n" + o["universal"].map((v) => `- ${String(v)}`).join("\n"));
|
|
3192
|
+
}
|
|
3193
|
+
if (Array.isArray(o["context_specific"]) && o["context_specific"].length > 0) {
|
|
3194
|
+
parts.push(
|
|
3195
|
+
"**Context-specific**\n" + o["context_specific"].map((v) => `- ${String(v)}`).join("\n")
|
|
3196
|
+
);
|
|
3197
|
+
}
|
|
3198
|
+
return parts.length > 0 ? parts.join("\n\n") : null;
|
|
3199
|
+
}
|
|
3200
|
+
return null;
|
|
3201
|
+
}
|
|
3202
|
+
function splitByHeadings(body) {
|
|
3203
|
+
const lines = body.split("\n");
|
|
3204
|
+
const sections = [];
|
|
3205
|
+
const intro = [];
|
|
3206
|
+
let cur = null;
|
|
3207
|
+
const flush = () => {
|
|
3208
|
+
if (cur) {
|
|
3209
|
+
sections.push({
|
|
3210
|
+
name: slugifyHeading(cur.label) || `section-${sections.length + 1}`,
|
|
3211
|
+
label: cur.label,
|
|
3212
|
+
body: cur.body.join("\n").trim()
|
|
3213
|
+
});
|
|
3214
|
+
}
|
|
3215
|
+
};
|
|
3216
|
+
for (const line of lines) {
|
|
3217
|
+
const m = /^(#{2,3})\s+(.+?)\s*$/.exec(line);
|
|
3218
|
+
if (m) {
|
|
3219
|
+
flush();
|
|
3220
|
+
cur = { label: m[2], body: [] };
|
|
3221
|
+
} else if (cur) {
|
|
3222
|
+
cur.body.push(line);
|
|
3223
|
+
} else {
|
|
3224
|
+
intro.push(line);
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
flush();
|
|
3228
|
+
return { intro: intro.join("\n").trim(), sections };
|
|
3229
|
+
}
|
|
3230
|
+
function outlineOf(source) {
|
|
3231
|
+
let fm = null;
|
|
3232
|
+
let body = source;
|
|
3233
|
+
try {
|
|
3234
|
+
const parsed = parseFrontmatter(source);
|
|
3235
|
+
fm = parsed.frontmatter;
|
|
3236
|
+
body = parsed.body;
|
|
3237
|
+
} catch {
|
|
3238
|
+
}
|
|
3239
|
+
const description = fm && typeof fm["description"] === "string" ? fm["description"] : null;
|
|
3240
|
+
const type = fm && typeof fm["type"] === "string" ? fm["type"] : null;
|
|
3241
|
+
if (type === "judgment-pattern" && fm) {
|
|
3242
|
+
const sections2 = [];
|
|
3243
|
+
for (const { key, label } of JUDGMENT_FIELDS) {
|
|
3244
|
+
const md = fieldToMarkdown(key, fm[key]);
|
|
3245
|
+
if (md !== null) sections2.push({ name: key, label, body: md });
|
|
3246
|
+
}
|
|
3247
|
+
return { description, type, intro: body.trim(), sections: sections2 };
|
|
3248
|
+
}
|
|
3249
|
+
const { intro, sections } = splitByHeadings(body);
|
|
3250
|
+
return { description, type, intro, sections };
|
|
3251
|
+
}
|
|
3252
|
+
function sectionOf(source, name) {
|
|
3253
|
+
const q = name.trim().toLowerCase();
|
|
3254
|
+
const { sections } = outlineOf(source);
|
|
3255
|
+
return sections.find((s) => s.name === q) ?? sections.find((s) => s.label.toLowerCase() === q) ?? sections.find((s) => s.name === slugifyHeading(name)) ?? null;
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
// src/lib/parse-username.ts
|
|
3259
|
+
init_src();
|
|
3260
|
+
var SORT_KEYWORDS = ["latest", "popular", "recent", "updated", "stars", "downloads"];
|
|
3261
|
+
function usernameError(message, hint) {
|
|
3262
|
+
const e = new Error(message);
|
|
3263
|
+
e.hint = hint;
|
|
3264
|
+
return e;
|
|
3265
|
+
}
|
|
3266
|
+
function parseUsername(raw) {
|
|
3267
|
+
if (SORT_KEYWORDS.includes(raw.toLowerCase())) {
|
|
3268
|
+
throw usernameError(
|
|
3269
|
+
`"${raw}" is a sort keyword \u2014 did you mean \`--sort ${raw}\`? Username arguments must follow the Kordis username shape (lowercase alphanumeric, hyphens allowed).`,
|
|
3270
|
+
`Pass it as the sort flag instead, e.g. \`--sort ${raw}\`, or supply an actual username argument.`
|
|
3271
|
+
);
|
|
3272
|
+
}
|
|
3273
|
+
const parsed = UsernameSchema.safeParse(raw);
|
|
3274
|
+
if (!parsed.success) {
|
|
3275
|
+
throw usernameError(
|
|
3276
|
+
`invalid username "${raw}" \u2014 must be 3-32 chars, lowercase alphanumeric with single hyphens (e.g. "alice", "my-team")`,
|
|
3277
|
+
"Usernames are lowercase alphanumeric with single hyphens. Browse existing users with `kordis search` or look one up with `kordis profile <username>`."
|
|
3278
|
+
);
|
|
3279
|
+
}
|
|
3280
|
+
return parsed.data;
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
// src/lib/parse-slug-version.ts
|
|
3284
|
+
var SUFFIX_CHARS = /[\^~*]/;
|
|
3285
|
+
function slugError(message, hint) {
|
|
3286
|
+
const e = new Error(message);
|
|
3287
|
+
e.hint = hint;
|
|
3288
|
+
return e;
|
|
3289
|
+
}
|
|
3290
|
+
function parseSlugAtVersion(raw) {
|
|
3291
|
+
if (!raw.includes("/")) {
|
|
3292
|
+
let username2;
|
|
3293
|
+
try {
|
|
3294
|
+
username2 = parseUsername(raw);
|
|
3295
|
+
} catch {
|
|
3296
|
+
username2 = void 0;
|
|
3297
|
+
}
|
|
3298
|
+
if (username2) {
|
|
3299
|
+
throw slugError(
|
|
3300
|
+
`"${raw}" looks like a username, not a package`,
|
|
3301
|
+
`Try \`kordis profile ${username2}\` for user details, or pass a full slug like "{username}/{slug}".`
|
|
3302
|
+
);
|
|
3303
|
+
}
|
|
3304
|
+
throw slugError(
|
|
3305
|
+
`"${raw}" is missing the "{username}/{slug}" form`,
|
|
3306
|
+
'Use the form "{username}/{slug}", e.g. "alice/json-pretty".'
|
|
3307
|
+
);
|
|
3308
|
+
}
|
|
3309
|
+
const atIdx = raw.indexOf("@");
|
|
3310
|
+
const slugPart = atIdx === -1 ? raw : raw.slice(0, atIdx);
|
|
3311
|
+
const versionPart = atIdx === -1 ? "" : raw.slice(atIdx + 1);
|
|
3312
|
+
const [username, slug] = slugPart.split("/");
|
|
3313
|
+
if (!username || !slug) {
|
|
3314
|
+
throw slugError(
|
|
3315
|
+
'full_slug must be "{username}/{slug}"',
|
|
3316
|
+
'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.'
|
|
3317
|
+
);
|
|
3318
|
+
}
|
|
3319
|
+
if (atIdx !== -1 && versionPart.length === 0) {
|
|
3320
|
+
throw slugError(
|
|
3321
|
+
`"${raw}" has a trailing "@" with no version`,
|
|
3322
|
+
`Drop the "@" or append a version/range, e.g. "${slugPart}@1.0.0" or "${slugPart}@^1".`
|
|
3323
|
+
);
|
|
3324
|
+
}
|
|
3325
|
+
const out = {
|
|
3326
|
+
fullSlug: slugPart,
|
|
3327
|
+
username,
|
|
3328
|
+
slug
|
|
3329
|
+
};
|
|
3330
|
+
if (versionPart.length > 0) {
|
|
3331
|
+
if (SUFFIX_CHARS.test(versionPart)) {
|
|
3332
|
+
out.range = versionPart;
|
|
3333
|
+
} else {
|
|
3334
|
+
out.version = versionPart;
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
return out;
|
|
3338
|
+
}
|
|
3339
|
+
|
|
2990
3340
|
// src/commands/push.ts
|
|
3341
|
+
var collect = (val, acc) => acc.concat(val);
|
|
3342
|
+
function toLineage(raws, kind) {
|
|
3343
|
+
return (raws ?? []).map((raw) => {
|
|
3344
|
+
const p = parseSlugAtVersion(raw);
|
|
3345
|
+
return {
|
|
3346
|
+
to_full_slug: `${p.username}/${p.slug}`,
|
|
3347
|
+
...p.version ? { to_version: p.version } : {},
|
|
3348
|
+
kind
|
|
3349
|
+
};
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
2991
3352
|
var ASSET_MAX_BYTES = 2 * 1024 * 1024;
|
|
2992
3353
|
var BUNDLE_MAX_BYTES = 5 * 1024 * 1024;
|
|
2993
3354
|
function pushCommand() {
|
|
2994
|
-
return new Command4("push").description("publish a new package or a new version").argument("<path>", "SKILL.md file, .skill ZIP, or skill directory").option("--bump <strategy>", "patch|minor|major (update only)", "patch").option(
|
|
2995
|
-
"--
|
|
2996
|
-
|
|
2997
|
-
|
|
3355
|
+
return new Command4("push").description("publish a new package or a new version").argument("<path>", "SKILL.md file, .skill ZIP, or skill directory").option("--bump <strategy>", "patch|minor|major (update only)", "patch").option("--slug <slug>", 'override slug; accepts "user/slug" to point at a specific package').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").option(
|
|
3356
|
+
"--adapted-from <slug>",
|
|
3357
|
+
"source this package was adapted from (repeatable); records lineage",
|
|
3358
|
+
collect,
|
|
3359
|
+
[]
|
|
3360
|
+
).option("--forked-from <slug>", "source this package was forked from (repeatable)", collect, []).option("--absorbs <slug>", "source this package absorbs (repeatable)", collect, []).option("--remixes <slug>", "source this package remixes (repeatable)", collect, []).action(
|
|
2998
3361
|
async (path2, opts) => {
|
|
2999
3362
|
const bundle = await useClient();
|
|
3000
3363
|
const abs = resolve(process.cwd(), path2);
|
|
@@ -3076,6 +3439,12 @@ function pushCommand() {
|
|
|
3076
3439
|
const spin = spinner(
|
|
3077
3440
|
opts.dryRun ? "Validating" : targetFullSlug ? `Publishing update to ${targetFullSlug}` : "Publishing"
|
|
3078
3441
|
);
|
|
3442
|
+
const lineage = [
|
|
3443
|
+
...toLineage(opts.adaptedFrom, "adaptation"),
|
|
3444
|
+
...toLineage(opts.forkedFrom, "fork"),
|
|
3445
|
+
...toLineage(opts.absorbs, "absorption"),
|
|
3446
|
+
...toLineage(opts.remixes, "remix")
|
|
3447
|
+
];
|
|
3079
3448
|
const res = await bundle.authedRequest(
|
|
3080
3449
|
(api) => api.publish(
|
|
3081
3450
|
{
|
|
@@ -3086,6 +3455,7 @@ function pushCommand() {
|
|
|
3086
3455
|
target_full_slug: targetFullSlug,
|
|
3087
3456
|
type: opts.type,
|
|
3088
3457
|
changelog: opts.changelog,
|
|
3458
|
+
lineage: lineage.length > 0 ? lineage : void 0,
|
|
3089
3459
|
dry_run: !!opts.dryRun
|
|
3090
3460
|
},
|
|
3091
3461
|
{
|
|
@@ -3104,6 +3474,21 @@ function pushCommand() {
|
|
|
3104
3474
|
printHuman(c.gray(` + ${assets.length} bundled file(s)`));
|
|
3105
3475
|
}
|
|
3106
3476
|
printHuman(` ${c.cyan(res.url)}`);
|
|
3477
|
+
if (res.lineage) {
|
|
3478
|
+
printHuman(c.gray(` lineage: ${res.lineage.recorded} edge(s) recorded`));
|
|
3479
|
+
if (res.lineage.skipped.length > 0) {
|
|
3480
|
+
printHuman(
|
|
3481
|
+
c.yellow(
|
|
3482
|
+
` ! lineage source(s) not found, skipped: ${res.lineage.skipped.join(", ")}`
|
|
3483
|
+
)
|
|
3484
|
+
);
|
|
3485
|
+
printHuman(
|
|
3486
|
+
c.gray(
|
|
3487
|
+
" Publish the source first or fix the slug, then re-push to record it."
|
|
3488
|
+
)
|
|
3489
|
+
);
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3107
3492
|
if (res.status === "pending") {
|
|
3108
3493
|
printHuman(
|
|
3109
3494
|
c.yellow(
|
|
@@ -3174,9 +3559,7 @@ async function runDryRun(args) {
|
|
|
3174
3559
|
);
|
|
3175
3560
|
}
|
|
3176
3561
|
if (assets.length > 0) printHuman(c.gray(` + ${assets.length} bundled file(s)`));
|
|
3177
|
-
printHuman(
|
|
3178
|
-
c.gray(" (AI review only runs on the real push; not performed here)")
|
|
3179
|
-
);
|
|
3562
|
+
printHuman(c.gray(" (AI review only runs on the real push; not performed here)"));
|
|
3180
3563
|
return;
|
|
3181
3564
|
}
|
|
3182
3565
|
printHuman(c.red(`\u2717 dry-run validation failed (${allFlags.length} flag(s))`));
|
|
@@ -3240,7 +3623,9 @@ async function bundleDirectory(dir2) {
|
|
|
3240
3623
|
await walkDir(dir2, "", all);
|
|
3241
3624
|
const skillEntry = all.find((f) => f.rel === "SKILL.md");
|
|
3242
3625
|
if (!skillEntry) {
|
|
3243
|
-
throw new Error(
|
|
3626
|
+
throw new Error(
|
|
3627
|
+
`No SKILL.md found in directory ${dir2}. Push a directory that has SKILL.md at its root.`
|
|
3628
|
+
);
|
|
3244
3629
|
}
|
|
3245
3630
|
const content = await readFile6(skillEntry.abs, "utf8");
|
|
3246
3631
|
const assets = [];
|
|
@@ -3255,7 +3640,11 @@ async function bundleSkillZip(zipPath) {
|
|
|
3255
3640
|
const buf = await readFile6(zipPath);
|
|
3256
3641
|
const zip = await JSZip.loadAsync(buf);
|
|
3257
3642
|
const entries = Object.keys(zip.files).filter((k) => !zip.files[k].dir);
|
|
3258
|
-
if (entries.length === 0)
|
|
3643
|
+
if (entries.length === 0) {
|
|
3644
|
+
const e = new Error(".skill file is empty");
|
|
3645
|
+
e.hint = "The .skill archive contains no files. Re-export the .skill from skill-creator, or push the unpacked skill directory instead (the dir containing SKILL.md).";
|
|
3646
|
+
throw e;
|
|
3647
|
+
}
|
|
3259
3648
|
const firstParts = entries[0].split("/");
|
|
3260
3649
|
const prefix = firstParts.length > 1 ? firstParts[0] + "/" : "";
|
|
3261
3650
|
const stripped = entries.map((k) => ({
|
|
@@ -3264,7 +3653,9 @@ async function bundleSkillZip(zipPath) {
|
|
|
3264
3653
|
})).filter((f) => f.rel.length > 0 && !shouldExclude(f.rel, false));
|
|
3265
3654
|
const skillEntry = stripped.find((f) => f.rel === "SKILL.md");
|
|
3266
3655
|
if (!skillEntry) {
|
|
3267
|
-
|
|
3656
|
+
const e = new Error(`.skill ZIP has no SKILL.md at its root (prefix="${prefix}")`);
|
|
3657
|
+
e.hint = "SKILL.md must sit at <skill-name>/SKILL.md at the archive root. Re-package so the single top-level directory contains SKILL.md (skill-creator does this by default).";
|
|
3658
|
+
throw e;
|
|
3268
3659
|
}
|
|
3269
3660
|
const content = await zip.files[skillEntry.zipKey].async("string");
|
|
3270
3661
|
const assets = [];
|
|
@@ -3287,7 +3678,8 @@ async function bundleSkillZip(zipPath) {
|
|
|
3287
3678
|
// src/commands/pull.ts
|
|
3288
3679
|
import { Command as Command5 } from "commander";
|
|
3289
3680
|
import { mkdir as mkdir6, writeFile as writeFile6 } from "fs/promises";
|
|
3290
|
-
import { dirname as dirname2, join as join9 } from "path";
|
|
3681
|
+
import { dirname as dirname2, join as join9, resolve as resolve2, sep as sep2 } from "path";
|
|
3682
|
+
init_src();
|
|
3291
3683
|
|
|
3292
3684
|
// ../../packages/translators/src/skill/identity.ts
|
|
3293
3685
|
function buildSkillFile(input, outputPath) {
|
|
@@ -3522,7 +3914,7 @@ var mcpToCodex = (input) => {
|
|
|
3522
3914
|
var mcpToContinue = (input) => {
|
|
3523
3915
|
const name = getName(input);
|
|
3524
3916
|
const t = getTransport(input);
|
|
3525
|
-
const lines = [`name: ${name}`];
|
|
3917
|
+
const lines = [`name: ${JSON.stringify(name)}`];
|
|
3526
3918
|
if (t.type === "stdio") {
|
|
3527
3919
|
lines.push("type: stdio", `command: ${JSON.stringify(t.command)}`);
|
|
3528
3920
|
if (t.args && t.args.length > 0) {
|
|
@@ -3531,13 +3923,13 @@ var mcpToContinue = (input) => {
|
|
|
3531
3923
|
}
|
|
3532
3924
|
if (t.env && Object.keys(t.env).length > 0) {
|
|
3533
3925
|
lines.push("env:");
|
|
3534
|
-
for (const [k, v] of Object.entries(t.env)) lines.push(` ${k}: ${JSON.stringify(v)}`);
|
|
3926
|
+
for (const [k, v] of Object.entries(t.env)) lines.push(` ${JSON.stringify(k)}: ${JSON.stringify(v)}`);
|
|
3535
3927
|
}
|
|
3536
3928
|
} else {
|
|
3537
3929
|
lines.push(`type: ${t.type}`, `url: ${JSON.stringify(t.url)}`);
|
|
3538
3930
|
if (t.headers && Object.keys(t.headers).length > 0) {
|
|
3539
3931
|
lines.push("headers:");
|
|
3540
|
-
for (const [k, v] of Object.entries(t.headers)) lines.push(` ${k}: ${JSON.stringify(v)}`);
|
|
3932
|
+
for (const [k, v] of Object.entries(t.headers)) lines.push(` ${JSON.stringify(k)}: ${JSON.stringify(v)}`);
|
|
3541
3933
|
}
|
|
3542
3934
|
}
|
|
3543
3935
|
return {
|
|
@@ -3581,9 +3973,13 @@ var customAgentToClaude = (input) => {
|
|
|
3581
3973
|
const context = typeof fm["context"] === "string" ? fm["context"] : null;
|
|
3582
3974
|
const allowed = Array.isArray(fm["allowed_tools"]) ? fm["allowed_tools"] : [];
|
|
3583
3975
|
const disallowed = Array.isArray(fm["disallowed_tools"]) ? fm["disallowed_tools"] : [];
|
|
3584
|
-
const lines = [
|
|
3976
|
+
const lines = [
|
|
3977
|
+
"---",
|
|
3978
|
+
`name: ${JSON.stringify(name)}`,
|
|
3979
|
+
`description: ${JSON.stringify(description)}`
|
|
3980
|
+
];
|
|
3585
3981
|
if (model) lines.push(`model: ${JSON.stringify(model)}`);
|
|
3586
|
-
if (context) lines.push(`context: ${context}`);
|
|
3982
|
+
if (context) lines.push(`context: ${JSON.stringify(context)}`);
|
|
3587
3983
|
if (allowed.length > 0) {
|
|
3588
3984
|
lines.push("allowed_tools:");
|
|
3589
3985
|
for (const t of allowed) lines.push(` - ${JSON.stringify(t)}`);
|
|
@@ -3666,6 +4062,70 @@ var hookCanonical = (input) => {
|
|
|
3666
4062
|
return res;
|
|
3667
4063
|
};
|
|
3668
4064
|
|
|
4065
|
+
// ../../packages/translators/src/judgment-pattern/identity.ts
|
|
4066
|
+
function yamlScalar(v) {
|
|
4067
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
4068
|
+
return JSON.stringify(String(v));
|
|
4069
|
+
}
|
|
4070
|
+
function emitNode(key, value, indent) {
|
|
4071
|
+
if (value === void 0 || value === null) return [];
|
|
4072
|
+
const pad = " ".repeat(indent);
|
|
4073
|
+
if (Array.isArray(value)) {
|
|
4074
|
+
if (value.length === 0) return [];
|
|
4075
|
+
const lines = [`${pad}${key}:`];
|
|
4076
|
+
for (const item of value) {
|
|
4077
|
+
if (item !== null && typeof item === "object" && !Array.isArray(item)) {
|
|
4078
|
+
const entries = Object.entries(item).filter(
|
|
4079
|
+
([, v]) => v !== void 0 && v !== null
|
|
4080
|
+
);
|
|
4081
|
+
if (entries.length === 0) {
|
|
4082
|
+
lines.push(`${pad} - {}`);
|
|
4083
|
+
continue;
|
|
4084
|
+
}
|
|
4085
|
+
entries.forEach(([k, v], i) => {
|
|
4086
|
+
const prefix = i === 0 ? `${pad} - ` : `${pad} `;
|
|
4087
|
+
lines.push(`${prefix}${k}: ${yamlScalar(v)}`);
|
|
4088
|
+
});
|
|
4089
|
+
} else {
|
|
4090
|
+
lines.push(`${pad} - ${yamlScalar(item)}`);
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
return lines;
|
|
4094
|
+
}
|
|
4095
|
+
if (typeof value === "object") {
|
|
4096
|
+
const entries = Object.entries(value).filter(
|
|
4097
|
+
([, v]) => v !== void 0 && v !== null
|
|
4098
|
+
);
|
|
4099
|
+
if (entries.length === 0) return [];
|
|
4100
|
+
const lines = [`${pad}${key}:`];
|
|
4101
|
+
for (const [k, v] of entries) lines.push(...emitNode(k, v, indent + 1));
|
|
4102
|
+
return lines;
|
|
4103
|
+
}
|
|
4104
|
+
if (typeof value === "string" && value.includes("\n")) {
|
|
4105
|
+
const lines = [`${pad}${key}: |`];
|
|
4106
|
+
for (const ln of value.replace(/\n+$/, "").split("\n")) lines.push(`${pad} ${ln}`);
|
|
4107
|
+
return lines;
|
|
4108
|
+
}
|
|
4109
|
+
return [`${pad}${key}: ${yamlScalar(value)}`];
|
|
4110
|
+
}
|
|
4111
|
+
function buildJudgmentFile(input, outputPath) {
|
|
4112
|
+
const fm = input.frontmatter["version"] === void 0 && input.version ? { ...input.frontmatter, version: input.version } : input.frontmatter;
|
|
4113
|
+
const name = typeof fm["name"] === "string" ? fm["name"] : input.fullSlug.split("/")[1] ?? input.fullSlug;
|
|
4114
|
+
const lines = ["---", "type: judgment-pattern"];
|
|
4115
|
+
for (const [k, v] of Object.entries(fm)) lines.push(...emitNode(k, v, 0));
|
|
4116
|
+
lines.push("---");
|
|
4117
|
+
const body = input.body.trim();
|
|
4118
|
+
const content = lines.join("\n") + (body.length > 0 ? "\n\n" + body + "\n" : "\n");
|
|
4119
|
+
const resolvedPath = outputPath.replace("{name}", String(name));
|
|
4120
|
+
return {
|
|
4121
|
+
files: [{ path: resolvedPath, content, mergeable: false, format: "markdown" }],
|
|
4122
|
+
notes: [],
|
|
4123
|
+
format: "kordis-judgment-pattern"
|
|
4124
|
+
};
|
|
4125
|
+
}
|
|
4126
|
+
var judgmentToClaude = (input) => buildJudgmentFile(input, ".claude/judgments/{name}.md");
|
|
4127
|
+
var judgmentCanonical = (input) => buildJudgmentFile(input, `kordis/judgment-patterns/${input.fullSlug.replace("/", "__")}.md`);
|
|
4128
|
+
|
|
3669
4129
|
// ../../packages/translators/src/registry.ts
|
|
3670
4130
|
var REGISTRY = {
|
|
3671
4131
|
skill: {
|
|
@@ -3707,6 +4167,13 @@ var REGISTRY = {
|
|
|
3707
4167
|
hook: {
|
|
3708
4168
|
canonical: hookCanonical,
|
|
3709
4169
|
"claude-code": hookToClaude
|
|
4170
|
+
},
|
|
4171
|
+
// Judgment is canonical Markdown — no per-tool wiring, so both targets are
|
|
4172
|
+
// identity. `read`/`adapt` (JIT) is the primary path; this is the local-copy
|
|
4173
|
+
// fallback. `toolsForType` will report only 'claude-code' as compatible.
|
|
4174
|
+
"judgment-pattern": {
|
|
4175
|
+
canonical: judgmentCanonical,
|
|
4176
|
+
"claude-code": judgmentToClaude
|
|
3710
4177
|
}
|
|
3711
4178
|
};
|
|
3712
4179
|
REGISTRY["project-rules"]["gemini-cli"] = projectRulesToGemini;
|
|
@@ -3753,7 +4220,10 @@ async function readLockfile() {
|
|
|
3753
4220
|
}
|
|
3754
4221
|
async function writeLockfile(lf) {
|
|
3755
4222
|
await mkdir5(kordisHome2(), { recursive: true });
|
|
3756
|
-
await writeFile5(lockfilePath(), JSON.stringify(lf, null, 2) + "\n",
|
|
4223
|
+
await writeFile5(lockfilePath(), JSON.stringify(lf, null, 2) + "\n", {
|
|
4224
|
+
encoding: "utf8",
|
|
4225
|
+
mode: 384
|
|
4226
|
+
});
|
|
3757
4227
|
}
|
|
3758
4228
|
async function recordInstalled(fullSlug, entry) {
|
|
3759
4229
|
const lf = await readLockfile();
|
|
@@ -3761,85 +4231,14 @@ async function recordInstalled(fullSlug, entry) {
|
|
|
3761
4231
|
await writeLockfile(lf);
|
|
3762
4232
|
}
|
|
3763
4233
|
|
|
3764
|
-
// src/lib/parse-username.ts
|
|
3765
|
-
init_src();
|
|
3766
|
-
var SORT_KEYWORDS = ["latest", "popular", "recent", "updated", "stars", "downloads"];
|
|
3767
|
-
function parseUsername(raw) {
|
|
3768
|
-
if (SORT_KEYWORDS.includes(raw.toLowerCase())) {
|
|
3769
|
-
throw new Error(
|
|
3770
|
-
`"${raw}" is a sort keyword \u2014 did you mean \`--sort ${raw}\`? Username arguments must follow the Kordis username shape (lowercase alphanumeric, hyphens allowed).`
|
|
3771
|
-
);
|
|
3772
|
-
}
|
|
3773
|
-
const parsed = UsernameSchema.safeParse(raw);
|
|
3774
|
-
if (!parsed.success) {
|
|
3775
|
-
throw new Error(
|
|
3776
|
-
`invalid username "${raw}" \u2014 must be 3-32 chars, lowercase alphanumeric with single hyphens (e.g. "alice", "my-team")`
|
|
3777
|
-
);
|
|
3778
|
-
}
|
|
3779
|
-
return parsed.data;
|
|
3780
|
-
}
|
|
3781
|
-
|
|
3782
|
-
// src/lib/parse-slug-version.ts
|
|
3783
|
-
var SUFFIX_CHARS = /[\^~*]/;
|
|
3784
|
-
function slugError(message, hint) {
|
|
3785
|
-
const e = new Error(message);
|
|
3786
|
-
e.hint = hint;
|
|
3787
|
-
return e;
|
|
3788
|
-
}
|
|
3789
|
-
function parseSlugAtVersion(raw) {
|
|
3790
|
-
if (!raw.includes("/")) {
|
|
3791
|
-
let username2;
|
|
3792
|
-
try {
|
|
3793
|
-
username2 = parseUsername(raw);
|
|
3794
|
-
} catch {
|
|
3795
|
-
username2 = void 0;
|
|
3796
|
-
}
|
|
3797
|
-
if (username2) {
|
|
3798
|
-
throw slugError(
|
|
3799
|
-
`"${raw}" looks like a username, not a package`,
|
|
3800
|
-
`Try \`kordis profile ${username2}\` for user details, or pass a full slug like "{username}/{slug}".`
|
|
3801
|
-
);
|
|
3802
|
-
}
|
|
3803
|
-
throw slugError(
|
|
3804
|
-
`"${raw}" is missing the "{username}/{slug}" form`,
|
|
3805
|
-
'Use the form "{username}/{slug}", e.g. "alice/json-pretty".'
|
|
3806
|
-
);
|
|
3807
|
-
}
|
|
3808
|
-
const atIdx = raw.indexOf("@");
|
|
3809
|
-
const slugPart = atIdx === -1 ? raw : raw.slice(0, atIdx);
|
|
3810
|
-
const versionPart = atIdx === -1 ? "" : raw.slice(atIdx + 1);
|
|
3811
|
-
const [username, slug] = slugPart.split("/");
|
|
3812
|
-
if (!username || !slug) {
|
|
3813
|
-
throw slugError(
|
|
3814
|
-
'full_slug must be "{username}/{slug}"',
|
|
3815
|
-
'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.'
|
|
3816
|
-
);
|
|
3817
|
-
}
|
|
3818
|
-
if (atIdx !== -1 && versionPart.length === 0) {
|
|
3819
|
-
throw slugError(
|
|
3820
|
-
`"${raw}" has a trailing "@" with no version`,
|
|
3821
|
-
`Drop the "@" or append a version/range, e.g. "${slugPart}@1.0.0" or "${slugPart}@^1".`
|
|
3822
|
-
);
|
|
3823
|
-
}
|
|
3824
|
-
const out = {
|
|
3825
|
-
fullSlug: slugPart,
|
|
3826
|
-
username,
|
|
3827
|
-
slug
|
|
3828
|
-
};
|
|
3829
|
-
if (versionPart.length > 0) {
|
|
3830
|
-
if (SUFFIX_CHARS.test(versionPart)) {
|
|
3831
|
-
out.range = versionPart;
|
|
3832
|
-
} else {
|
|
3833
|
-
out.version = versionPart;
|
|
3834
|
-
}
|
|
3835
|
-
}
|
|
3836
|
-
return out;
|
|
3837
|
-
}
|
|
3838
|
-
|
|
3839
4234
|
// src/commands/pull.ts
|
|
3840
4235
|
async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
|
|
3841
4236
|
const [username, slug] = fullSlug.split("/");
|
|
3842
|
-
if (!username || !slug)
|
|
4237
|
+
if (!username || !slug) {
|
|
4238
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
4239
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
4240
|
+
throw e;
|
|
4241
|
+
}
|
|
3843
4242
|
const bundle = await useClient();
|
|
3844
4243
|
const pullRes = await bundle.api.pull(username, slug, {
|
|
3845
4244
|
...opts.version ? { version: opts.version } : {},
|
|
@@ -3854,7 +4253,9 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
|
|
|
3854
4253
|
});
|
|
3855
4254
|
const parsed = validatePackageSource(contentRes.content);
|
|
3856
4255
|
if (!parsed.ok || !parsed.type || !parsed.frontmatter) {
|
|
3857
|
-
|
|
4256
|
+
const e = new Error("package stored content is not currently valid");
|
|
4257
|
+
e.hint = `This version's stored bytes no longer pass validation. Try another version via \`kordis versions ${fullSlug}\` (then pull a specific \`@<version>\`), or re-push a corrected version if you authored it.`;
|
|
4258
|
+
throw e;
|
|
3858
4259
|
}
|
|
3859
4260
|
const written = [];
|
|
3860
4261
|
const notes = [];
|
|
@@ -3869,7 +4270,7 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
|
|
|
3869
4270
|
assets: contentRes.assets
|
|
3870
4271
|
});
|
|
3871
4272
|
for (const f of result.files) {
|
|
3872
|
-
const abs =
|
|
4273
|
+
const abs = safeInstallPath(installRoot, f.path);
|
|
3873
4274
|
await mkdir6(dirname2(abs), { recursive: true });
|
|
3874
4275
|
await writeEmittedFile(abs, f.content, f.encoding);
|
|
3875
4276
|
written.push(abs);
|
|
@@ -3889,7 +4290,7 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
|
|
|
3889
4290
|
`Tool "${format}" is not supported for type "${parsed.type}"; installed canonical form instead.`
|
|
3890
4291
|
);
|
|
3891
4292
|
for (const f of result.files) {
|
|
3892
|
-
const abs =
|
|
4293
|
+
const abs = safeInstallPath(installRoot, f.path);
|
|
3893
4294
|
await mkdir6(dirname2(abs), { recursive: true });
|
|
3894
4295
|
await writeEmittedFile(abs, f.content, f.encoding);
|
|
3895
4296
|
written.push(abs);
|
|
@@ -3960,6 +4361,13 @@ function pullCommand() {
|
|
|
3960
4361
|
...opts.withRecommends ? result.plan?.recommended.map((n) => ({ ...n, kind: "recommended" })) ?? [] : []
|
|
3961
4362
|
];
|
|
3962
4363
|
for (const node of installPlan) {
|
|
4364
|
+
if (!FullSlugSchema.safeParse(node.full_slug).success) {
|
|
4365
|
+
const e = new Error(
|
|
4366
|
+
`registry returned a malformed dependency slug: ${JSON.stringify(node.full_slug)}`
|
|
4367
|
+
);
|
|
4368
|
+
e.hint = "Refusing to install it. Report this package to the registry operator.";
|
|
4369
|
+
throw e;
|
|
4370
|
+
}
|
|
3963
4371
|
const subSpin = spinner(
|
|
3964
4372
|
` \u21B3 ${node.kind === "required" ? "dep" : "rec"} ${node.full_slug}@${node.version}`
|
|
3965
4373
|
);
|
|
@@ -4052,6 +4460,27 @@ async function detectTool(root) {
|
|
|
4052
4460
|
}
|
|
4053
4461
|
return null;
|
|
4054
4462
|
}
|
|
4463
|
+
function safeInstallPath(installRoot, relPath) {
|
|
4464
|
+
const windowsAbsolute = /^[A-Za-z]:/.test(relPath) || relPath.startsWith("\\\\");
|
|
4465
|
+
if (relPath.startsWith("/") || windowsAbsolute || resolve2(relPath) === relPath) {
|
|
4466
|
+
const e = new Error(
|
|
4467
|
+
`refusing to write absolute path "${relPath}" outside the install root`
|
|
4468
|
+
);
|
|
4469
|
+
e.hint = "Package emitted an absolute file path. Re-push the package with relative paths only; report it to the author if you did not publish it.";
|
|
4470
|
+
throw e;
|
|
4471
|
+
}
|
|
4472
|
+
const abs = join9(installRoot, relPath);
|
|
4473
|
+
const resolvedRoot = resolve2(installRoot);
|
|
4474
|
+
const resolvedAbs = resolve2(abs);
|
|
4475
|
+
if (resolvedAbs !== resolvedRoot && !resolvedAbs.startsWith(resolvedRoot + sep2)) {
|
|
4476
|
+
const e = new Error(
|
|
4477
|
+
`refusing to write "${relPath}" \u2014 resolves outside the install root`
|
|
4478
|
+
);
|
|
4479
|
+
e.hint = "Package tried to write outside the install directory (path traversal). Re-push the package with relative in-tree paths only; report it to the author if you did not publish it.";
|
|
4480
|
+
throw e;
|
|
4481
|
+
}
|
|
4482
|
+
return abs;
|
|
4483
|
+
}
|
|
4055
4484
|
async function writeEmittedFile(abs, content, encoding) {
|
|
4056
4485
|
if (encoding === "base64") {
|
|
4057
4486
|
await writeFile6(abs, Buffer.from(content, "base64"));
|
|
@@ -4063,7 +4492,7 @@ async function writeEmittedFile(abs, content, encoding) {
|
|
|
4063
4492
|
// src/commands/read.ts
|
|
4064
4493
|
import { Command as Command6 } from "commander";
|
|
4065
4494
|
function readCommand() {
|
|
4066
|
-
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("--pin <semver>", "pin to an exact version (overrides @suffix)").action(
|
|
4495
|
+
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("--pin <semver>", "pin to an exact version (overrides @suffix)").option("--outline", "print only the section map (skeleton) \u2014 cheap on context", false).option("--section <name>", "print only one section (see names with --outline)").action(
|
|
4067
4496
|
async (rawSlug, opts) => {
|
|
4068
4497
|
const parsed = parseSlugAtVersion(rawSlug);
|
|
4069
4498
|
const pin = opts.pin ?? parsed.version ?? parsed.range;
|
|
@@ -4071,11 +4500,7 @@ function readCommand() {
|
|
|
4071
4500
|
const bundle = await useClient();
|
|
4072
4501
|
let resolvedVersion;
|
|
4073
4502
|
if (pin && pinIsRange) {
|
|
4074
|
-
const resolved = await bundle.api.resolveVersion(
|
|
4075
|
-
parsed.username,
|
|
4076
|
-
parsed.slug,
|
|
4077
|
-
pin
|
|
4078
|
-
);
|
|
4503
|
+
const resolved = await bundle.api.resolveVersion(parsed.username, parsed.slug, pin);
|
|
4079
4504
|
resolvedVersion = resolved.resolved_version;
|
|
4080
4505
|
}
|
|
4081
4506
|
const versionForRead = resolvedVersion ?? pin;
|
|
@@ -4083,6 +4508,79 @@ function readCommand() {
|
|
|
4083
4508
|
allow_pending: opts.allowPending,
|
|
4084
4509
|
...versionForRead ? { version: versionForRead } : {}
|
|
4085
4510
|
});
|
|
4511
|
+
if (opts.outline) {
|
|
4512
|
+
const o = outlineOf(res.body);
|
|
4513
|
+
if (isJsonMode()) {
|
|
4514
|
+
printJson({
|
|
4515
|
+
full_slug: res.full_slug,
|
|
4516
|
+
version: res.version,
|
|
4517
|
+
type: res.type,
|
|
4518
|
+
tier: res.tier,
|
|
4519
|
+
trust_tier: res.trust_tier ?? null,
|
|
4520
|
+
description: o.description,
|
|
4521
|
+
sections: o.sections.map((s) => ({ name: s.name, label: s.label })),
|
|
4522
|
+
bundled_assets: res.bundled_assets ?? []
|
|
4523
|
+
});
|
|
4524
|
+
return;
|
|
4525
|
+
}
|
|
4526
|
+
process.stdout.write(
|
|
4527
|
+
`${c.bold(res.full_slug)}${c.gray("@")}${res.version} ${c.gray("\xB7")} ${res.type} (tier ${res.tier})
|
|
4528
|
+
`
|
|
4529
|
+
);
|
|
4530
|
+
if (o.description) process.stdout.write(`${o.description}
|
|
4531
|
+
`);
|
|
4532
|
+
process.stdout.write("\n");
|
|
4533
|
+
if (o.sections.length > 0) {
|
|
4534
|
+
process.stdout.write(
|
|
4535
|
+
`Sections \u2014 read one with ${c.cyan(`kordis read ${res.full_slug} --section <name>`)}:
|
|
4536
|
+
`
|
|
4537
|
+
);
|
|
4538
|
+
for (const s of o.sections) {
|
|
4539
|
+
process.stdout.write(` ${c.gray("-")} ${s.name}
|
|
4540
|
+
`);
|
|
4541
|
+
}
|
|
4542
|
+
} else {
|
|
4543
|
+
process.stdout.write(
|
|
4544
|
+
`No sub-sections \u2014 read the whole body with ${c.cyan(`kordis read ${res.full_slug}`)}.
|
|
4545
|
+
`
|
|
4546
|
+
);
|
|
4547
|
+
}
|
|
4548
|
+
const assets = res.bundled_assets ?? [];
|
|
4549
|
+
if (assets.length > 0) {
|
|
4550
|
+
process.stdout.write(
|
|
4551
|
+
`
|
|
4552
|
+
Bundled files \u2014 fetch with ${c.cyan(`kordis pull ${res.full_slug}`)}:
|
|
4553
|
+
`
|
|
4554
|
+
);
|
|
4555
|
+
for (const p of assets) process.stdout.write(` ${c.gray("-")} ${p}
|
|
4556
|
+
`);
|
|
4557
|
+
}
|
|
4558
|
+
return;
|
|
4559
|
+
}
|
|
4560
|
+
if (opts.section) {
|
|
4561
|
+
const sec = sectionOf(res.body, opts.section);
|
|
4562
|
+
if (!sec) {
|
|
4563
|
+
const names = outlineOf(res.body).sections.map((s) => s.name).join(", ");
|
|
4564
|
+
process.stderr.write(
|
|
4565
|
+
`${c.red("\u2717")} section "${opts.section}" not found. Available: ${names || "(none \u2014 this package has no sub-sections)"}
|
|
4566
|
+
See the map with ${c.cyan(`kordis read ${res.full_slug} --outline`)}.
|
|
4567
|
+
`
|
|
4568
|
+
);
|
|
4569
|
+
process.exitCode = 2;
|
|
4570
|
+
return;
|
|
4571
|
+
}
|
|
4572
|
+
if (isJsonMode()) {
|
|
4573
|
+
printJson({ full_slug: res.full_slug, version: res.version, section: sec });
|
|
4574
|
+
return;
|
|
4575
|
+
}
|
|
4576
|
+
process.stdout.write(sec.body);
|
|
4577
|
+
if (!sec.body.endsWith("\n")) process.stdout.write("\n");
|
|
4578
|
+
process.stderr.write(
|
|
4579
|
+
`${c.gray("\u2014")} ${c.bold(res.full_slug)}${c.gray("@")}${res.version} ${c.gray("\xA7")} ${sec.label} ${c.gray("\xB7")} ${c.cyan(res.attribution)}
|
|
4580
|
+
`
|
|
4581
|
+
);
|
|
4582
|
+
return;
|
|
4583
|
+
}
|
|
4086
4584
|
if (isJsonMode()) {
|
|
4087
4585
|
printJson(res);
|
|
4088
4586
|
return;
|
|
@@ -4101,8 +4599,9 @@ function readCommand() {
|
|
|
4101
4599
|
}
|
|
4102
4600
|
}
|
|
4103
4601
|
const resolvedNote = pinIsRange && resolvedVersion ? ` ${c.gray("(resolved")} ${c.gray("@" + pin)} ${c.gray("\u2192")} ${resolvedVersion}${c.gray(")")}` : "";
|
|
4602
|
+
const trustNote = res.trust_tier ? ` ${c.gray("\xB7")} trust: ${res.trust_tier}` : "";
|
|
4104
4603
|
process.stderr.write(
|
|
4105
|
-
`${c.gray("\u2014")} ${c.bold(res.full_slug)}${c.gray("@")}${res.version}${resolvedNote} ${c.gray("\xB7")} ${res.type} (tier ${res.tier}) ${c.gray("\xB7")} ${c.cyan(res.attribution)}
|
|
4604
|
+
`${c.gray("\u2014")} ${c.bold(res.full_slug)}${c.gray("@")}${res.version}${resolvedNote} ${c.gray("\xB7")} ${res.type} (tier ${res.tier})${trustNote} ${c.gray("\xB7")} ${c.cyan(res.attribution)}
|
|
4106
4605
|
`
|
|
4107
4606
|
);
|
|
4108
4607
|
printHuman("");
|
|
@@ -4110,11 +4609,175 @@ function readCommand() {
|
|
|
4110
4609
|
);
|
|
4111
4610
|
}
|
|
4112
4611
|
|
|
4612
|
+
// src/commands/adapt.ts
|
|
4613
|
+
import { Command as Command7 } from "commander";
|
|
4614
|
+
|
|
4615
|
+
// src/context/harness-reader.ts
|
|
4616
|
+
import { access as access3, readFile as readFile8 } from "fs/promises";
|
|
4617
|
+
import { homedir as homedir8 } from "os";
|
|
4618
|
+
import { join as join10 } from "path";
|
|
4619
|
+
var PER_FILE_CAP = 16 * 1024;
|
|
4620
|
+
var TOTAL_CAP = 48 * 1024;
|
|
4621
|
+
async function pathExists3(p) {
|
|
4622
|
+
try {
|
|
4623
|
+
await access3(p);
|
|
4624
|
+
return true;
|
|
4625
|
+
} catch {
|
|
4626
|
+
return false;
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
function capContent(raw, cap) {
|
|
4630
|
+
if (Buffer.byteLength(raw, "utf8") <= cap) return { content: raw, truncated: false };
|
|
4631
|
+
const lines = raw.split("\n");
|
|
4632
|
+
let acc = 0;
|
|
4633
|
+
let lastHeadingIdx = -1;
|
|
4634
|
+
let cutIdx = lines.length;
|
|
4635
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4636
|
+
const line = lines[i] ?? "";
|
|
4637
|
+
const lineBytes = Buffer.byteLength(`${line}
|
|
4638
|
+
`, "utf8");
|
|
4639
|
+
if (acc + lineBytes > cap) {
|
|
4640
|
+
cutIdx = i;
|
|
4641
|
+
break;
|
|
4642
|
+
}
|
|
4643
|
+
acc += lineBytes;
|
|
4644
|
+
if (/^#{1,6}\s/.test(line)) lastHeadingIdx = i;
|
|
4645
|
+
}
|
|
4646
|
+
const cut = lastHeadingIdx > cutIdx / 2 ? lastHeadingIdx : cutIdx;
|
|
4647
|
+
const kept = lines.slice(0, cut).join("\n");
|
|
4648
|
+
return {
|
|
4649
|
+
content: `${kept}
|
|
4650
|
+
|
|
4651
|
+
[truncated \u2014 ${cut}/${lines.length} lines shown]`,
|
|
4652
|
+
truncated: true
|
|
4653
|
+
};
|
|
4654
|
+
}
|
|
4655
|
+
function extractHeadings(content) {
|
|
4656
|
+
return content.split("\n").filter((l) => /^#{2,3}\s/.test(l)).map((l) => l.replace(/^#{2,3}\s+/, "").trim()).slice(0, 40);
|
|
4657
|
+
}
|
|
4658
|
+
async function readHarnessContext(cwd = process.cwd(), home = homedir8()) {
|
|
4659
|
+
const candidates = [
|
|
4660
|
+
{ path: join10(cwd, "CLAUDE.md"), display: "CLAUDE.md", scope: "project" },
|
|
4661
|
+
{ path: join10(cwd, "AGENTS.md"), display: "AGENTS.md", scope: "project" },
|
|
4662
|
+
{ path: join10(cwd, "GEMINI.md"), display: "GEMINI.md", scope: "project" },
|
|
4663
|
+
{ path: join10(cwd, ".cursorrules"), display: ".cursorrules", scope: "project" },
|
|
4664
|
+
{
|
|
4665
|
+
path: join10(cwd, ".github", "copilot-instructions.md"),
|
|
4666
|
+
display: ".github/copilot-instructions.md",
|
|
4667
|
+
scope: "project"
|
|
4668
|
+
},
|
|
4669
|
+
{ path: join10(cwd, ".claude", "CLAUDE.md"), display: ".claude/CLAUDE.md", scope: "project" },
|
|
4670
|
+
{ path: join10(home, ".claude", "CLAUDE.md"), display: "~/.claude/CLAUDE.md", scope: "global" }
|
|
4671
|
+
];
|
|
4672
|
+
const files = [];
|
|
4673
|
+
let totalBytes = 0;
|
|
4674
|
+
for (const cand of candidates) {
|
|
4675
|
+
if (totalBytes >= TOTAL_CAP) break;
|
|
4676
|
+
if (!await pathExists3(cand.path)) continue;
|
|
4677
|
+
let raw;
|
|
4678
|
+
try {
|
|
4679
|
+
raw = await readFile8(cand.path, "utf8");
|
|
4680
|
+
} catch {
|
|
4681
|
+
continue;
|
|
4682
|
+
}
|
|
4683
|
+
const cap = Math.min(PER_FILE_CAP, TOTAL_CAP - totalBytes);
|
|
4684
|
+
const { content, truncated } = capContent(raw, cap);
|
|
4685
|
+
totalBytes += Buffer.byteLength(content, "utf8");
|
|
4686
|
+
files.push({
|
|
4687
|
+
path: cand.display,
|
|
4688
|
+
scope: cand.scope,
|
|
4689
|
+
content,
|
|
4690
|
+
truncated,
|
|
4691
|
+
headings: extractHeadings(content)
|
|
4692
|
+
});
|
|
4693
|
+
}
|
|
4694
|
+
return { files, found: files.length > 0 };
|
|
4695
|
+
}
|
|
4696
|
+
|
|
4697
|
+
// src/commands/adapt.ts
|
|
4698
|
+
var TRANSPLANT_INSTRUCTIONS = `1. Extract the *judgment* from \xA71 \u2014 the decision logic / discipline it encodes, not its surface wording.
|
|
4699
|
+
2. Fit it to the harness in \xA72: keep universal parts verbatim; re-express context-specific parts in THIS harness's own conventions, stack, and section structure. Never duplicate a rule the harness already has \u2014 reconcile instead.
|
|
4700
|
+
3. For EVERY proposed change, write a plain-language explanation of what changes and WHY. The deliverable is understanding transfer, not a paste.
|
|
4701
|
+
4. PROPOSE, do not apply. Present a reviewable diff/edit for the user to approve. Kordis wrote nothing to disk and neither should you without approval.
|
|
4702
|
+
5. Cite attribution (the "following \u2026" line below) in whatever you add.
|
|
4703
|
+
6. If the user approves and wants to share their variation back, offer to run \`kordis push --adapted-from <slug@version>\` so the lineage is recorded \u2014 do not do this silently.`;
|
|
4704
|
+
function renderBrief(res, harness) {
|
|
4705
|
+
const out = [];
|
|
4706
|
+
out.push("# TRANSPLANT BRIEF");
|
|
4707
|
+
out.push(
|
|
4708
|
+
"> You (the agent) fit the judgment below into the user's existing harness. Propose changes WITH explanations; do not apply them automatically."
|
|
4709
|
+
);
|
|
4710
|
+
out.push("");
|
|
4711
|
+
out.push(`## 1. THE JUDGMENT \u2014 ${res.full_slug}@${res.version} (${res.type}, tier ${res.tier})`);
|
|
4712
|
+
out.push("");
|
|
4713
|
+
out.push(res.body.trimEnd());
|
|
4714
|
+
out.push("");
|
|
4715
|
+
out.push("## 2. THE USER'S EXISTING HARNESS");
|
|
4716
|
+
if (!harness.found) {
|
|
4717
|
+
out.push("");
|
|
4718
|
+
out.push(
|
|
4719
|
+
"_No harness file found in this project or globally._ Propose a NEW minimal placement (e.g. create or extend `CLAUDE.md`) \u2014 do not invent structure that is not there."
|
|
4720
|
+
);
|
|
4721
|
+
} else {
|
|
4722
|
+
for (const f of harness.files) {
|
|
4723
|
+
out.push("");
|
|
4724
|
+
out.push(`### ${f.path} (${f.scope}${f.truncated ? ", truncated" : ""})`);
|
|
4725
|
+
if (f.headings.length > 0) out.push(`Sections: ${f.headings.join(" \xB7 ")}`);
|
|
4726
|
+
out.push("");
|
|
4727
|
+
out.push("```md");
|
|
4728
|
+
out.push(f.content.trimEnd());
|
|
4729
|
+
out.push("```");
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
out.push("");
|
|
4733
|
+
out.push("## 3. TRANSPLANT INSTRUCTIONS");
|
|
4734
|
+
out.push(TRANSPLANT_INSTRUCTIONS);
|
|
4735
|
+
out.push("");
|
|
4736
|
+
out.push(`\u2014 ${res.attribution}`);
|
|
4737
|
+
out.push("");
|
|
4738
|
+
return out.join("\n");
|
|
4739
|
+
}
|
|
4740
|
+
function adaptCommand() {
|
|
4741
|
+
return new Command7("adapt").description(
|
|
4742
|
+
"fit a judgment pattern to your existing harness (prints a transplant brief; writes nothing)"
|
|
4743
|
+
).argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").option("--allow-pending", "allow versions whose AI review has not passed", false).option("--pin <semver>", "pin to an exact version (overrides @suffix)").action(async (rawSlug, opts) => {
|
|
4744
|
+
const parsed = parseSlugAtVersion(rawSlug);
|
|
4745
|
+
const pin = opts.pin ?? parsed.version ?? parsed.range;
|
|
4746
|
+
const pinIsRange = pin ? /[\^~*]/.test(pin) : false;
|
|
4747
|
+
const bundle = await useClient();
|
|
4748
|
+
let resolvedVersion;
|
|
4749
|
+
if (pin && pinIsRange) {
|
|
4750
|
+
const resolved = await bundle.api.resolveVersion(parsed.username, parsed.slug, pin);
|
|
4751
|
+
resolvedVersion = resolved.resolved_version;
|
|
4752
|
+
}
|
|
4753
|
+
const versionForRead = resolvedVersion ?? pin;
|
|
4754
|
+
const res = await bundle.api.jitRead(parsed.username, parsed.slug, {
|
|
4755
|
+
allow_pending: opts.allowPending,
|
|
4756
|
+
...versionForRead ? { version: versionForRead } : {}
|
|
4757
|
+
});
|
|
4758
|
+
const harness = await readHarnessContext();
|
|
4759
|
+
if (isJsonMode()) {
|
|
4760
|
+
printJson({
|
|
4761
|
+
full_slug: res.full_slug,
|
|
4762
|
+
version: res.version,
|
|
4763
|
+
type: res.type,
|
|
4764
|
+
tier: res.tier,
|
|
4765
|
+
attribution: res.attribution,
|
|
4766
|
+
judgment_body: res.body,
|
|
4767
|
+
host_harness: harness,
|
|
4768
|
+
transplant_instructions: TRANSPLANT_INSTRUCTIONS
|
|
4769
|
+
});
|
|
4770
|
+
return;
|
|
4771
|
+
}
|
|
4772
|
+
process.stdout.write(renderBrief(res, harness));
|
|
4773
|
+
});
|
|
4774
|
+
}
|
|
4775
|
+
|
|
4113
4776
|
// src/commands/info.ts
|
|
4114
4777
|
init_src();
|
|
4115
|
-
import { Command as
|
|
4778
|
+
import { Command as Command8 } from "commander";
|
|
4116
4779
|
function infoCommand() {
|
|
4117
|
-
return new
|
|
4780
|
+
return new Command8("info").description("show details about a package").argument(
|
|
4118
4781
|
"<full_slug>",
|
|
4119
4782
|
"package identifier {username}/{slug} or {username}/{slug}@{version|range}"
|
|
4120
4783
|
).option("--pin <semver>", "pin to an exact version or range (overrides @suffix)").action(async (fullSlug, opts) => {
|
|
@@ -4178,9 +4841,9 @@ function infoCommand() {
|
|
|
4178
4841
|
}
|
|
4179
4842
|
|
|
4180
4843
|
// src/commands/logout.ts
|
|
4181
|
-
import { Command as
|
|
4844
|
+
import { Command as Command9 } from "commander";
|
|
4182
4845
|
function logoutCommand() {
|
|
4183
|
-
return new
|
|
4846
|
+
return new Command9("logout").description("revoke the current session and clear local credentials").action(async () => {
|
|
4184
4847
|
const bundle = await useClient();
|
|
4185
4848
|
if (!bundle.username) {
|
|
4186
4849
|
printHuman(c.yellow("Not logged in; nothing to do."));
|
|
@@ -4203,11 +4866,11 @@ function logoutCommand() {
|
|
|
4203
4866
|
}
|
|
4204
4867
|
|
|
4205
4868
|
// src/commands/setup.ts
|
|
4206
|
-
import { readFile as
|
|
4207
|
-
import { homedir as
|
|
4208
|
-
import { join as
|
|
4869
|
+
import { readFile as readFile9, writeFile as writeFile7, mkdir as mkdir7, access as access4 } from "fs/promises";
|
|
4870
|
+
import { homedir as homedir9 } from "os";
|
|
4871
|
+
import { join as join11, dirname as dirname3 } from "path";
|
|
4209
4872
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4210
|
-
import { Command as
|
|
4873
|
+
import { Command as Command10 } from "commander";
|
|
4211
4874
|
import prompts from "prompts";
|
|
4212
4875
|
var MARKER_START = "<!-- kordis-start -->";
|
|
4213
4876
|
var MARKER_END = "<!-- kordis-end -->";
|
|
@@ -4230,7 +4893,7 @@ var SKILL_TARGETS = [
|
|
|
4230
4893
|
];
|
|
4231
4894
|
async function exists(path2) {
|
|
4232
4895
|
try {
|
|
4233
|
-
await
|
|
4896
|
+
await access4(path2);
|
|
4234
4897
|
return true;
|
|
4235
4898
|
} catch {
|
|
4236
4899
|
return false;
|
|
@@ -4240,18 +4903,18 @@ function assetRoots() {
|
|
|
4240
4903
|
const __dir2 = dirname3(fileURLToPath2(import.meta.url));
|
|
4241
4904
|
return {
|
|
4242
4905
|
// Built binary: bin/kordis.js → assets/ is one level up
|
|
4243
|
-
bundled:
|
|
4906
|
+
bundled: join11(__dir2, "..", "assets"),
|
|
4244
4907
|
// Dev mode: src/commands/setup.ts → 4 levels up to repo root, then packages/skills/
|
|
4245
|
-
source:
|
|
4908
|
+
source: join11(__dir2, "..", "..", "..", "..", "packages", "skills")
|
|
4246
4909
|
};
|
|
4247
4910
|
}
|
|
4248
4911
|
async function pickAssetRoot() {
|
|
4249
4912
|
const { bundled, source } = assetRoots();
|
|
4250
|
-
return await exists(
|
|
4913
|
+
return await exists(join11(bundled, "KORDIS.md")) ? bundled : source;
|
|
4251
4914
|
}
|
|
4252
4915
|
async function loadHubContent() {
|
|
4253
4916
|
const root = await pickAssetRoot();
|
|
4254
|
-
const raw = await
|
|
4917
|
+
const raw = await readFile9(join11(root, "KORDIS.md"), "utf-8");
|
|
4255
4918
|
if (raw.startsWith("---")) {
|
|
4256
4919
|
const end = raw.indexOf("\n---", 4);
|
|
4257
4920
|
if (end !== -1) return raw.slice(end + 4).trimStart();
|
|
@@ -4260,7 +4923,7 @@ async function loadHubContent() {
|
|
|
4260
4923
|
}
|
|
4261
4924
|
async function loadSubSkillContent(name) {
|
|
4262
4925
|
const root = await pickAssetRoot();
|
|
4263
|
-
return
|
|
4926
|
+
return readFile9(join11(root, name, "SKILL.md"), "utf-8");
|
|
4264
4927
|
}
|
|
4265
4928
|
async function injectBlock(filePath, content, dryRun) {
|
|
4266
4929
|
const block = `${MARKER_START}
|
|
@@ -4269,7 +4932,7 @@ ${MARKER_END}`;
|
|
|
4269
4932
|
let existing = "";
|
|
4270
4933
|
let fileExists = true;
|
|
4271
4934
|
try {
|
|
4272
|
-
existing = await
|
|
4935
|
+
existing = await readFile9(filePath, "utf-8");
|
|
4273
4936
|
} catch {
|
|
4274
4937
|
fileExists = false;
|
|
4275
4938
|
}
|
|
@@ -4299,7 +4962,7 @@ ${MARKER_END}`;
|
|
|
4299
4962
|
async function copyBundledSkill(filePath, content, dryRun) {
|
|
4300
4963
|
let existing = null;
|
|
4301
4964
|
try {
|
|
4302
|
-
existing = await
|
|
4965
|
+
existing = await readFile9(filePath, "utf-8");
|
|
4303
4966
|
} catch {
|
|
4304
4967
|
}
|
|
4305
4968
|
if (existing === content) return "unchanged";
|
|
@@ -4310,14 +4973,14 @@ async function copyBundledSkill(filePath, content, dryRun) {
|
|
|
4310
4973
|
return existing === null ? "injected" : "updated";
|
|
4311
4974
|
}
|
|
4312
4975
|
function setupCommand() {
|
|
4313
|
-
return new
|
|
4976
|
+
return new Command10("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").option(
|
|
4314
4977
|
"--personalization <choice>",
|
|
4315
4978
|
"record opt-in non-interactively: 'on' | 'off' (skips the Y/n prompt; safe for agents that just received user consent in chat)"
|
|
4316
4979
|
).option(
|
|
4317
4980
|
"--error-telemetry <choice>",
|
|
4318
4981
|
"record opt-in non-interactively: 'on' | 'off' (skips the Y/n prompt; agent forwards consent received in chat)"
|
|
4319
4982
|
).action(async (opts) => {
|
|
4320
|
-
const home =
|
|
4983
|
+
const home = homedir9();
|
|
4321
4984
|
const dryRun = opts.dryRun ?? false;
|
|
4322
4985
|
if (opts.personalization !== void 0) {
|
|
4323
4986
|
const v = opts.personalization.toLowerCase();
|
|
@@ -4376,8 +5039,8 @@ function setupCommand() {
|
|
|
4376
5039
|
let anyFound = false;
|
|
4377
5040
|
const changedTools = /* @__PURE__ */ new Set();
|
|
4378
5041
|
for (const target of HUB_TARGETS) {
|
|
4379
|
-
const dirPath =
|
|
4380
|
-
const filePath =
|
|
5042
|
+
const dirPath = join11(home, target.dir);
|
|
5043
|
+
const filePath = join11(home, target.file);
|
|
4381
5044
|
if (!await exists(dirPath)) {
|
|
4382
5045
|
if (!opts.silent) {
|
|
4383
5046
|
printHuman(c.gray(` - ${target.tool.padEnd(12)} not installed, skipped`));
|
|
@@ -4404,10 +5067,10 @@ function setupCommand() {
|
|
|
4404
5067
|
}
|
|
4405
5068
|
}
|
|
4406
5069
|
for (const target of SKILL_TARGETS) {
|
|
4407
|
-
const dirPath =
|
|
5070
|
+
const dirPath = join11(home, target.dir);
|
|
4408
5071
|
if (!await exists(dirPath)) continue;
|
|
4409
5072
|
for (const subName of SUB_SKILLS) {
|
|
4410
|
-
const filePath =
|
|
5073
|
+
const filePath = join11(home, target.skillsDir, subName, "SKILL.md");
|
|
4411
5074
|
const dest = `~/${target.skillsDir}/${subName}/SKILL.md`;
|
|
4412
5075
|
try {
|
|
4413
5076
|
const result = await copyBundledSkill(filePath, subSkillContents[subName], dryRun);
|
|
@@ -4495,7 +5158,7 @@ function setupCommand() {
|
|
|
4495
5158
|
}
|
|
4496
5159
|
|
|
4497
5160
|
// src/commands/comment.ts
|
|
4498
|
-
import { Command as
|
|
5161
|
+
import { Command as Command11 } from "commander";
|
|
4499
5162
|
var KINDS = [
|
|
4500
5163
|
"comment",
|
|
4501
5164
|
"tip",
|
|
@@ -4506,7 +5169,7 @@ var KINDS = [
|
|
|
4506
5169
|
var LIST_SORTS = ["recent", "old"];
|
|
4507
5170
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4508
5171
|
function commentCommand() {
|
|
4509
|
-
return new
|
|
5172
|
+
return new Command11("comment").description("post a comment on a package").argument("<full_slug>", "package identifier {username}/{slug}").requiredOption("--body <text>", "comment body (1\u201310000 chars)").option("--kind <kind>", `top-level kind: ${KINDS.join("|")}`, "comment").option("--title <text>", "top-level post title (1\u2013140 chars)").action(
|
|
4510
5173
|
async (rawSlug, opts) => {
|
|
4511
5174
|
if (!KINDS.includes(opts.kind)) {
|
|
4512
5175
|
throw new Error(`--kind must be one of ${KINDS.join(", ")}`);
|
|
@@ -4542,14 +5205,16 @@ function commentCommand() {
|
|
|
4542
5205
|
);
|
|
4543
5206
|
}
|
|
4544
5207
|
function commentsCommand() {
|
|
4545
|
-
return new
|
|
5208
|
+
return new Command11("comments").description("list comments on a package").argument("<full_slug>", "package identifier {username}/{slug}").option("--limit <n>", "max top-level comments (1\u201350)", (v) => parseInt(v, 10), 20).option("--cursor <token>", "pagination cursor returned by a prior call as next_cursor").option(
|
|
4546
5209
|
"--kind <kind>",
|
|
4547
5210
|
`filter top-level posts by kind: ${KINDS.join("|")}`
|
|
4548
5211
|
).option("--sort <mode>", `sort: ${LIST_SORTS.join("|")} (default: recent)`).action(
|
|
4549
5212
|
async (rawSlug, opts) => {
|
|
4550
5213
|
const [username, slug] = rawSlug.split("/");
|
|
4551
5214
|
if (!username || !slug) {
|
|
4552
|
-
|
|
5215
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
5216
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
5217
|
+
throw e;
|
|
4553
5218
|
}
|
|
4554
5219
|
if (opts.kind && !KINDS.includes(opts.kind)) {
|
|
4555
5220
|
throw new Error(`--kind must be one of ${KINDS.join(", ")}`);
|
|
@@ -4564,14 +5229,13 @@ function commentsCommand() {
|
|
|
4564
5229
|
...opts.kind ? { kind: opts.kind } : {},
|
|
4565
5230
|
...opts.sort ? { sort: opts.sort } : {}
|
|
4566
5231
|
});
|
|
5232
|
+
const emptyHint = "no comments yet \u2014 be the first: kordis comment " + rawSlug + ' --body "..."';
|
|
4567
5233
|
printDual({
|
|
4568
|
-
json: res,
|
|
5234
|
+
json: res.results.length === 0 ? { ...res, hint: emptyHint } : res,
|
|
4569
5235
|
human: () => {
|
|
4570
5236
|
if (res.results.length === 0) {
|
|
4571
5237
|
printHuman(c.yellow("(no comments)"));
|
|
4572
|
-
printHuman(
|
|
4573
|
-
c.gray(" \u2192 be the first: kordis comment " + rawSlug + ' --body "..."')
|
|
4574
|
-
);
|
|
5238
|
+
printHuman(c.gray(" \u2192 " + emptyHint));
|
|
4575
5239
|
return;
|
|
4576
5240
|
}
|
|
4577
5241
|
printHuman(
|
|
@@ -4615,7 +5279,7 @@ function renderCommentNode(node, depth) {
|
|
|
4615
5279
|
}
|
|
4616
5280
|
}
|
|
4617
5281
|
function uncommentCommand() {
|
|
4618
|
-
return new
|
|
5282
|
+
return new Command11("uncomment").description("delete one of your comments by id").argument("<id>", "comment id (uuid v4)").action(async (id) => {
|
|
4619
5283
|
if (!UUID_RE.test(id)) {
|
|
4620
5284
|
throw new Error(
|
|
4621
5285
|
"comment id must be a uuid v4 \u2014 get it from `kordis comments <slug>` or the `id` field on `kordis comment`'s response"
|
|
@@ -4634,12 +5298,14 @@ function uncommentCommand() {
|
|
|
4634
5298
|
}
|
|
4635
5299
|
|
|
4636
5300
|
// src/commands/star.ts
|
|
4637
|
-
import { Command as
|
|
5301
|
+
import { Command as Command12 } from "commander";
|
|
4638
5302
|
function starCommand() {
|
|
4639
|
-
return new
|
|
5303
|
+
return new Command12("star").description("star a package").argument("<full_slug>", "package identifier {username}/{slug}").action(async (rawSlug) => {
|
|
4640
5304
|
const [username, slug] = rawSlug.split("/");
|
|
4641
5305
|
if (!username || !slug) {
|
|
4642
|
-
|
|
5306
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
5307
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
5308
|
+
throw e;
|
|
4643
5309
|
}
|
|
4644
5310
|
const bundle = await useClient();
|
|
4645
5311
|
requireAuth(bundle);
|
|
@@ -4654,10 +5320,12 @@ function starCommand() {
|
|
|
4654
5320
|
});
|
|
4655
5321
|
}
|
|
4656
5322
|
function unstarCommand() {
|
|
4657
|
-
return new
|
|
5323
|
+
return new Command12("unstar").description("remove your star from a package").argument("<full_slug>", "package identifier {username}/{slug}").action(async (rawSlug) => {
|
|
4658
5324
|
const [username, slug] = rawSlug.split("/");
|
|
4659
5325
|
if (!username || !slug) {
|
|
4660
|
-
|
|
5326
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
5327
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
5328
|
+
throw e;
|
|
4661
5329
|
}
|
|
4662
5330
|
const bundle = await useClient();
|
|
4663
5331
|
requireAuth(bundle);
|
|
@@ -4675,13 +5343,15 @@ function unstarCommand() {
|
|
|
4675
5343
|
}
|
|
4676
5344
|
|
|
4677
5345
|
// src/commands/delete.ts
|
|
4678
|
-
import { Command as
|
|
5346
|
+
import { Command as Command13 } from "commander";
|
|
4679
5347
|
import prompts2 from "prompts";
|
|
4680
5348
|
function deleteCommand() {
|
|
4681
|
-
return new
|
|
5349
|
+
return new Command13("delete").description("permanently delete a package you own").argument("<full_slug>", "package identifier {username}/{slug}").option("--force", "skip the confirmation prompt").action(async (fullSlug, opts) => {
|
|
4682
5350
|
const [username, slug] = fullSlug.split("/");
|
|
4683
5351
|
if (!username || !slug) {
|
|
4684
|
-
|
|
5352
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
5353
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
5354
|
+
throw e;
|
|
4685
5355
|
}
|
|
4686
5356
|
const bundle = await useClient();
|
|
4687
5357
|
const detail = await bundle.api.getPackage(username, slug);
|
|
@@ -4714,10 +5384,10 @@ function deleteCommand() {
|
|
|
4714
5384
|
}
|
|
4715
5385
|
|
|
4716
5386
|
// src/commands/yank.ts
|
|
4717
|
-
import { Command as
|
|
5387
|
+
import { Command as Command14 } from "commander";
|
|
4718
5388
|
import prompts3 from "prompts";
|
|
4719
5389
|
function yankCommand() {
|
|
4720
|
-
return new
|
|
5390
|
+
return new Command14("yank").description("soft-mark a specific version of your package as yanked").argument(
|
|
4721
5391
|
"<full_slug>",
|
|
4722
5392
|
'package identifier \u2014 bare slug yanks current version (e.g. "user/slug"), or pin: "user/slug@1.2.3"'
|
|
4723
5393
|
).option("--reason <text>", "optional reason shown to consumers (\u2264280 chars)").option("--force", "skip the confirmation prompt").action(
|
|
@@ -4798,9 +5468,9 @@ function yankCommand() {
|
|
|
4798
5468
|
}
|
|
4799
5469
|
|
|
4800
5470
|
// src/commands/unyank.ts
|
|
4801
|
-
import { Command as
|
|
5471
|
+
import { Command as Command15 } from "commander";
|
|
4802
5472
|
function unyankCommand() {
|
|
4803
|
-
return new
|
|
5473
|
+
return new Command15("unyank").description("reverse a yank on a specific version").argument(
|
|
4804
5474
|
"<full_slug>",
|
|
4805
5475
|
'package identifier with explicit version, e.g. "user/slug@1.2.3"'
|
|
4806
5476
|
).action(async (fullSlugArg) => {
|
|
@@ -4841,9 +5511,9 @@ function unyankCommand() {
|
|
|
4841
5511
|
}
|
|
4842
5512
|
|
|
4843
5513
|
// src/commands/list.ts
|
|
4844
|
-
import { Command as
|
|
5514
|
+
import { Command as Command16 } from "commander";
|
|
4845
5515
|
function listCommand() {
|
|
4846
|
-
return new
|
|
5516
|
+
return new Command16("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) => {
|
|
4847
5517
|
const bundle = await useClient();
|
|
4848
5518
|
if (rawUsername === void 0) {
|
|
4849
5519
|
requireAuth(bundle);
|
|
@@ -4889,11 +5559,13 @@ function listCommand() {
|
|
|
4889
5559
|
sort: publicSort,
|
|
4890
5560
|
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
4891
5561
|
});
|
|
5562
|
+
const emptyHint = `no public packages for ${username} \u2014 verify the username via \`kordis profile ${username}\`, or browse the registry via \`kordis search\``;
|
|
4892
5563
|
printDual({
|
|
4893
|
-
json: res,
|
|
5564
|
+
json: res.results.length === 0 ? { ...res, hint: emptyHint } : res,
|
|
4894
5565
|
human: () => {
|
|
4895
5566
|
if (res.results.length === 0) {
|
|
4896
5567
|
printHuman(c.yellow(`(no public packages for ${username})`));
|
|
5568
|
+
printHuman(c.gray(` \u2192 ${emptyHint}`));
|
|
4897
5569
|
return;
|
|
4898
5570
|
}
|
|
4899
5571
|
for (const p of res.results) {
|
|
@@ -4914,12 +5586,14 @@ function listCommand() {
|
|
|
4914
5586
|
}
|
|
4915
5587
|
|
|
4916
5588
|
// src/commands/versions.ts
|
|
4917
|
-
import { Command as
|
|
5589
|
+
import { Command as Command17 } from "commander";
|
|
4918
5590
|
function versionsCommand() {
|
|
4919
|
-
return new
|
|
5591
|
+
return new Command17("versions").description("list all versions of a package").argument("<full_slug>", "package identifier {username}/{slug}").action(async (fullSlug) => {
|
|
4920
5592
|
const [username, slug] = fullSlug.split("/");
|
|
4921
5593
|
if (!username || !slug) {
|
|
4922
|
-
|
|
5594
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
5595
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
5596
|
+
throw e;
|
|
4923
5597
|
}
|
|
4924
5598
|
const bundle = await useClient();
|
|
4925
5599
|
const spin = spinner(`Fetching versions for ${fullSlug}`);
|
|
@@ -4975,13 +5649,13 @@ function relativeTime(isoString) {
|
|
|
4975
5649
|
}
|
|
4976
5650
|
|
|
4977
5651
|
// src/commands/outdated.ts
|
|
4978
|
-
import { Command as
|
|
5652
|
+
import { Command as Command18 } from "commander";
|
|
4979
5653
|
import semver from "semver";
|
|
4980
5654
|
function effectiveRange(entry) {
|
|
4981
5655
|
return entry.requestedRange ?? `^${entry.version}`;
|
|
4982
5656
|
}
|
|
4983
5657
|
function outdatedCommand() {
|
|
4984
|
-
return new
|
|
5658
|
+
return new Command18("outdated").description("check installed packages for available updates").action(async () => {
|
|
4985
5659
|
const lf = await readLockfile();
|
|
4986
5660
|
const slugs = Object.keys(lf.packages);
|
|
4987
5661
|
if (slugs.length === 0) {
|
|
@@ -5079,13 +5753,13 @@ function outdatedCommand() {
|
|
|
5079
5753
|
}
|
|
5080
5754
|
|
|
5081
5755
|
// src/commands/update.ts
|
|
5082
|
-
import { Command as
|
|
5756
|
+
import { Command as Command19 } from "commander";
|
|
5083
5757
|
import semver2 from "semver";
|
|
5084
5758
|
function effectiveRange2(entry) {
|
|
5085
5759
|
return entry.requestedRange ?? `^${entry.version}`;
|
|
5086
5760
|
}
|
|
5087
5761
|
function updateCommand() {
|
|
5088
|
-
return new
|
|
5762
|
+
return new Command19("update").description("update installed packages to their latest versions").argument("[full_slug]", "package identifier {username}/{slug} (omit to update all)").option("--dry-run", "show what would be updated without doing it", false).action(async (fullSlugArg, opts) => {
|
|
5089
5763
|
const lf = await readLockfile();
|
|
5090
5764
|
let slugs = Object.keys(lf.packages);
|
|
5091
5765
|
if (slugs.length === 0) {
|
|
@@ -5219,9 +5893,9 @@ function updateCommand() {
|
|
|
5219
5893
|
}
|
|
5220
5894
|
|
|
5221
5895
|
// src/commands/diff.ts
|
|
5222
|
-
import { Command as
|
|
5896
|
+
import { Command as Command20 } from "commander";
|
|
5223
5897
|
function diffCommand() {
|
|
5224
|
-
return new
|
|
5898
|
+
return new Command20("diff").description("show changes between two versions of a package").argument("<full_slug>", "package identifier: {username}/{slug}, or {username}/{slug}@{from}..{to}").argument("[from]", "older version (omit to use second-latest)").argument("[to]", "newer version (omit to use latest)").action(async (fullSlugArg, fromArg, toArg) => {
|
|
5225
5899
|
let fullSlug = fullSlugArg;
|
|
5226
5900
|
let from = fromArg;
|
|
5227
5901
|
let to = toArg;
|
|
@@ -5233,7 +5907,9 @@ function diffCommand() {
|
|
|
5233
5907
|
}
|
|
5234
5908
|
const [username, slug] = fullSlug.split("/");
|
|
5235
5909
|
if (!username || !slug) {
|
|
5236
|
-
|
|
5910
|
+
const e = new Error('full_slug must be "{username}/{slug}"');
|
|
5911
|
+
e.hint = 'Pass both parts, e.g. "alice/json-pretty". Browse with `kordis search` if unsure.';
|
|
5912
|
+
throw e;
|
|
5237
5913
|
}
|
|
5238
5914
|
const bundle = await useClient();
|
|
5239
5915
|
if (!from || !to) {
|
|
@@ -5292,10 +5968,10 @@ function diffCommand() {
|
|
|
5292
5968
|
}
|
|
5293
5969
|
|
|
5294
5970
|
// src/commands/recommend.ts
|
|
5295
|
-
import { Command as
|
|
5971
|
+
import { Command as Command21 } from "commander";
|
|
5296
5972
|
import prompts4 from "prompts";
|
|
5297
5973
|
function recommendCommand() {
|
|
5298
|
-
return new
|
|
5974
|
+
return new Command21("recommend").description("show personalized package recommendations").option("--limit <n>", "max items to return (1-50)", (v) => parseInt(v, 10), 10).option("--verbose", "include score_breakdown per item", false).option("--no-context", "skip the X-Kordis-Context header for this call").action(async (opts) => {
|
|
5299
5975
|
const cfg = await loadCliConfig();
|
|
5300
5976
|
if (cfg.personalization === "undecided" && process.stdin.isTTY && !isJsonMode() && opts.context !== false) {
|
|
5301
5977
|
const { enable } = await prompts4({
|
|
@@ -5326,8 +6002,9 @@ function recommendCommand() {
|
|
|
5326
6002
|
spin.succeed();
|
|
5327
6003
|
await writeCache(res.items, res.generated_at);
|
|
5328
6004
|
await markSeen();
|
|
6005
|
+
const emptyHint = !res.personalized ? "personalization is OFF, so the server ranks on popularity alone \u2014 likely empty pool. Enable for stack-aware matches: `kordis setup --personalization on`, or broaden via `kordis search <topic>` / `kordis search --trending`." : "personalization is ON but no matches for your stack \u2014 registry may be sparse for this profile. Try `kordis search <topic>` or `kordis search --trending`.";
|
|
5329
6006
|
printDual({
|
|
5330
|
-
json: res,
|
|
6007
|
+
json: res.items.length === 0 ? { ...res, hint: emptyHint } : res,
|
|
5331
6008
|
human: () => {
|
|
5332
6009
|
if (res.items.length === 0) {
|
|
5333
6010
|
printHuman(c.yellow("(no recommendations available right now)"));
|
|
@@ -5403,9 +6080,9 @@ function renderItem(idx, it, verbose) {
|
|
|
5403
6080
|
}
|
|
5404
6081
|
|
|
5405
6082
|
// src/commands/profile.ts
|
|
5406
|
-
import { Command as
|
|
6083
|
+
import { Command as Command22 } from "commander";
|
|
5407
6084
|
function profileCommand() {
|
|
5408
|
-
return new
|
|
6085
|
+
return new Command22("profile").description("inspect a user\u2019s public profile").argument("<username>", "kordis username").action(async (raw) => {
|
|
5409
6086
|
const username = parseUsername(raw);
|
|
5410
6087
|
const bundle = await useClient();
|
|
5411
6088
|
const profile = await bundle.api.getUserProfile(username);
|
|
@@ -5440,21 +6117,22 @@ function profileCommand() {
|
|
|
5440
6117
|
}
|
|
5441
6118
|
|
|
5442
6119
|
// src/commands/stars.ts
|
|
5443
|
-
import { Command as
|
|
6120
|
+
import { Command as Command23 } from "commander";
|
|
5444
6121
|
function starsCommand() {
|
|
5445
|
-
return new
|
|
6122
|
+
return new Command23("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) => {
|
|
5446
6123
|
const username = parseUsername(raw);
|
|
5447
6124
|
const bundle = await useClient();
|
|
5448
6125
|
const res = await bundle.api.listUserStars(username, {
|
|
5449
6126
|
limit: opts.limit,
|
|
5450
6127
|
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
5451
6128
|
});
|
|
6129
|
+
const emptyHint = `no public stars for ${username} \u2014 they may have none, or keep them private`;
|
|
5452
6130
|
printDual({
|
|
5453
|
-
json: res,
|
|
6131
|
+
json: res.results.length === 0 ? { ...res, hint: emptyHint } : res,
|
|
5454
6132
|
human: () => {
|
|
5455
6133
|
if (res.results.length === 0) {
|
|
5456
6134
|
printHuman(c.yellow(`(no stars yet for ${username})`));
|
|
5457
|
-
printHuman(c.gray(` \u2192
|
|
6135
|
+
printHuman(c.gray(` \u2192 ${emptyHint}`));
|
|
5458
6136
|
return;
|
|
5459
6137
|
}
|
|
5460
6138
|
for (const p of res.results) {
|
|
@@ -5474,9 +6152,9 @@ function starsCommand() {
|
|
|
5474
6152
|
}
|
|
5475
6153
|
|
|
5476
6154
|
// src/commands/follow.ts
|
|
5477
|
-
import { Command as
|
|
6155
|
+
import { Command as Command24 } from "commander";
|
|
5478
6156
|
function followCommand() {
|
|
5479
|
-
return new
|
|
6157
|
+
return new Command24("follow").description("follow a kordis user").argument("<username>", "kordis username").action(async (raw) => {
|
|
5480
6158
|
const username = parseUsername(raw);
|
|
5481
6159
|
const bundle = await useClient();
|
|
5482
6160
|
requireAuth(bundle);
|
|
@@ -5491,7 +6169,7 @@ function followCommand() {
|
|
|
5491
6169
|
});
|
|
5492
6170
|
}
|
|
5493
6171
|
function unfollowCommand() {
|
|
5494
|
-
return new
|
|
6172
|
+
return new Command24("unfollow").description("stop following a kordis user").argument("<username>", "kordis username").action(async (raw) => {
|
|
5495
6173
|
const username = parseUsername(raw);
|
|
5496
6174
|
const bundle = await useClient();
|
|
5497
6175
|
requireAuth(bundle);
|
|
@@ -5507,9 +6185,9 @@ function unfollowCommand() {
|
|
|
5507
6185
|
}
|
|
5508
6186
|
|
|
5509
6187
|
// src/commands/followers.ts
|
|
5510
|
-
import { Command as
|
|
6188
|
+
import { Command as Command25 } from "commander";
|
|
5511
6189
|
function makeListing(name) {
|
|
5512
|
-
return new
|
|
6190
|
+
return new Command25(name).description(
|
|
5513
6191
|
name === "followers" ? "list users following <username>" : "list users that <username> follows"
|
|
5514
6192
|
).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) => {
|
|
5515
6193
|
const username = parseUsername(raw);
|
|
@@ -5519,12 +6197,14 @@ function makeListing(name) {
|
|
|
5519
6197
|
limit: opts.limit,
|
|
5520
6198
|
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
5521
6199
|
});
|
|
6200
|
+
const emptyHint = name === "followers" ? `no followers yet for ${username} \u2014 verify the username via \`kordis profile ${username}\`, or this account simply has no followers` : `${username} isn't following anyone yet \u2014 verify the username via \`kordis profile ${username}\``;
|
|
5522
6201
|
printDual({
|
|
5523
|
-
json: res,
|
|
6202
|
+
json: res.results.length === 0 ? { ...res, hint: emptyHint } : res,
|
|
5524
6203
|
human: () => {
|
|
5525
6204
|
if (res.results.length === 0) {
|
|
5526
6205
|
const noun = name === "followers" ? "followers yet" : "one yet";
|
|
5527
6206
|
printHuman(c.yellow(`(no ${noun} for ${username})`));
|
|
6207
|
+
printHuman(c.gray(` \u2192 ${emptyHint}`));
|
|
5528
6208
|
return;
|
|
5529
6209
|
}
|
|
5530
6210
|
for (const u of res.results) {
|
|
@@ -5554,10 +6234,12 @@ function followingCommand() {
|
|
|
5554
6234
|
|
|
5555
6235
|
// src/index.ts
|
|
5556
6236
|
function buildProgram() {
|
|
5557
|
-
const program = new
|
|
6237
|
+
const program = new Command26("kordis").version(VERSION).description("Kordis \u2014 registry for agentic-coding packages").option("--json", "emit JSON output to stdout (default: human)", false).hook("preAction", (thisCommand) => {
|
|
5558
6238
|
const opts = thisCommand.optsWithGlobals();
|
|
5559
6239
|
if (opts.json) setJsonMode(true);
|
|
5560
|
-
}).addHelpText(
|
|
6240
|
+
}).addHelpText(
|
|
6241
|
+
"after",
|
|
6242
|
+
`
|
|
5561
6243
|
Environment variables (see README.md "Privacy & environment variables"):
|
|
5562
6244
|
KORDIS_API_URL=<url> override registry endpoint (default: https://api.kordis.store)
|
|
5563
6245
|
KORDIS_HOME=<dir> override config + token fallback dir (default: ~/.kordis)
|
|
@@ -5566,7 +6248,8 @@ Environment variables (see README.md "Privacy & environment variables"):
|
|
|
5566
6248
|
KORDIS_NO_KEYCHAIN=1 force plaintext token storage at ~/.kordis/.tokens.json
|
|
5567
6249
|
(escape hatch for macOS keychain-prompt fatigue)
|
|
5568
6250
|
KORDIS_DEBUG=1 surface auto-finalize stage outcomes to stderr (diagnostic)
|
|
5569
|
-
`
|
|
6251
|
+
`
|
|
6252
|
+
);
|
|
5570
6253
|
program.addCommand(loginCommand());
|
|
5571
6254
|
program.addCommand(logoutCommand());
|
|
5572
6255
|
program.addCommand(whoamiCommand());
|
|
@@ -5574,6 +6257,7 @@ Environment variables (see README.md "Privacy & environment variables"):
|
|
|
5574
6257
|
program.addCommand(pushCommand());
|
|
5575
6258
|
program.addCommand(pullCommand());
|
|
5576
6259
|
program.addCommand(readCommand());
|
|
6260
|
+
program.addCommand(adaptCommand());
|
|
5577
6261
|
program.addCommand(infoCommand());
|
|
5578
6262
|
program.addCommand(commentCommand());
|
|
5579
6263
|
program.addCommand(commentsCommand());
|
|
@@ -5604,10 +6288,10 @@ async function main(argv) {
|
|
|
5604
6288
|
const cleaned = argv.filter((a, i) => !(a === "--" && i >= 2));
|
|
5605
6289
|
await program.parseAsync(cleaned);
|
|
5606
6290
|
} catch (err) {
|
|
5607
|
-
handleError(err);
|
|
6291
|
+
await handleError(err);
|
|
5608
6292
|
}
|
|
5609
6293
|
}
|
|
5610
|
-
function handleError(err) {
|
|
6294
|
+
async function handleError(err) {
|
|
5611
6295
|
if (err instanceof KordisApiError) {
|
|
5612
6296
|
if (isJsonMode()) {
|
|
5613
6297
|
printJson({
|
|
@@ -5654,7 +6338,7 @@ function handleError(err) {
|
|
|
5654
6338
|
printHuman(c.red(`\u2717 ${err.message}`));
|
|
5655
6339
|
for (const line of hint.split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
5656
6340
|
}
|
|
5657
|
-
|
|
6341
|
+
await reportError("network_unreachable", err);
|
|
5658
6342
|
process.exit(err.exitCode());
|
|
5659
6343
|
}
|
|
5660
6344
|
if (err instanceof Error) {
|
|
@@ -5674,7 +6358,7 @@ function handleError(err) {
|
|
|
5674
6358
|
for (const line of hintStr.split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
5675
6359
|
}
|
|
5676
6360
|
}
|
|
5677
|
-
|
|
6361
|
+
await reportError("unhandled_error", err);
|
|
5678
6362
|
process.exit(1);
|
|
5679
6363
|
}
|
|
5680
6364
|
if (isJsonMode()) printJson({ error: { code: "generic_failure", message: String(err) } });
|