@rayul/kordis 0.6.7 → 0.7.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 +19 -0
- package/assets/kordis-using/SKILL.md +40 -0
- package/dist/index.js +315 -118
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,25 @@ Pass `--json` on any command to get machine-readable output.
|
|
|
56
56
|
|
|
57
57
|
Refresh and access tokens are stored in the OS keychain via `keytar` (with a file-based fallback at `~/.kordis/.tokens.json`, mode `0600`, when keytar is unavailable).
|
|
58
58
|
|
|
59
|
+
## Privacy & environment variables
|
|
60
|
+
|
|
61
|
+
Kordis collects two opt-in signals, both off by default. Both can be set non-interactively for agents (`kordis setup --personalization on|off` and `kordis setup --error-telemetry on|off`) and both have an env-var escape hatch that overrides config.
|
|
62
|
+
|
|
63
|
+
| env var | purpose | default |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `KORDIS_NO_CONTEXT=1` | suppress personalization header even if opted in | off |
|
|
66
|
+
| `KORDIS_NO_TELEMETRY=1` | suppress error telemetry POST even if opted in | off |
|
|
67
|
+
| `KORDIS_NO_KEYCHAIN=1` | force plaintext token storage even when keytar loads (escape hatch for macOS keychain-prompt fatigue) | off |
|
|
68
|
+
| `KORDIS_DEBUG=1` | surface auto-finalize stage outcomes to stderr (diagnostic) | off |
|
|
69
|
+
|
|
70
|
+
**What error telemetry carries** (0.7.0+, when `error_telemetry: 'on'`):
|
|
71
|
+
- error `kind` (4 enum values — auto-finalize Stage 1/2 failure, network unreachable, unhandled error)
|
|
72
|
+
- `cli_version`, `node_version`, `platform`
|
|
73
|
+
- `message_excerpt` — first 200 chars of `err.message` AFTER client-side sanitization (homedir → `~`; unix paths ≥2 segments → `<path>`; Windows paths → `<path>`; query strings → `?<query>`; JWTs → `<jwt>`; UUIDs → `<uuid>`; hex runs ≥32 → `<hex>`)
|
|
74
|
+
- optional `error_code` (when present on the Error) + `elapsed_ms` + ISO timestamp
|
|
75
|
+
|
|
76
|
+
**What it NEVER carries**: `argv`, env-var values, file paths (after sanitization), identity (`user_id`, `username`, `session_id`, refresh tokens), `X-Kordis-Context` content, full stack traces. The server route additionally ignores any `Authorization` header so identity can't leak in even by accident.
|
|
77
|
+
|
|
59
78
|
## Docs
|
|
60
79
|
|
|
61
80
|
The agent guide that `setup` injects is also shipped as `assets/KORDIS.md` in this package.
|
|
@@ -155,3 +155,43 @@ User: "add a pre-commit hook that blocks committing API keys"
|
|
|
155
155
|
- confirm with the user before pulling
|
|
156
156
|
- kordis pull <full_slug>
|
|
157
157
|
</example>
|
|
158
|
+
|
|
159
|
+
## Telemetry & opt-in (0.7.0+)
|
|
160
|
+
|
|
161
|
+
Kordis can send anonymized error reports to help maintainers fix wild failures that the user might otherwise just give up on. Off by default.
|
|
162
|
+
|
|
163
|
+
When asked at `kordis setup`, the consent prompt explicitly states the payload contents. Agents that already received user consent in chat can record it non-interactively:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
kordis setup --error-telemetry on
|
|
167
|
+
kordis setup --error-telemetry off
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The same applies to the older personalization opt-in:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
kordis setup --personalization on
|
|
174
|
+
kordis setup --personalization off
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Never assume consent on the user's behalf.** Ask in plain words, then record their answer with the flag.
|
|
178
|
+
|
|
179
|
+
**What error telemetry carries** (sent to `POST /api/telemetry/error`):
|
|
180
|
+
|
|
181
|
+
| field | value |
|
|
182
|
+
|---|---|
|
|
183
|
+
| `kind` | one of `auto_finalize_stage1_fail`, `auto_finalize_stage2_fail`, `network_unreachable`, `unhandled_error` |
|
|
184
|
+
| `cli_version`, `node_version`, `platform` | version + OS strings |
|
|
185
|
+
| `message_excerpt` | first 200 chars of `err.message` after sanitization (homedir → `~`; paths / JWTs / UUIDs / long hex runs stripped) |
|
|
186
|
+
| `error_code`, `elapsed_ms` | optional |
|
|
187
|
+
| `timestamp` | ISO 8601 |
|
|
188
|
+
|
|
189
|
+
**Never carries**: `argv`, env-var values, file paths (after sanitization), identity (`user_id` / `username` / `session_id` / refresh tokens), `X-Kordis-Context` content, full stack traces. The server's POST route ignores `Authorization` headers entirely.
|
|
190
|
+
|
|
191
|
+
**Escape hatches** (highest precedence wins):
|
|
192
|
+
|
|
193
|
+
1. `KORDIS_NO_TELEMETRY=1` env var — disables for that invocation
|
|
194
|
+
2. `kordis setup --error-telemetry off` — persists the off decision
|
|
195
|
+
3. `kordis setup --error-telemetry on` — persists the on decision
|
|
196
|
+
|
|
197
|
+
If a user asks "what does Kordis collect about me" — quote the table above; don't paraphrase.
|
package/dist/index.js
CHANGED
|
@@ -829,6 +829,69 @@ var init_recommend = __esm({
|
|
|
829
829
|
}
|
|
830
830
|
});
|
|
831
831
|
|
|
832
|
+
// ../../packages/contracts/src/telemetry.ts
|
|
833
|
+
import { z as z12 } from "zod";
|
|
834
|
+
var TelemetryErrorKindSchema, TelemetryPlatformSchema, TelemetryErrorPayloadSchema, AdminTelemetrySummaryQuerySchema, TelemetryKindCountSchema, TelemetryDailyBucketSchema, TelemetryTopExcerptSchema, AdminTelemetrySummaryResponseSchema;
|
|
835
|
+
var init_telemetry = __esm({
|
|
836
|
+
"../../packages/contracts/src/telemetry.ts"() {
|
|
837
|
+
"use strict";
|
|
838
|
+
TelemetryErrorKindSchema = z12.enum([
|
|
839
|
+
/** Stage 1 of CLI auto-finalize (pollDeviceFlow) failed with a non-pending error. */
|
|
840
|
+
"auto_finalize_stage1_fail",
|
|
841
|
+
/** Stage 2 of CLI auto-finalize (me/keychain/saveConfig) failed — the 0.6.6 class. */
|
|
842
|
+
"auto_finalize_stage2_fail",
|
|
843
|
+
/** Top-level CLI handler caught KordisNetworkError. */
|
|
844
|
+
"network_unreachable",
|
|
845
|
+
/** Top-level CLI handler caught an unrecognised error class. */
|
|
846
|
+
"unhandled_error"
|
|
847
|
+
]);
|
|
848
|
+
TelemetryPlatformSchema = z12.enum(["darwin", "linux", "win32", "other"]);
|
|
849
|
+
TelemetryErrorPayloadSchema = z12.object({
|
|
850
|
+
kind: TelemetryErrorKindSchema,
|
|
851
|
+
cli_version: z12.string().min(1).max(32),
|
|
852
|
+
node_version: z12.string().min(1).max(32),
|
|
853
|
+
platform: TelemetryPlatformSchema,
|
|
854
|
+
/** First 200 chars of err.message AFTER client-side sanitization. */
|
|
855
|
+
message_excerpt: z12.string().min(1).max(200),
|
|
856
|
+
/** err.code when present on the original error object. */
|
|
857
|
+
error_code: z12.string().min(1).max(64).optional(),
|
|
858
|
+
/** Optional timing — duration of the failing operation if the catch site knows it. */
|
|
859
|
+
elapsed_ms: z12.number().int().nonnegative().optional(),
|
|
860
|
+
/** ISO 8601 UTC. */
|
|
861
|
+
timestamp: z12.string().min(1).max(40)
|
|
862
|
+
});
|
|
863
|
+
AdminTelemetrySummaryQuerySchema = z12.object({
|
|
864
|
+
/** ISO 8601 cutoff; defaults to 7 days back. */
|
|
865
|
+
since: z12.string().datetime().optional()
|
|
866
|
+
});
|
|
867
|
+
TelemetryKindCountSchema = z12.object({
|
|
868
|
+
kind: TelemetryErrorKindSchema,
|
|
869
|
+
cli_version: z12.string(),
|
|
870
|
+
count: z12.number().int().nonnegative()
|
|
871
|
+
});
|
|
872
|
+
TelemetryDailyBucketSchema = z12.object({
|
|
873
|
+
/** YYYY-MM-DD UTC. */
|
|
874
|
+
date: z12.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
875
|
+
count: z12.number().int().nonnegative()
|
|
876
|
+
});
|
|
877
|
+
TelemetryTopExcerptSchema = z12.object({
|
|
878
|
+
kind: TelemetryErrorKindSchema,
|
|
879
|
+
message_excerpt: z12.string(),
|
|
880
|
+
count: z12.number().int().nonnegative()
|
|
881
|
+
});
|
|
882
|
+
AdminTelemetrySummaryResponseSchema = z12.object({
|
|
883
|
+
/** Cutoff applied to all aggregates. */
|
|
884
|
+
since: z12.string().datetime(),
|
|
885
|
+
/** Count grouped by (kind, cli_version), descending. */
|
|
886
|
+
counts: z12.array(TelemetryKindCountSchema),
|
|
887
|
+
/** Top 10 distinct message_excerpts per kind, for quick triage. */
|
|
888
|
+
top_excerpts: z12.array(TelemetryTopExcerptSchema),
|
|
889
|
+
/** Daily volume sparkline, oldest → newest. */
|
|
890
|
+
daily: z12.array(TelemetryDailyBucketSchema)
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
|
|
832
895
|
// ../../packages/contracts/src/index.ts
|
|
833
896
|
var init_src = __esm({
|
|
834
897
|
"../../packages/contracts/src/index.ts"() {
|
|
@@ -844,6 +907,7 @@ var init_src = __esm({
|
|
|
844
907
|
init_api();
|
|
845
908
|
init_context();
|
|
846
909
|
init_recommend();
|
|
910
|
+
init_telemetry();
|
|
847
911
|
}
|
|
848
912
|
});
|
|
849
913
|
|
|
@@ -1136,6 +1200,24 @@ var init_client = __esm({
|
|
|
1136
1200
|
`/api/comments/${id}/replies`
|
|
1137
1201
|
);
|
|
1138
1202
|
}
|
|
1203
|
+
// ─── telemetry ─────────────────────────────────────────────────────────
|
|
1204
|
+
/**
|
|
1205
|
+
* Fire-and-forget anonymized error report (0.7.0). The CLI's
|
|
1206
|
+
* `apps/cli/src/lib/error-reporter.ts` wraps this with sanitization +
|
|
1207
|
+
* gating + timeout. The server route is anonymous-OK (no Authorization
|
|
1208
|
+
* header read) and rate-limited to 30/min per IP via its own bucket so
|
|
1209
|
+
* a CI runner burst can't lock the IP out of the actual API.
|
|
1210
|
+
*
|
|
1211
|
+
* Server returns `204 No Content` on success; the reporter ignores
|
|
1212
|
+
* any non-2xx (and even network errors — telemetry MUST NOT itself
|
|
1213
|
+
* become a source of user-visible failure).
|
|
1214
|
+
*/
|
|
1215
|
+
recordTelemetryError(payload) {
|
|
1216
|
+
return this.request("/api/telemetry/error", {
|
|
1217
|
+
method: "POST",
|
|
1218
|
+
body: payload
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1139
1221
|
};
|
|
1140
1222
|
}
|
|
1141
1223
|
});
|
|
@@ -1186,7 +1268,8 @@ async function loadCliConfig() {
|
|
|
1186
1268
|
apiUrl: apiUrlFromEnv ?? parsed.apiUrl ?? "https://api.kordis.store",
|
|
1187
1269
|
...parsed.username ? { username: parsed.username } : {},
|
|
1188
1270
|
...parsed.sessionId ? { sessionId: parsed.sessionId } : {},
|
|
1189
|
-
personalization: parsed.personalization ?? "undecided"
|
|
1271
|
+
personalization: parsed.personalization ?? "undecided",
|
|
1272
|
+
error_telemetry: parsed.error_telemetry ?? "undecided"
|
|
1190
1273
|
};
|
|
1191
1274
|
} catch {
|
|
1192
1275
|
return { apiUrl: apiUrlFromEnv ?? "https://api.kordis.store" };
|
|
@@ -1518,15 +1601,93 @@ async function getCachedContextHeader(cwd = process.cwd()) {
|
|
|
1518
1601
|
return header;
|
|
1519
1602
|
}
|
|
1520
1603
|
|
|
1604
|
+
// src/lib/error-reporter.ts
|
|
1605
|
+
init_src2();
|
|
1606
|
+
import { homedir as homedir4, platform } from "os";
|
|
1607
|
+
|
|
1608
|
+
// src/version.ts
|
|
1609
|
+
import { readFileSync } from "fs";
|
|
1610
|
+
import { fileURLToPath } from "url";
|
|
1611
|
+
import { dirname, join as join4 } from "path";
|
|
1612
|
+
var __dir = dirname(fileURLToPath(import.meta.url));
|
|
1613
|
+
var pkgPath = join4(__dir, "..", "package.json");
|
|
1614
|
+
var VERSION = (() => {
|
|
1615
|
+
try {
|
|
1616
|
+
const raw = readFileSync(pkgPath, "utf8");
|
|
1617
|
+
const parsed = JSON.parse(raw);
|
|
1618
|
+
return typeof parsed.version === "string" ? parsed.version : "0.0.0-unknown";
|
|
1619
|
+
} catch {
|
|
1620
|
+
return "0.0.0-unknown";
|
|
1621
|
+
}
|
|
1622
|
+
})();
|
|
1623
|
+
|
|
1624
|
+
// src/lib/error-reporter.ts
|
|
1625
|
+
var HOME = homedir4();
|
|
1626
|
+
var REPORT_TIMEOUT_MS = 3e3;
|
|
1627
|
+
function sanitizeMessage(raw) {
|
|
1628
|
+
let s = raw;
|
|
1629
|
+
if (HOME && HOME.length > 0) {
|
|
1630
|
+
s = s.split(HOME).join("~");
|
|
1631
|
+
}
|
|
1632
|
+
s = s.replace(/(\/[^\s'"]+){2,}/g, "<path>");
|
|
1633
|
+
s = s.replace(/[A-Za-z]:\\[^\s'"]+/g, "<path>");
|
|
1634
|
+
s = s.replace(/\?[^\s'"]+/g, "?<query>");
|
|
1635
|
+
s = s.replace(/eyJ[\w-]+\.[\w-]+\.[\w-]+/g, "<jwt>");
|
|
1636
|
+
s = s.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<uuid>");
|
|
1637
|
+
s = s.replace(/\b[0-9a-f]{32,}\b/gi, "<hex>");
|
|
1638
|
+
s = s.trim().slice(0, 200);
|
|
1639
|
+
return s.length === 0 ? "<empty>" : s;
|
|
1640
|
+
}
|
|
1641
|
+
function normalisePlatform() {
|
|
1642
|
+
const p = platform();
|
|
1643
|
+
if (p === "darwin" || p === "linux" || p === "win32") return p;
|
|
1644
|
+
return "other";
|
|
1645
|
+
}
|
|
1646
|
+
async function telemetryBaseUrl() {
|
|
1647
|
+
const override = process.env["KORDIS_TELEMETRY_URL"];
|
|
1648
|
+
if (override) return override;
|
|
1649
|
+
const cfg = await loadCliConfig();
|
|
1650
|
+
return cfg.apiUrl;
|
|
1651
|
+
}
|
|
1652
|
+
async function reportError(kind, err, extras = {}) {
|
|
1653
|
+
try {
|
|
1654
|
+
if (process.env["KORDIS_NO_TELEMETRY"] === "1") return;
|
|
1655
|
+
const cfg = await loadCliConfig();
|
|
1656
|
+
if (cfg.error_telemetry !== "on") return;
|
|
1657
|
+
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
1658
|
+
const detectedCode = extras.error_code ?? (err.code !== void 0 ? String(err.code).slice(0, 64) : void 0);
|
|
1659
|
+
const payload = {
|
|
1660
|
+
kind,
|
|
1661
|
+
cli_version: VERSION,
|
|
1662
|
+
node_version: process.version,
|
|
1663
|
+
platform: normalisePlatform(),
|
|
1664
|
+
message_excerpt: sanitizeMessage(rawMessage),
|
|
1665
|
+
...detectedCode ? { error_code: detectedCode } : {},
|
|
1666
|
+
...extras.elapsed_ms !== void 0 ? { elapsed_ms: extras.elapsed_ms } : {},
|
|
1667
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1668
|
+
};
|
|
1669
|
+
const baseUrl = await telemetryBaseUrl();
|
|
1670
|
+
const client = new KordisClient({
|
|
1671
|
+
baseUrl,
|
|
1672
|
+
userAgent: `kordis-cli/${VERSION}/telemetry node/${process.version}`
|
|
1673
|
+
});
|
|
1674
|
+
await Promise.race([
|
|
1675
|
+
client.recordTelemetryError(payload),
|
|
1676
|
+
new Promise((resolve2) => setTimeout(resolve2, REPORT_TIMEOUT_MS))
|
|
1677
|
+
]);
|
|
1678
|
+
} catch {
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1521
1682
|
// src/lib/pending-login.ts
|
|
1522
1683
|
import { readFile as readFile4, writeFile as writeFile3, unlink as unlink2, mkdir as mkdir3 } from "fs/promises";
|
|
1523
|
-
import { homedir as
|
|
1524
|
-
import { join as
|
|
1684
|
+
import { homedir as homedir5 } from "os";
|
|
1685
|
+
import { join as join5 } from "path";
|
|
1525
1686
|
function dir() {
|
|
1526
|
-
return process.env["KORDIS_HOME"] ??
|
|
1687
|
+
return process.env["KORDIS_HOME"] ?? join5(homedir5(), ".kordis");
|
|
1527
1688
|
}
|
|
1528
1689
|
function path() {
|
|
1529
|
-
return
|
|
1690
|
+
return join5(dir(), "pending-login.json");
|
|
1530
1691
|
}
|
|
1531
1692
|
async function readPending() {
|
|
1532
1693
|
try {
|
|
@@ -1552,22 +1713,6 @@ function isExpired(file) {
|
|
|
1552
1713
|
return Math.floor(Date.now() / 1e3) >= file.expires_at_epoch;
|
|
1553
1714
|
}
|
|
1554
1715
|
|
|
1555
|
-
// src/version.ts
|
|
1556
|
-
import { readFileSync } from "fs";
|
|
1557
|
-
import { fileURLToPath } from "url";
|
|
1558
|
-
import { dirname, join as join5 } from "path";
|
|
1559
|
-
var __dir = dirname(fileURLToPath(import.meta.url));
|
|
1560
|
-
var pkgPath = join5(__dir, "..", "package.json");
|
|
1561
|
-
var VERSION = (() => {
|
|
1562
|
-
try {
|
|
1563
|
-
const raw = readFileSync(pkgPath, "utf8");
|
|
1564
|
-
const parsed = JSON.parse(raw);
|
|
1565
|
-
return typeof parsed.version === "string" ? parsed.version : "0.0.0-unknown";
|
|
1566
|
-
} catch {
|
|
1567
|
-
return "0.0.0-unknown";
|
|
1568
|
-
}
|
|
1569
|
-
})();
|
|
1570
|
-
|
|
1571
1716
|
// src/client.ts
|
|
1572
1717
|
async function useClient() {
|
|
1573
1718
|
let cfg = await loadCliConfig();
|
|
@@ -1599,6 +1744,7 @@ async function useClient() {
|
|
|
1599
1744
|
process.stderr.write(`[kordis] auto-finalize: poll failed (code=${code ?? "none"}): ${msg}
|
|
1600
1745
|
`);
|
|
1601
1746
|
}
|
|
1747
|
+
void reportError("auto_finalize_stage1_fail", err);
|
|
1602
1748
|
}
|
|
1603
1749
|
}
|
|
1604
1750
|
if (pollResult) {
|
|
@@ -1631,6 +1777,7 @@ async function useClient() {
|
|
|
1631
1777
|
`[kordis] auto-finalize: server issued tokens but local persistence failed (${msg}). Run \`kordis login --emit-only\` again to retry.
|
|
1632
1778
|
`
|
|
1633
1779
|
);
|
|
1780
|
+
void reportError("auto_finalize_stage2_fail", err);
|
|
1634
1781
|
}
|
|
1635
1782
|
}
|
|
1636
1783
|
}
|
|
@@ -1848,10 +1995,10 @@ import { Command as Command3 } from "commander";
|
|
|
1848
1995
|
|
|
1849
1996
|
// src/lib/recommendation-cache.ts
|
|
1850
1997
|
import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
|
|
1851
|
-
import { homedir as
|
|
1998
|
+
import { homedir as homedir6 } from "os";
|
|
1852
1999
|
import { join as join6 } from "path";
|
|
1853
2000
|
function cacheDir() {
|
|
1854
|
-
return process.env["KORDIS_HOME"] ?? join6(
|
|
2001
|
+
return process.env["KORDIS_HOME"] ?? join6(homedir6(), ".kordis");
|
|
1855
2002
|
}
|
|
1856
2003
|
function cachePath() {
|
|
1857
2004
|
return join6(cacheDir(), "recommendations.json");
|
|
@@ -2107,9 +2254,9 @@ var FrontmatterParseError = class extends Error {
|
|
|
2107
2254
|
|
|
2108
2255
|
// ../../packages/harness-schema/src/deps.ts
|
|
2109
2256
|
init_src();
|
|
2110
|
-
import { z as
|
|
2257
|
+
import { z as z13 } from "zod";
|
|
2111
2258
|
var StringDepRe = /^([a-z0-9](-?[a-z0-9])*\/[a-z0-9](-?[a-z0-9])*)(?:@(.+))?$/;
|
|
2112
|
-
var ObjectDepSchema =
|
|
2259
|
+
var ObjectDepSchema = z13.object({
|
|
2113
2260
|
full_slug: FullSlugSchema,
|
|
2114
2261
|
version_range: SemverRangeSchema.default("*")
|
|
2115
2262
|
});
|
|
@@ -2145,50 +2292,50 @@ init_src();
|
|
|
2145
2292
|
|
|
2146
2293
|
// ../../packages/harness-schema/src/types/skill.ts
|
|
2147
2294
|
init_src();
|
|
2148
|
-
import { z as
|
|
2149
|
-
var SkillFrontmatterSchema =
|
|
2150
|
-
name:
|
|
2151
|
-
description:
|
|
2295
|
+
import { z as z14 } from "zod";
|
|
2296
|
+
var SkillFrontmatterSchema = z14.object({
|
|
2297
|
+
name: z14.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "name must be lowercase-with-hyphens (Agent Skills spec)"),
|
|
2298
|
+
description: z14.string().min(1).max(2048),
|
|
2152
2299
|
version: SemverSchema.optional(),
|
|
2153
2300
|
// Agent Skills optional metadata
|
|
2154
|
-
license:
|
|
2155
|
-
homepage:
|
|
2156
|
-
model:
|
|
2301
|
+
license: z14.string().max(64).optional(),
|
|
2302
|
+
homepage: z14.string().url().optional(),
|
|
2303
|
+
model: z14.string().optional(),
|
|
2157
2304
|
// model override hint
|
|
2158
|
-
"allowed-tools":
|
|
2159
|
-
"disable-model-invocation":
|
|
2305
|
+
"allowed-tools": z14.array(z14.string()).optional(),
|
|
2306
|
+
"disable-model-invocation": z14.boolean().optional(),
|
|
2160
2307
|
// Kordis-forwarded cross-type fields (kept minimal to not pollute the spec)
|
|
2161
|
-
title:
|
|
2162
|
-
tags:
|
|
2163
|
-
tools:
|
|
2308
|
+
title: z14.string().min(1).max(200).optional(),
|
|
2309
|
+
tags: z14.array(TagSchema).max(16).optional(),
|
|
2310
|
+
tools: z14.array(ToolSlugSchema).optional()
|
|
2164
2311
|
}).strict();
|
|
2165
2312
|
|
|
2166
2313
|
// ../../packages/harness-schema/src/types/mcp-server.ts
|
|
2167
2314
|
init_src();
|
|
2168
|
-
import { z as
|
|
2169
|
-
var McpTransportSchema =
|
|
2170
|
-
|
|
2171
|
-
type:
|
|
2172
|
-
command:
|
|
2173
|
-
args:
|
|
2174
|
-
env:
|
|
2315
|
+
import { z as z15 } from "zod";
|
|
2316
|
+
var McpTransportSchema = z15.union([
|
|
2317
|
+
z15.object({
|
|
2318
|
+
type: z15.literal("stdio"),
|
|
2319
|
+
command: z15.string().min(1),
|
|
2320
|
+
args: z15.array(z15.string()).optional().default([]),
|
|
2321
|
+
env: z15.record(z15.string()).optional().default({})
|
|
2175
2322
|
}),
|
|
2176
|
-
|
|
2177
|
-
type:
|
|
2178
|
-
url:
|
|
2179
|
-
headers:
|
|
2323
|
+
z15.object({
|
|
2324
|
+
type: z15.enum(["http", "sse"]),
|
|
2325
|
+
url: z15.string().url(),
|
|
2326
|
+
headers: z15.record(z15.string()).optional().default({})
|
|
2180
2327
|
})
|
|
2181
2328
|
]);
|
|
2182
|
-
var McpCapabilitiesSchema =
|
|
2183
|
-
tools:
|
|
2184
|
-
resources:
|
|
2185
|
-
prompts:
|
|
2329
|
+
var McpCapabilitiesSchema = z15.object({
|
|
2330
|
+
tools: z15.array(z15.string()).optional().default([]),
|
|
2331
|
+
resources: z15.array(z15.string()).optional().default([]),
|
|
2332
|
+
prompts: z15.array(z15.string()).optional().default([])
|
|
2186
2333
|
});
|
|
2187
|
-
var McpServerFrontmatterSchema =
|
|
2188
|
-
name:
|
|
2189
|
-
description:
|
|
2334
|
+
var McpServerFrontmatterSchema = z15.object({
|
|
2335
|
+
name: z15.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
|
2336
|
+
description: z15.string().min(1).max(2048),
|
|
2190
2337
|
version: SemverSchema,
|
|
2191
|
-
mcp_spec_version:
|
|
2338
|
+
mcp_spec_version: z15.string().default("2026-03"),
|
|
2192
2339
|
transport: McpTransportSchema,
|
|
2193
2340
|
capabilities: McpCapabilitiesSchema.optional().default({
|
|
2194
2341
|
tools: [],
|
|
@@ -2196,35 +2343,35 @@ var McpServerFrontmatterSchema = z14.object({
|
|
|
2196
2343
|
prompts: []
|
|
2197
2344
|
}),
|
|
2198
2345
|
/** Human-readable setup / auth instructions. Surfaced in `kordis info`. */
|
|
2199
|
-
auth_notes:
|
|
2346
|
+
auth_notes: z15.string().optional(),
|
|
2200
2347
|
/** Required env vars with description + whether they must be secret. */
|
|
2201
|
-
env_schema:
|
|
2202
|
-
|
|
2203
|
-
description:
|
|
2204
|
-
required:
|
|
2205
|
-
secret:
|
|
2348
|
+
env_schema: z15.record(
|
|
2349
|
+
z15.object({
|
|
2350
|
+
description: z15.string(),
|
|
2351
|
+
required: z15.boolean().default(true),
|
|
2352
|
+
secret: z15.boolean().default(false)
|
|
2206
2353
|
})
|
|
2207
2354
|
).optional(),
|
|
2208
|
-
homepage:
|
|
2209
|
-
repository:
|
|
2210
|
-
license:
|
|
2211
|
-
title:
|
|
2355
|
+
homepage: z15.string().url().optional(),
|
|
2356
|
+
repository: z15.string().url().optional(),
|
|
2357
|
+
license: z15.string().max(64).optional(),
|
|
2358
|
+
title: z15.string().min(1).max(200).optional()
|
|
2212
2359
|
}).strict();
|
|
2213
2360
|
|
|
2214
2361
|
// ../../packages/harness-schema/src/types/project-rules.ts
|
|
2215
2362
|
init_src();
|
|
2216
|
-
import { z as
|
|
2217
|
-
var ProjectRulesFrontmatterSchema =
|
|
2218
|
-
title:
|
|
2219
|
-
description:
|
|
2363
|
+
import { z as z16 } from "zod";
|
|
2364
|
+
var ProjectRulesFrontmatterSchema = z16.object({
|
|
2365
|
+
title: z16.string().min(1).max(200),
|
|
2366
|
+
description: z16.string().min(1).max(2048),
|
|
2220
2367
|
version: SemverSchema.optional(),
|
|
2221
|
-
scope:
|
|
2222
|
-
target_path:
|
|
2223
|
-
globs:
|
|
2224
|
-
target_tools:
|
|
2368
|
+
scope: z16.enum(["root", "nested", "globs"]).default("root"),
|
|
2369
|
+
target_path: z16.string().optional(),
|
|
2370
|
+
globs: z16.array(z16.string()).optional(),
|
|
2371
|
+
target_tools: z16.array(ToolSlugSchema).optional().default([]),
|
|
2225
2372
|
/** When true, installation is idempotent via `<!-- kordis:... -->` markers. */
|
|
2226
|
-
mergeable:
|
|
2227
|
-
license:
|
|
2373
|
+
mergeable: z16.boolean().default(true),
|
|
2374
|
+
license: z16.string().max(64).optional()
|
|
2228
2375
|
}).strict().refine(
|
|
2229
2376
|
(v) => v.scope !== "nested" || !!v.target_path,
|
|
2230
2377
|
{ message: 'scope="nested" requires target_path' }
|
|
@@ -2235,33 +2382,33 @@ var ProjectRulesFrontmatterSchema = z15.object({
|
|
|
2235
2382
|
|
|
2236
2383
|
// ../../packages/harness-schema/src/types/custom-agent.ts
|
|
2237
2384
|
init_src();
|
|
2238
|
-
import { z as
|
|
2239
|
-
var CustomAgentFrontmatterSchema =
|
|
2240
|
-
name:
|
|
2241
|
-
description:
|
|
2242
|
-
title:
|
|
2385
|
+
import { z as z17 } from "zod";
|
|
2386
|
+
var CustomAgentFrontmatterSchema = z17.object({
|
|
2387
|
+
name: z17.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
|
2388
|
+
description: z17.string().min(1).max(2048),
|
|
2389
|
+
title: z17.string().min(1).max(200).optional(),
|
|
2243
2390
|
version: SemverSchema.optional(),
|
|
2244
2391
|
/** System prompt that defines the agent's persona + rules. Required. */
|
|
2245
|
-
system_prompt:
|
|
2392
|
+
system_prompt: z17.string().min(1).max(5e4),
|
|
2246
2393
|
/** Optional model override — string passed verbatim to the host. */
|
|
2247
|
-
model:
|
|
2394
|
+
model: z17.string().optional(),
|
|
2248
2395
|
/** Tools this agent can call. Host-specific names (no uniform namespace). */
|
|
2249
|
-
allowed_tools:
|
|
2250
|
-
disallowed_tools:
|
|
2396
|
+
allowed_tools: z17.array(z17.string()).optional().default([]),
|
|
2397
|
+
disallowed_tools: z17.array(z17.string()).optional().default([]),
|
|
2251
2398
|
/** Fork/context behaviour hint (Claude: `context: fork` etc.). */
|
|
2252
|
-
context:
|
|
2399
|
+
context: z17.enum(["inherit", "fork", "isolated"]).optional(),
|
|
2253
2400
|
/** Known-good tool runtimes. Empty = untested everywhere. */
|
|
2254
|
-
tool_compat:
|
|
2401
|
+
tool_compat: z17.array(ToolSlugSchema).optional().default([]),
|
|
2255
2402
|
/** Pre-load skills when this agent activates. */
|
|
2256
|
-
preload_skills:
|
|
2257
|
-
license:
|
|
2258
|
-
homepage:
|
|
2403
|
+
preload_skills: z17.array(z17.string()).optional().default([]),
|
|
2404
|
+
license: z17.string().max(64).optional(),
|
|
2405
|
+
homepage: z17.string().url().optional()
|
|
2259
2406
|
}).strict();
|
|
2260
2407
|
|
|
2261
2408
|
// ../../packages/harness-schema/src/types/hook.ts
|
|
2262
2409
|
init_src();
|
|
2263
|
-
import { z as
|
|
2264
|
-
var HookEventSchema =
|
|
2410
|
+
import { z as z18 } from "zod";
|
|
2411
|
+
var HookEventSchema = z18.enum([
|
|
2265
2412
|
"PreToolUse",
|
|
2266
2413
|
"PostToolUse",
|
|
2267
2414
|
"UserPromptSubmit",
|
|
@@ -2273,37 +2420,37 @@ var HookEventSchema = z17.enum([
|
|
|
2273
2420
|
"SubagentStop",
|
|
2274
2421
|
"Background"
|
|
2275
2422
|
]);
|
|
2276
|
-
var HookActionSchema =
|
|
2277
|
-
|
|
2278
|
-
type:
|
|
2279
|
-
command:
|
|
2280
|
-
timeout_ms:
|
|
2281
|
-
blocking:
|
|
2423
|
+
var HookActionSchema = z18.union([
|
|
2424
|
+
z18.object({
|
|
2425
|
+
type: z18.literal("command"),
|
|
2426
|
+
command: z18.string().min(1),
|
|
2427
|
+
timeout_ms: z18.number().int().positive().max(6e5).optional(),
|
|
2428
|
+
blocking: z18.boolean().default(false)
|
|
2282
2429
|
}),
|
|
2283
|
-
|
|
2284
|
-
type:
|
|
2285
|
-
url:
|
|
2286
|
-
method:
|
|
2287
|
-
headers:
|
|
2288
|
-
timeout_ms:
|
|
2289
|
-
blocking:
|
|
2430
|
+
z18.object({
|
|
2431
|
+
type: z18.literal("http"),
|
|
2432
|
+
url: z18.string().url(),
|
|
2433
|
+
method: z18.enum(["GET", "POST", "PUT", "PATCH"]).default("POST"),
|
|
2434
|
+
headers: z18.record(z18.string()).optional().default({}),
|
|
2435
|
+
timeout_ms: z18.number().int().positive().max(6e5).optional(),
|
|
2436
|
+
blocking: z18.boolean().default(false)
|
|
2290
2437
|
})
|
|
2291
2438
|
]);
|
|
2292
|
-
var HookBindingSchema =
|
|
2439
|
+
var HookBindingSchema = z18.object({
|
|
2293
2440
|
event: HookEventSchema,
|
|
2294
|
-
matcher:
|
|
2441
|
+
matcher: z18.string().optional(),
|
|
2295
2442
|
action: HookActionSchema
|
|
2296
2443
|
});
|
|
2297
|
-
var HookFrontmatterSchema =
|
|
2298
|
-
name:
|
|
2299
|
-
description:
|
|
2300
|
-
title:
|
|
2444
|
+
var HookFrontmatterSchema = z18.object({
|
|
2445
|
+
name: z18.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
|
2446
|
+
description: z18.string().min(1).max(2048),
|
|
2447
|
+
title: z18.string().min(1).max(200).optional(),
|
|
2301
2448
|
version: SemverSchema.optional(),
|
|
2302
2449
|
/** One or more hook bindings. */
|
|
2303
|
-
hooks:
|
|
2450
|
+
hooks: z18.array(HookBindingSchema).min(1),
|
|
2304
2451
|
/** Declared to surface in search filters — always "claude-code" for now. */
|
|
2305
|
-
tool_compat:
|
|
2306
|
-
license:
|
|
2452
|
+
tool_compat: z18.array(z18.enum(["claude-code"])).default(["claude-code"]),
|
|
2453
|
+
license: z18.string().max(64).optional()
|
|
2307
2454
|
}).strict();
|
|
2308
2455
|
|
|
2309
2456
|
// ../../packages/harness-schema/src/validator.ts
|
|
@@ -3333,11 +3480,11 @@ function getTranslator(type, format) {
|
|
|
3333
3480
|
}
|
|
3334
3481
|
|
|
3335
3482
|
// src/lockfile.ts
|
|
3336
|
-
import { homedir as
|
|
3483
|
+
import { homedir as homedir7 } from "os";
|
|
3337
3484
|
import { join as join8 } from "path";
|
|
3338
3485
|
import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
|
|
3339
3486
|
function kordisHome2() {
|
|
3340
|
-
return process.env["KORDIS_HOME"] ?? join8(
|
|
3487
|
+
return process.env["KORDIS_HOME"] ?? join8(homedir7(), ".kordis");
|
|
3341
3488
|
}
|
|
3342
3489
|
function lockfilePath() {
|
|
3343
3490
|
return join8(kordisHome2(), "installed.json");
|
|
@@ -3621,7 +3768,7 @@ function logoutCommand() {
|
|
|
3621
3768
|
|
|
3622
3769
|
// src/commands/setup.ts
|
|
3623
3770
|
import { readFile as readFile8, writeFile as writeFile7, mkdir as mkdir7, access as access3 } from "fs/promises";
|
|
3624
|
-
import { homedir as
|
|
3771
|
+
import { homedir as homedir8 } from "os";
|
|
3625
3772
|
import { join as join10, dirname as dirname3 } from "path";
|
|
3626
3773
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3627
3774
|
import { Command as Command9 } from "commander";
|
|
@@ -3730,8 +3877,11 @@ function setupCommand() {
|
|
|
3730
3877
|
return new Command9("setup").description("inject kordis usage guide into global agent instruction files").option("--silent", "suppress errors \u2014 safe for postinstall").option("--dry-run", "detect installed tools without writing any files").option(
|
|
3731
3878
|
"--personalization <choice>",
|
|
3732
3879
|
"record opt-in non-interactively: 'on' | 'off' (skips the Y/n prompt; safe for agents that just received user consent in chat)"
|
|
3880
|
+
).option(
|
|
3881
|
+
"--error-telemetry <choice>",
|
|
3882
|
+
"record opt-in non-interactively: 'on' | 'off' (skips the Y/n prompt; agent forwards consent received in chat)"
|
|
3733
3883
|
).action(async (opts) => {
|
|
3734
|
-
const home =
|
|
3884
|
+
const home = homedir8();
|
|
3735
3885
|
const dryRun = opts.dryRun ?? false;
|
|
3736
3886
|
if (opts.personalization !== void 0) {
|
|
3737
3887
|
const v = opts.personalization.toLowerCase();
|
|
@@ -3754,6 +3904,27 @@ function setupCommand() {
|
|
|
3754
3904
|
}
|
|
3755
3905
|
}
|
|
3756
3906
|
}
|
|
3907
|
+
if (opts.errorTelemetry !== void 0) {
|
|
3908
|
+
const v = opts.errorTelemetry.toLowerCase();
|
|
3909
|
+
if (v !== "on" && v !== "off") {
|
|
3910
|
+
throw new Error("--error-telemetry must be 'on' or 'off'");
|
|
3911
|
+
}
|
|
3912
|
+
if (!dryRun) {
|
|
3913
|
+
const cfg = await loadCliConfig();
|
|
3914
|
+
await saveCliConfig({ ...cfg, error_telemetry: v });
|
|
3915
|
+
}
|
|
3916
|
+
if (!opts.silent) {
|
|
3917
|
+
if (v === "on") {
|
|
3918
|
+
printHuman(
|
|
3919
|
+
c.green("\u2713 error telemetry on") + c.gray(" \u2014 anonymized: kind + cli/node version + platform + sanitized message excerpt (\u2264200 chars). Never carries identity, paths, or env values. Override any time: `KORDIS_NO_TELEMETRY=1` env or `kordis setup --error-telemetry off`")
|
|
3920
|
+
);
|
|
3921
|
+
} else {
|
|
3922
|
+
printHuman(
|
|
3923
|
+
c.gray("\u2192 error telemetry off \u2014 failures stay on your machine.")
|
|
3924
|
+
);
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3757
3928
|
let hubContent;
|
|
3758
3929
|
let subSkillContents;
|
|
3759
3930
|
try {
|
|
@@ -3859,6 +4030,30 @@ function setupCommand() {
|
|
|
3859
4030
|
);
|
|
3860
4031
|
}
|
|
3861
4032
|
}
|
|
4033
|
+
const cfg2 = await loadCliConfig();
|
|
4034
|
+
if (cfg2.error_telemetry === "undecided") {
|
|
4035
|
+
printHuman("");
|
|
4036
|
+
const { enable } = await prompts({
|
|
4037
|
+
type: "confirm",
|
|
4038
|
+
name: "enable",
|
|
4039
|
+
message: "Send anonymized error reports to help fix wild failures? Payload: kind + cli/node version + platform + sanitized message excerpt (\u2264200 chars). NEVER includes paths, env values, identity, query text, or the full stack. KORDIS_NO_TELEMETRY=1 disables any time.",
|
|
4040
|
+
initial: true
|
|
4041
|
+
});
|
|
4042
|
+
const newCfg = {
|
|
4043
|
+
...cfg2,
|
|
4044
|
+
error_telemetry: enable ? "on" : "off"
|
|
4045
|
+
};
|
|
4046
|
+
await saveCliConfig(newCfg);
|
|
4047
|
+
if (enable) {
|
|
4048
|
+
printHuman(
|
|
4049
|
+
c.green("\u2713 error telemetry on") + c.gray(" \u2014 disable any time: `KORDIS_NO_TELEMETRY=1` env or `kordis setup --error-telemetry off`")
|
|
4050
|
+
);
|
|
4051
|
+
} else {
|
|
4052
|
+
printHuman(
|
|
4053
|
+
c.gray("\u2192 error telemetry off \u2014 failures stay on your machine.")
|
|
4054
|
+
);
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
3862
4057
|
}
|
|
3863
4058
|
});
|
|
3864
4059
|
}
|
|
@@ -4668,6 +4863,7 @@ function handleError(err) {
|
|
|
4668
4863
|
printHuman(c.red(`\u2717 ${err.message}`));
|
|
4669
4864
|
for (const line of hint.split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
4670
4865
|
}
|
|
4866
|
+
void reportError("network_unreachable", err);
|
|
4671
4867
|
process.exit(err.exitCode());
|
|
4672
4868
|
}
|
|
4673
4869
|
if (err instanceof Error) {
|
|
@@ -4687,6 +4883,7 @@ function handleError(err) {
|
|
|
4687
4883
|
for (const line of hintStr.split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
4688
4884
|
}
|
|
4689
4885
|
}
|
|
4886
|
+
void reportError("unhandled_error", err);
|
|
4690
4887
|
process.exit(1);
|
|
4691
4888
|
}
|
|
4692
4889
|
if (isJsonMode()) printJson({ error: { code: "generic_failure", message: String(err) } });
|