@rayul/kordis 0.6.7 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/assets/kordis-search/SKILL.md +12 -1
- package/assets/kordis-using/SKILL.md +40 -0
- package/dist/index.js +353 -132
- 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.
|
|
@@ -26,7 +26,8 @@ Keyword search; `--json` for parsing.
|
|
|
26
26
|
| `--trending` | list packages trending in `--window` (overrides `--sort`) |
|
|
27
27
|
| `--rising` | list rising packages by 7d acceleration (overrides `--sort`) |
|
|
28
28
|
| `--window <w>` | `24h` (default), `7d`, or `30d` — only honoured with `--trending` |
|
|
29
|
-
| `--limit <n>` | max results (default 20)
|
|
29
|
+
| `--limit <n>` | max results per page (default 20, max 100) |
|
|
30
|
+
| `--cursor <t>` | pagination — pass `next_cursor` from a prior response to fetch page 2+. 0.6.7+. |
|
|
30
31
|
|
|
31
32
|
### `kordis info <full_slug> --json`
|
|
32
33
|
Inspect metadata (title, description, type, tier, version, author, stars,
|
|
@@ -121,3 +122,13 @@ User: "how many packages has alice published?" / "alice가 publish한거 다 보
|
|
|
121
122
|
`total: null`); render "Showing N of M matches"
|
|
122
123
|
- do NOT post-process with jq for author filtering — use the flag
|
|
123
124
|
</example>
|
|
125
|
+
|
|
126
|
+
<example>
|
|
127
|
+
User: "show me page 2 of those results" / "다음 페이지 보여줘"
|
|
128
|
+
- response from page 1 carries `next_cursor` (opaque base64 token, or
|
|
129
|
+
`null` when no more pages)
|
|
130
|
+
- kordis search <q> --cursor <token> --json # 0.6.7+
|
|
131
|
+
- keep feeding `next_cursor` from each response until it comes back `null`
|
|
132
|
+
- DO NOT try `--offset` for cursor-paginated endpoints — CLI passes
|
|
133
|
+
`--cursor` only
|
|
134
|
+
</example>
|
|
@@ -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
|
|
|
@@ -929,20 +993,35 @@ var init_client = __esm({
|
|
|
929
993
|
};
|
|
930
994
|
if (opts.body !== void 0) headers["content-type"] = "application/json";
|
|
931
995
|
let res;
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
996
|
+
const init = {
|
|
997
|
+
method: opts.method ?? "GET",
|
|
998
|
+
headers,
|
|
999
|
+
cache: "no-store"
|
|
1000
|
+
};
|
|
1001
|
+
if (opts.body !== void 0) init.body = JSON.stringify(opts.body);
|
|
1002
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
1003
|
+
const retryable = method === "GET";
|
|
1004
|
+
let lastErr;
|
|
1005
|
+
for (let attempt = 0; attempt < (retryable ? 2 : 1); attempt++) {
|
|
1006
|
+
try {
|
|
1007
|
+
res = await this.fetchImpl(url, init);
|
|
1008
|
+
lastErr = void 0;
|
|
1009
|
+
break;
|
|
1010
|
+
} catch (err) {
|
|
1011
|
+
lastErr = err;
|
|
1012
|
+
if (attempt === 0 && retryable) {
|
|
1013
|
+
await new Promise((resolve2) => setTimeout(resolve2, 150));
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (lastErr !== void 0) {
|
|
941
1019
|
throw new KordisNetworkError(
|
|
942
|
-
`cannot reach ${url.hostname}: ${
|
|
943
|
-
|
|
1020
|
+
`cannot reach ${url.hostname}: ${lastErr.message}`,
|
|
1021
|
+
lastErr
|
|
944
1022
|
);
|
|
945
1023
|
}
|
|
1024
|
+
res = res;
|
|
946
1025
|
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
947
1026
|
if (res.status === 204) return void 0;
|
|
948
1027
|
const text = await res.text();
|
|
@@ -1136,6 +1215,24 @@ var init_client = __esm({
|
|
|
1136
1215
|
`/api/comments/${id}/replies`
|
|
1137
1216
|
);
|
|
1138
1217
|
}
|
|
1218
|
+
// ─── telemetry ─────────────────────────────────────────────────────────
|
|
1219
|
+
/**
|
|
1220
|
+
* Fire-and-forget anonymized error report (0.7.0). The CLI's
|
|
1221
|
+
* `apps/cli/src/lib/error-reporter.ts` wraps this with sanitization +
|
|
1222
|
+
* gating + timeout. The server route is anonymous-OK (no Authorization
|
|
1223
|
+
* header read) and rate-limited to 30/min per IP via its own bucket so
|
|
1224
|
+
* a CI runner burst can't lock the IP out of the actual API.
|
|
1225
|
+
*
|
|
1226
|
+
* Server returns `204 No Content` on success; the reporter ignores
|
|
1227
|
+
* any non-2xx (and even network errors — telemetry MUST NOT itself
|
|
1228
|
+
* become a source of user-visible failure).
|
|
1229
|
+
*/
|
|
1230
|
+
recordTelemetryError(payload) {
|
|
1231
|
+
return this.request("/api/telemetry/error", {
|
|
1232
|
+
method: "POST",
|
|
1233
|
+
body: payload
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1139
1236
|
};
|
|
1140
1237
|
}
|
|
1141
1238
|
});
|
|
@@ -1186,7 +1283,8 @@ async function loadCliConfig() {
|
|
|
1186
1283
|
apiUrl: apiUrlFromEnv ?? parsed.apiUrl ?? "https://api.kordis.store",
|
|
1187
1284
|
...parsed.username ? { username: parsed.username } : {},
|
|
1188
1285
|
...parsed.sessionId ? { sessionId: parsed.sessionId } : {},
|
|
1189
|
-
personalization: parsed.personalization ?? "undecided"
|
|
1286
|
+
personalization: parsed.personalization ?? "undecided",
|
|
1287
|
+
error_telemetry: parsed.error_telemetry ?? "undecided"
|
|
1190
1288
|
};
|
|
1191
1289
|
} catch {
|
|
1192
1290
|
return { apiUrl: apiUrlFromEnv ?? "https://api.kordis.store" };
|
|
@@ -1518,15 +1616,93 @@ async function getCachedContextHeader(cwd = process.cwd()) {
|
|
|
1518
1616
|
return header;
|
|
1519
1617
|
}
|
|
1520
1618
|
|
|
1619
|
+
// src/lib/error-reporter.ts
|
|
1620
|
+
init_src2();
|
|
1621
|
+
import { homedir as homedir4, platform } from "os";
|
|
1622
|
+
|
|
1623
|
+
// src/version.ts
|
|
1624
|
+
import { readFileSync } from "fs";
|
|
1625
|
+
import { fileURLToPath } from "url";
|
|
1626
|
+
import { dirname, join as join4 } from "path";
|
|
1627
|
+
var __dir = dirname(fileURLToPath(import.meta.url));
|
|
1628
|
+
var pkgPath = join4(__dir, "..", "package.json");
|
|
1629
|
+
var VERSION = (() => {
|
|
1630
|
+
try {
|
|
1631
|
+
const raw = readFileSync(pkgPath, "utf8");
|
|
1632
|
+
const parsed = JSON.parse(raw);
|
|
1633
|
+
return typeof parsed.version === "string" ? parsed.version : "0.0.0-unknown";
|
|
1634
|
+
} catch {
|
|
1635
|
+
return "0.0.0-unknown";
|
|
1636
|
+
}
|
|
1637
|
+
})();
|
|
1638
|
+
|
|
1639
|
+
// src/lib/error-reporter.ts
|
|
1640
|
+
var HOME = homedir4();
|
|
1641
|
+
var REPORT_TIMEOUT_MS = 3e3;
|
|
1642
|
+
function sanitizeMessage(raw) {
|
|
1643
|
+
let s = raw;
|
|
1644
|
+
if (HOME && HOME.length > 0) {
|
|
1645
|
+
s = s.split(HOME).join("~");
|
|
1646
|
+
}
|
|
1647
|
+
s = s.replace(/(\/[^\s'"]+){2,}/g, "<path>");
|
|
1648
|
+
s = s.replace(/[A-Za-z]:\\[^\s'"]+/g, "<path>");
|
|
1649
|
+
s = s.replace(/\?[^\s'"]+/g, "?<query>");
|
|
1650
|
+
s = s.replace(/eyJ[\w-]+\.[\w-]+\.[\w-]+/g, "<jwt>");
|
|
1651
|
+
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>");
|
|
1652
|
+
s = s.replace(/\b[0-9a-f]{32,}\b/gi, "<hex>");
|
|
1653
|
+
s = s.trim().slice(0, 200);
|
|
1654
|
+
return s.length === 0 ? "<empty>" : s;
|
|
1655
|
+
}
|
|
1656
|
+
function normalisePlatform() {
|
|
1657
|
+
const p = platform();
|
|
1658
|
+
if (p === "darwin" || p === "linux" || p === "win32") return p;
|
|
1659
|
+
return "other";
|
|
1660
|
+
}
|
|
1661
|
+
async function telemetryBaseUrl() {
|
|
1662
|
+
const override = process.env["KORDIS_TELEMETRY_URL"];
|
|
1663
|
+
if (override) return override;
|
|
1664
|
+
const cfg = await loadCliConfig();
|
|
1665
|
+
return cfg.apiUrl;
|
|
1666
|
+
}
|
|
1667
|
+
async function reportError(kind, err, extras = {}) {
|
|
1668
|
+
try {
|
|
1669
|
+
if (process.env["KORDIS_NO_TELEMETRY"] === "1") return;
|
|
1670
|
+
const cfg = await loadCliConfig();
|
|
1671
|
+
if (cfg.error_telemetry !== "on") return;
|
|
1672
|
+
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
1673
|
+
const detectedCode = extras.error_code ?? (err.code !== void 0 ? String(err.code).slice(0, 64) : void 0);
|
|
1674
|
+
const payload = {
|
|
1675
|
+
kind,
|
|
1676
|
+
cli_version: VERSION,
|
|
1677
|
+
node_version: process.version,
|
|
1678
|
+
platform: normalisePlatform(),
|
|
1679
|
+
message_excerpt: sanitizeMessage(rawMessage),
|
|
1680
|
+
...detectedCode ? { error_code: detectedCode } : {},
|
|
1681
|
+
...extras.elapsed_ms !== void 0 ? { elapsed_ms: extras.elapsed_ms } : {},
|
|
1682
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1683
|
+
};
|
|
1684
|
+
const baseUrl = await telemetryBaseUrl();
|
|
1685
|
+
const client = new KordisClient({
|
|
1686
|
+
baseUrl,
|
|
1687
|
+
userAgent: `kordis-cli/${VERSION}/telemetry node/${process.version}`
|
|
1688
|
+
});
|
|
1689
|
+
await Promise.race([
|
|
1690
|
+
client.recordTelemetryError(payload),
|
|
1691
|
+
new Promise((resolve2) => setTimeout(resolve2, REPORT_TIMEOUT_MS))
|
|
1692
|
+
]);
|
|
1693
|
+
} catch {
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1521
1697
|
// src/lib/pending-login.ts
|
|
1522
1698
|
import { readFile as readFile4, writeFile as writeFile3, unlink as unlink2, mkdir as mkdir3 } from "fs/promises";
|
|
1523
|
-
import { homedir as
|
|
1524
|
-
import { join as
|
|
1699
|
+
import { homedir as homedir5 } from "os";
|
|
1700
|
+
import { join as join5 } from "path";
|
|
1525
1701
|
function dir() {
|
|
1526
|
-
return process.env["KORDIS_HOME"] ??
|
|
1702
|
+
return process.env["KORDIS_HOME"] ?? join5(homedir5(), ".kordis");
|
|
1527
1703
|
}
|
|
1528
1704
|
function path() {
|
|
1529
|
-
return
|
|
1705
|
+
return join5(dir(), "pending-login.json");
|
|
1530
1706
|
}
|
|
1531
1707
|
async function readPending() {
|
|
1532
1708
|
try {
|
|
@@ -1552,22 +1728,6 @@ function isExpired(file) {
|
|
|
1552
1728
|
return Math.floor(Date.now() / 1e3) >= file.expires_at_epoch;
|
|
1553
1729
|
}
|
|
1554
1730
|
|
|
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
1731
|
// src/client.ts
|
|
1572
1732
|
async function useClient() {
|
|
1573
1733
|
let cfg = await loadCliConfig();
|
|
@@ -1599,6 +1759,7 @@ async function useClient() {
|
|
|
1599
1759
|
process.stderr.write(`[kordis] auto-finalize: poll failed (code=${code ?? "none"}): ${msg}
|
|
1600
1760
|
`);
|
|
1601
1761
|
}
|
|
1762
|
+
void reportError("auto_finalize_stage1_fail", err);
|
|
1602
1763
|
}
|
|
1603
1764
|
}
|
|
1604
1765
|
if (pollResult) {
|
|
@@ -1628,9 +1789,10 @@ async function useClient() {
|
|
|
1628
1789
|
username = null;
|
|
1629
1790
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1630
1791
|
process.stderr.write(
|
|
1631
|
-
`[kordis] auto-finalize: server issued tokens but local persistence failed (${msg}). Run \`kordis login --emit-only\` again to retry.
|
|
1792
|
+
`[kordis] auto-finalize: server issued tokens but local persistence failed (${msg}). Run \`kordis login --emit-only\` again to retry. If this keeps happening on macOS, the OS keychain prompt may be timing out \u2014 set \`KORDIS_NO_KEYCHAIN=1\` to fall back to a plaintext file at ~/.kordis/.tokens.json (mode 0600). Set \`KORDIS_DEBUG=1\` for full auto-finalize stage output.
|
|
1632
1793
|
`
|
|
1633
1794
|
);
|
|
1795
|
+
void reportError("auto_finalize_stage2_fail", err);
|
|
1634
1796
|
}
|
|
1635
1797
|
}
|
|
1636
1798
|
}
|
|
@@ -1821,7 +1983,7 @@ function requireAuth(bundle) {
|
|
|
1821
1983
|
error: {
|
|
1822
1984
|
code: "not_authenticated",
|
|
1823
1985
|
message: "authentication required",
|
|
1824
|
-
hint: "Run `kordis login --emit-only` to start browser sign-in (exits immediately, no blocking poll), then re-run this command \u2014 the next CLI call auto-claims the token. For interactive shells, plain `kordis login` (10-min poll) also works."
|
|
1986
|
+
hint: "Run `kordis login --emit-only` to start browser sign-in (exits immediately, no blocking poll), then re-run this command \u2014 the next CLI call auto-claims the token. For interactive shells, plain `kordis login` (10-min poll) also works. If you DID login but auth keeps failing, run with `KORDIS_DEBUG=1` to surface auto-finalize stages (Round 12 F1 \u2014 diagnostic moment-of-need)."
|
|
1825
1987
|
}
|
|
1826
1988
|
});
|
|
1827
1989
|
}
|
|
@@ -1848,10 +2010,10 @@ import { Command as Command3 } from "commander";
|
|
|
1848
2010
|
|
|
1849
2011
|
// src/lib/recommendation-cache.ts
|
|
1850
2012
|
import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
|
|
1851
|
-
import { homedir as
|
|
2013
|
+
import { homedir as homedir6 } from "os";
|
|
1852
2014
|
import { join as join6 } from "path";
|
|
1853
2015
|
function cacheDir() {
|
|
1854
|
-
return process.env["KORDIS_HOME"] ?? join6(
|
|
2016
|
+
return process.env["KORDIS_HOME"] ?? join6(homedir6(), ".kordis");
|
|
1855
2017
|
}
|
|
1856
2018
|
function cachePath() {
|
|
1857
2019
|
return join6(cacheDir(), "recommendations.json");
|
|
@@ -2107,9 +2269,9 @@ var FrontmatterParseError = class extends Error {
|
|
|
2107
2269
|
|
|
2108
2270
|
// ../../packages/harness-schema/src/deps.ts
|
|
2109
2271
|
init_src();
|
|
2110
|
-
import { z as
|
|
2272
|
+
import { z as z13 } from "zod";
|
|
2111
2273
|
var StringDepRe = /^([a-z0-9](-?[a-z0-9])*\/[a-z0-9](-?[a-z0-9])*)(?:@(.+))?$/;
|
|
2112
|
-
var ObjectDepSchema =
|
|
2274
|
+
var ObjectDepSchema = z13.object({
|
|
2113
2275
|
full_slug: FullSlugSchema,
|
|
2114
2276
|
version_range: SemverRangeSchema.default("*")
|
|
2115
2277
|
});
|
|
@@ -2145,50 +2307,50 @@ init_src();
|
|
|
2145
2307
|
|
|
2146
2308
|
// ../../packages/harness-schema/src/types/skill.ts
|
|
2147
2309
|
init_src();
|
|
2148
|
-
import { z as
|
|
2149
|
-
var SkillFrontmatterSchema =
|
|
2150
|
-
name:
|
|
2151
|
-
description:
|
|
2310
|
+
import { z as z14 } from "zod";
|
|
2311
|
+
var SkillFrontmatterSchema = z14.object({
|
|
2312
|
+
name: z14.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "name must be lowercase-with-hyphens (Agent Skills spec)"),
|
|
2313
|
+
description: z14.string().min(1).max(2048),
|
|
2152
2314
|
version: SemverSchema.optional(),
|
|
2153
2315
|
// Agent Skills optional metadata
|
|
2154
|
-
license:
|
|
2155
|
-
homepage:
|
|
2156
|
-
model:
|
|
2316
|
+
license: z14.string().max(64).optional(),
|
|
2317
|
+
homepage: z14.string().url().optional(),
|
|
2318
|
+
model: z14.string().optional(),
|
|
2157
2319
|
// model override hint
|
|
2158
|
-
"allowed-tools":
|
|
2159
|
-
"disable-model-invocation":
|
|
2320
|
+
"allowed-tools": z14.array(z14.string()).optional(),
|
|
2321
|
+
"disable-model-invocation": z14.boolean().optional(),
|
|
2160
2322
|
// Kordis-forwarded cross-type fields (kept minimal to not pollute the spec)
|
|
2161
|
-
title:
|
|
2162
|
-
tags:
|
|
2163
|
-
tools:
|
|
2323
|
+
title: z14.string().min(1).max(200).optional(),
|
|
2324
|
+
tags: z14.array(TagSchema).max(16).optional(),
|
|
2325
|
+
tools: z14.array(ToolSlugSchema).optional()
|
|
2164
2326
|
}).strict();
|
|
2165
2327
|
|
|
2166
2328
|
// ../../packages/harness-schema/src/types/mcp-server.ts
|
|
2167
2329
|
init_src();
|
|
2168
|
-
import { z as
|
|
2169
|
-
var McpTransportSchema =
|
|
2170
|
-
|
|
2171
|
-
type:
|
|
2172
|
-
command:
|
|
2173
|
-
args:
|
|
2174
|
-
env:
|
|
2330
|
+
import { z as z15 } from "zod";
|
|
2331
|
+
var McpTransportSchema = z15.union([
|
|
2332
|
+
z15.object({
|
|
2333
|
+
type: z15.literal("stdio"),
|
|
2334
|
+
command: z15.string().min(1),
|
|
2335
|
+
args: z15.array(z15.string()).optional().default([]),
|
|
2336
|
+
env: z15.record(z15.string()).optional().default({})
|
|
2175
2337
|
}),
|
|
2176
|
-
|
|
2177
|
-
type:
|
|
2178
|
-
url:
|
|
2179
|
-
headers:
|
|
2338
|
+
z15.object({
|
|
2339
|
+
type: z15.enum(["http", "sse"]),
|
|
2340
|
+
url: z15.string().url(),
|
|
2341
|
+
headers: z15.record(z15.string()).optional().default({})
|
|
2180
2342
|
})
|
|
2181
2343
|
]);
|
|
2182
|
-
var McpCapabilitiesSchema =
|
|
2183
|
-
tools:
|
|
2184
|
-
resources:
|
|
2185
|
-
prompts:
|
|
2344
|
+
var McpCapabilitiesSchema = z15.object({
|
|
2345
|
+
tools: z15.array(z15.string()).optional().default([]),
|
|
2346
|
+
resources: z15.array(z15.string()).optional().default([]),
|
|
2347
|
+
prompts: z15.array(z15.string()).optional().default([])
|
|
2186
2348
|
});
|
|
2187
|
-
var McpServerFrontmatterSchema =
|
|
2188
|
-
name:
|
|
2189
|
-
description:
|
|
2349
|
+
var McpServerFrontmatterSchema = z15.object({
|
|
2350
|
+
name: z15.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
|
2351
|
+
description: z15.string().min(1).max(2048),
|
|
2190
2352
|
version: SemverSchema,
|
|
2191
|
-
mcp_spec_version:
|
|
2353
|
+
mcp_spec_version: z15.string().default("2026-03"),
|
|
2192
2354
|
transport: McpTransportSchema,
|
|
2193
2355
|
capabilities: McpCapabilitiesSchema.optional().default({
|
|
2194
2356
|
tools: [],
|
|
@@ -2196,35 +2358,35 @@ var McpServerFrontmatterSchema = z14.object({
|
|
|
2196
2358
|
prompts: []
|
|
2197
2359
|
}),
|
|
2198
2360
|
/** Human-readable setup / auth instructions. Surfaced in `kordis info`. */
|
|
2199
|
-
auth_notes:
|
|
2361
|
+
auth_notes: z15.string().optional(),
|
|
2200
2362
|
/** Required env vars with description + whether they must be secret. */
|
|
2201
|
-
env_schema:
|
|
2202
|
-
|
|
2203
|
-
description:
|
|
2204
|
-
required:
|
|
2205
|
-
secret:
|
|
2363
|
+
env_schema: z15.record(
|
|
2364
|
+
z15.object({
|
|
2365
|
+
description: z15.string(),
|
|
2366
|
+
required: z15.boolean().default(true),
|
|
2367
|
+
secret: z15.boolean().default(false)
|
|
2206
2368
|
})
|
|
2207
2369
|
).optional(),
|
|
2208
|
-
homepage:
|
|
2209
|
-
repository:
|
|
2210
|
-
license:
|
|
2211
|
-
title:
|
|
2370
|
+
homepage: z15.string().url().optional(),
|
|
2371
|
+
repository: z15.string().url().optional(),
|
|
2372
|
+
license: z15.string().max(64).optional(),
|
|
2373
|
+
title: z15.string().min(1).max(200).optional()
|
|
2212
2374
|
}).strict();
|
|
2213
2375
|
|
|
2214
2376
|
// ../../packages/harness-schema/src/types/project-rules.ts
|
|
2215
2377
|
init_src();
|
|
2216
|
-
import { z as
|
|
2217
|
-
var ProjectRulesFrontmatterSchema =
|
|
2218
|
-
title:
|
|
2219
|
-
description:
|
|
2378
|
+
import { z as z16 } from "zod";
|
|
2379
|
+
var ProjectRulesFrontmatterSchema = z16.object({
|
|
2380
|
+
title: z16.string().min(1).max(200),
|
|
2381
|
+
description: z16.string().min(1).max(2048),
|
|
2220
2382
|
version: SemverSchema.optional(),
|
|
2221
|
-
scope:
|
|
2222
|
-
target_path:
|
|
2223
|
-
globs:
|
|
2224
|
-
target_tools:
|
|
2383
|
+
scope: z16.enum(["root", "nested", "globs"]).default("root"),
|
|
2384
|
+
target_path: z16.string().optional(),
|
|
2385
|
+
globs: z16.array(z16.string()).optional(),
|
|
2386
|
+
target_tools: z16.array(ToolSlugSchema).optional().default([]),
|
|
2225
2387
|
/** When true, installation is idempotent via `<!-- kordis:... -->` markers. */
|
|
2226
|
-
mergeable:
|
|
2227
|
-
license:
|
|
2388
|
+
mergeable: z16.boolean().default(true),
|
|
2389
|
+
license: z16.string().max(64).optional()
|
|
2228
2390
|
}).strict().refine(
|
|
2229
2391
|
(v) => v.scope !== "nested" || !!v.target_path,
|
|
2230
2392
|
{ message: 'scope="nested" requires target_path' }
|
|
@@ -2235,33 +2397,33 @@ var ProjectRulesFrontmatterSchema = z15.object({
|
|
|
2235
2397
|
|
|
2236
2398
|
// ../../packages/harness-schema/src/types/custom-agent.ts
|
|
2237
2399
|
init_src();
|
|
2238
|
-
import { z as
|
|
2239
|
-
var CustomAgentFrontmatterSchema =
|
|
2240
|
-
name:
|
|
2241
|
-
description:
|
|
2242
|
-
title:
|
|
2400
|
+
import { z as z17 } from "zod";
|
|
2401
|
+
var CustomAgentFrontmatterSchema = z17.object({
|
|
2402
|
+
name: z17.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
|
2403
|
+
description: z17.string().min(1).max(2048),
|
|
2404
|
+
title: z17.string().min(1).max(200).optional(),
|
|
2243
2405
|
version: SemverSchema.optional(),
|
|
2244
2406
|
/** System prompt that defines the agent's persona + rules. Required. */
|
|
2245
|
-
system_prompt:
|
|
2407
|
+
system_prompt: z17.string().min(1).max(5e4),
|
|
2246
2408
|
/** Optional model override — string passed verbatim to the host. */
|
|
2247
|
-
model:
|
|
2409
|
+
model: z17.string().optional(),
|
|
2248
2410
|
/** Tools this agent can call. Host-specific names (no uniform namespace). */
|
|
2249
|
-
allowed_tools:
|
|
2250
|
-
disallowed_tools:
|
|
2411
|
+
allowed_tools: z17.array(z17.string()).optional().default([]),
|
|
2412
|
+
disallowed_tools: z17.array(z17.string()).optional().default([]),
|
|
2251
2413
|
/** Fork/context behaviour hint (Claude: `context: fork` etc.). */
|
|
2252
|
-
context:
|
|
2414
|
+
context: z17.enum(["inherit", "fork", "isolated"]).optional(),
|
|
2253
2415
|
/** Known-good tool runtimes. Empty = untested everywhere. */
|
|
2254
|
-
tool_compat:
|
|
2416
|
+
tool_compat: z17.array(ToolSlugSchema).optional().default([]),
|
|
2255
2417
|
/** Pre-load skills when this agent activates. */
|
|
2256
|
-
preload_skills:
|
|
2257
|
-
license:
|
|
2258
|
-
homepage:
|
|
2418
|
+
preload_skills: z17.array(z17.string()).optional().default([]),
|
|
2419
|
+
license: z17.string().max(64).optional(),
|
|
2420
|
+
homepage: z17.string().url().optional()
|
|
2259
2421
|
}).strict();
|
|
2260
2422
|
|
|
2261
2423
|
// ../../packages/harness-schema/src/types/hook.ts
|
|
2262
2424
|
init_src();
|
|
2263
|
-
import { z as
|
|
2264
|
-
var HookEventSchema =
|
|
2425
|
+
import { z as z18 } from "zod";
|
|
2426
|
+
var HookEventSchema = z18.enum([
|
|
2265
2427
|
"PreToolUse",
|
|
2266
2428
|
"PostToolUse",
|
|
2267
2429
|
"UserPromptSubmit",
|
|
@@ -2273,37 +2435,37 @@ var HookEventSchema = z17.enum([
|
|
|
2273
2435
|
"SubagentStop",
|
|
2274
2436
|
"Background"
|
|
2275
2437
|
]);
|
|
2276
|
-
var HookActionSchema =
|
|
2277
|
-
|
|
2278
|
-
type:
|
|
2279
|
-
command:
|
|
2280
|
-
timeout_ms:
|
|
2281
|
-
blocking:
|
|
2438
|
+
var HookActionSchema = z18.union([
|
|
2439
|
+
z18.object({
|
|
2440
|
+
type: z18.literal("command"),
|
|
2441
|
+
command: z18.string().min(1),
|
|
2442
|
+
timeout_ms: z18.number().int().positive().max(6e5).optional(),
|
|
2443
|
+
blocking: z18.boolean().default(false)
|
|
2282
2444
|
}),
|
|
2283
|
-
|
|
2284
|
-
type:
|
|
2285
|
-
url:
|
|
2286
|
-
method:
|
|
2287
|
-
headers:
|
|
2288
|
-
timeout_ms:
|
|
2289
|
-
blocking:
|
|
2445
|
+
z18.object({
|
|
2446
|
+
type: z18.literal("http"),
|
|
2447
|
+
url: z18.string().url(),
|
|
2448
|
+
method: z18.enum(["GET", "POST", "PUT", "PATCH"]).default("POST"),
|
|
2449
|
+
headers: z18.record(z18.string()).optional().default({}),
|
|
2450
|
+
timeout_ms: z18.number().int().positive().max(6e5).optional(),
|
|
2451
|
+
blocking: z18.boolean().default(false)
|
|
2290
2452
|
})
|
|
2291
2453
|
]);
|
|
2292
|
-
var HookBindingSchema =
|
|
2454
|
+
var HookBindingSchema = z18.object({
|
|
2293
2455
|
event: HookEventSchema,
|
|
2294
|
-
matcher:
|
|
2456
|
+
matcher: z18.string().optional(),
|
|
2295
2457
|
action: HookActionSchema
|
|
2296
2458
|
});
|
|
2297
|
-
var HookFrontmatterSchema =
|
|
2298
|
-
name:
|
|
2299
|
-
description:
|
|
2300
|
-
title:
|
|
2459
|
+
var HookFrontmatterSchema = z18.object({
|
|
2460
|
+
name: z18.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
|
2461
|
+
description: z18.string().min(1).max(2048),
|
|
2462
|
+
title: z18.string().min(1).max(200).optional(),
|
|
2301
2463
|
version: SemverSchema.optional(),
|
|
2302
2464
|
/** One or more hook bindings. */
|
|
2303
|
-
hooks:
|
|
2465
|
+
hooks: z18.array(HookBindingSchema).min(1),
|
|
2304
2466
|
/** Declared to surface in search filters — always "claude-code" for now. */
|
|
2305
|
-
tool_compat:
|
|
2306
|
-
license:
|
|
2467
|
+
tool_compat: z18.array(z18.enum(["claude-code"])).default(["claude-code"]),
|
|
2468
|
+
license: z18.string().max(64).optional()
|
|
2307
2469
|
}).strict();
|
|
2308
2470
|
|
|
2309
2471
|
// ../../packages/harness-schema/src/validator.ts
|
|
@@ -3333,11 +3495,11 @@ function getTranslator(type, format) {
|
|
|
3333
3495
|
}
|
|
3334
3496
|
|
|
3335
3497
|
// src/lockfile.ts
|
|
3336
|
-
import { homedir as
|
|
3498
|
+
import { homedir as homedir7 } from "os";
|
|
3337
3499
|
import { join as join8 } from "path";
|
|
3338
3500
|
import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
|
|
3339
3501
|
function kordisHome2() {
|
|
3340
|
-
return process.env["KORDIS_HOME"] ?? join8(
|
|
3502
|
+
return process.env["KORDIS_HOME"] ?? join8(homedir7(), ".kordis");
|
|
3341
3503
|
}
|
|
3342
3504
|
function lockfilePath() {
|
|
3343
3505
|
return join8(kordisHome2(), "installed.json");
|
|
@@ -3621,7 +3783,7 @@ function logoutCommand() {
|
|
|
3621
3783
|
|
|
3622
3784
|
// src/commands/setup.ts
|
|
3623
3785
|
import { readFile as readFile8, writeFile as writeFile7, mkdir as mkdir7, access as access3 } from "fs/promises";
|
|
3624
|
-
import { homedir as
|
|
3786
|
+
import { homedir as homedir8 } from "os";
|
|
3625
3787
|
import { join as join10, dirname as dirname3 } from "path";
|
|
3626
3788
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3627
3789
|
import { Command as Command9 } from "commander";
|
|
@@ -3730,8 +3892,11 @@ function setupCommand() {
|
|
|
3730
3892
|
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
3893
|
"--personalization <choice>",
|
|
3732
3894
|
"record opt-in non-interactively: 'on' | 'off' (skips the Y/n prompt; safe for agents that just received user consent in chat)"
|
|
3895
|
+
).option(
|
|
3896
|
+
"--error-telemetry <choice>",
|
|
3897
|
+
"record opt-in non-interactively: 'on' | 'off' (skips the Y/n prompt; agent forwards consent received in chat)"
|
|
3733
3898
|
).action(async (opts) => {
|
|
3734
|
-
const home =
|
|
3899
|
+
const home = homedir8();
|
|
3735
3900
|
const dryRun = opts.dryRun ?? false;
|
|
3736
3901
|
if (opts.personalization !== void 0) {
|
|
3737
3902
|
const v = opts.personalization.toLowerCase();
|
|
@@ -3754,6 +3919,27 @@ function setupCommand() {
|
|
|
3754
3919
|
}
|
|
3755
3920
|
}
|
|
3756
3921
|
}
|
|
3922
|
+
if (opts.errorTelemetry !== void 0) {
|
|
3923
|
+
const v = opts.errorTelemetry.toLowerCase();
|
|
3924
|
+
if (v !== "on" && v !== "off") {
|
|
3925
|
+
throw new Error("--error-telemetry must be 'on' or 'off'");
|
|
3926
|
+
}
|
|
3927
|
+
if (!dryRun) {
|
|
3928
|
+
const cfg = await loadCliConfig();
|
|
3929
|
+
await saveCliConfig({ ...cfg, error_telemetry: v });
|
|
3930
|
+
}
|
|
3931
|
+
if (!opts.silent) {
|
|
3932
|
+
if (v === "on") {
|
|
3933
|
+
printHuman(
|
|
3934
|
+
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`")
|
|
3935
|
+
);
|
|
3936
|
+
} else {
|
|
3937
|
+
printHuman(
|
|
3938
|
+
c.gray("\u2192 error telemetry off \u2014 failures stay on your machine.")
|
|
3939
|
+
);
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3757
3943
|
let hubContent;
|
|
3758
3944
|
let subSkillContents;
|
|
3759
3945
|
try {
|
|
@@ -3859,6 +4045,30 @@ function setupCommand() {
|
|
|
3859
4045
|
);
|
|
3860
4046
|
}
|
|
3861
4047
|
}
|
|
4048
|
+
const cfg2 = await loadCliConfig();
|
|
4049
|
+
if (cfg2.error_telemetry === "undecided") {
|
|
4050
|
+
printHuman("");
|
|
4051
|
+
const { enable } = await prompts({
|
|
4052
|
+
type: "confirm",
|
|
4053
|
+
name: "enable",
|
|
4054
|
+
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.",
|
|
4055
|
+
initial: true
|
|
4056
|
+
});
|
|
4057
|
+
const newCfg = {
|
|
4058
|
+
...cfg2,
|
|
4059
|
+
error_telemetry: enable ? "on" : "off"
|
|
4060
|
+
};
|
|
4061
|
+
await saveCliConfig(newCfg);
|
|
4062
|
+
if (enable) {
|
|
4063
|
+
printHuman(
|
|
4064
|
+
c.green("\u2713 error telemetry on") + c.gray(" \u2014 disable any time: `KORDIS_NO_TELEMETRY=1` env or `kordis setup --error-telemetry off`")
|
|
4065
|
+
);
|
|
4066
|
+
} else {
|
|
4067
|
+
printHuman(
|
|
4068
|
+
c.gray("\u2192 error telemetry off \u2014 failures stay on your machine.")
|
|
4069
|
+
);
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
3862
4072
|
}
|
|
3863
4073
|
});
|
|
3864
4074
|
}
|
|
@@ -4588,7 +4798,16 @@ function buildProgram() {
|
|
|
4588
4798
|
const program = new Command19("kordis").version(VERSION).description("Kordis \u2014 registry for agentic-coding packages").option("--json", "emit JSON output to stdout (default: human)", false).hook("preAction", (thisCommand) => {
|
|
4589
4799
|
const opts = thisCommand.optsWithGlobals();
|
|
4590
4800
|
if (opts.json) setJsonMode(true);
|
|
4591
|
-
})
|
|
4801
|
+
}).addHelpText("after", `
|
|
4802
|
+
Environment variables (see README.md "Privacy & environment variables"):
|
|
4803
|
+
KORDIS_API_URL=<url> override registry endpoint (default: https://api.kordis.store)
|
|
4804
|
+
KORDIS_HOME=<dir> override config + token fallback dir (default: ~/.kordis)
|
|
4805
|
+
KORDIS_NO_CONTEXT=1 suppress personalization header even if opted in
|
|
4806
|
+
KORDIS_NO_TELEMETRY=1 suppress anonymized error telemetry even if opted in
|
|
4807
|
+
KORDIS_NO_KEYCHAIN=1 force plaintext token storage at ~/.kordis/.tokens.json
|
|
4808
|
+
(escape hatch for macOS keychain-prompt fatigue)
|
|
4809
|
+
KORDIS_DEBUG=1 surface auto-finalize stage outcomes to stderr (diagnostic)
|
|
4810
|
+
`);
|
|
4592
4811
|
program.addCommand(loginCommand());
|
|
4593
4812
|
program.addCommand(logoutCommand());
|
|
4594
4813
|
program.addCommand(whoamiCommand());
|
|
@@ -4668,6 +4887,7 @@ function handleError(err) {
|
|
|
4668
4887
|
printHuman(c.red(`\u2717 ${err.message}`));
|
|
4669
4888
|
for (const line of hint.split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
4670
4889
|
}
|
|
4890
|
+
void reportError("network_unreachable", err);
|
|
4671
4891
|
process.exit(err.exitCode());
|
|
4672
4892
|
}
|
|
4673
4893
|
if (err instanceof Error) {
|
|
@@ -4687,6 +4907,7 @@ function handleError(err) {
|
|
|
4687
4907
|
for (const line of hintStr.split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
4688
4908
|
}
|
|
4689
4909
|
}
|
|
4910
|
+
void reportError("unhandled_error", err);
|
|
4690
4911
|
process.exit(1);
|
|
4691
4912
|
}
|
|
4692
4913
|
if (isJsonMode()) printJson({ error: { code: "generic_failure", message: String(err) } });
|