codiedev 0.9.20 → 0.9.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/captureReadiness.d.ts +14 -1
- package/dist/captureReadiness.js +47 -4
- package/dist/commands/doctor.js +20 -4
- package/dist/commands/pull.d.ts +1 -0
- package/dist/commands/pull.js +32 -6
- package/dist/hook.d.ts +5 -1
- package/dist/hook.js +23 -9
- package/package.json +1 -1
|
@@ -36,7 +36,20 @@ export interface CaptureActivitySummary {
|
|
|
36
36
|
status: "pass" | "warn" | "fail";
|
|
37
37
|
detail: string;
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
/**
|
|
40
|
+
* When `text` (an error message, code, or capture.log detail) indicates a
|
|
41
|
+
* TLS-trust failure from SSL inspection, return the remediation hint;
|
|
42
|
+
* otherwise null.
|
|
43
|
+
*/
|
|
44
|
+
export declare function tlsInterceptionHint(text: string, host: string): string | null;
|
|
45
|
+
/**
|
|
46
|
+
* Flatten a thrown fetch/network error to "<message> (<code>)". undici's
|
|
47
|
+
* fetch wraps the real failure in TypeError("fetch failed") with the cause
|
|
48
|
+
* chained on `.cause` — walk to the deepest cause so doctor reports the
|
|
49
|
+
* actual TLS/socket error instead of "fetch failed".
|
|
50
|
+
*/
|
|
51
|
+
export declare function describeFetchError(err: unknown): string;
|
|
52
|
+
export declare function summarizeCaptureActivity(tailLines: string[], pendingJobCount: number, backendHost?: string): CaptureActivitySummary;
|
|
40
53
|
export interface ServerCaptureResponse {
|
|
41
54
|
found: boolean;
|
|
42
55
|
latest: {
|
package/dist/captureReadiness.js
CHANGED
|
@@ -11,6 +11,8 @@ exports.assessCaptureReadiness = assessCaptureReadiness;
|
|
|
11
11
|
exports.assessWorkspaceRepos = assessWorkspaceRepos;
|
|
12
12
|
exports.formatRelativeAge = formatRelativeAge;
|
|
13
13
|
exports.summarizeLastCapture = summarizeLastCapture;
|
|
14
|
+
exports.tlsInterceptionHint = tlsInterceptionHint;
|
|
15
|
+
exports.describeFetchError = describeFetchError;
|
|
14
16
|
exports.summarizeCaptureActivity = summarizeCaptureActivity;
|
|
15
17
|
exports.summarizeServerCapture = summarizeServerCapture;
|
|
16
18
|
function assessCaptureReadiness(cwd, repos, deps) {
|
|
@@ -109,15 +111,56 @@ const STRUCTURED_LINE_RE = /^(\S+) (capture-finish|upload|upload-deferred|stderr
|
|
|
109
111
|
function withSuffix(detail) {
|
|
110
112
|
return `${detail} ${LOG_PATH_SUFFIX}`;
|
|
111
113
|
}
|
|
114
|
+
// TLS-trust failure phrases/codes. These are what Node's TLS stack emits when
|
|
115
|
+
// a middlebox (corporate proxy / VPN doing SSL inspection) re-signs HTTPS with
|
|
116
|
+
// an internal CA. Node uses its OWN bundled Mozilla trust store — NOT the OS
|
|
117
|
+
// store — so installing the corporate cert system-wide fixes curl/git but not
|
|
118
|
+
// the CLI; only NODE_EXTRA_CA_CERTS (or an IT bypass) does.
|
|
119
|
+
// "certificate has expired" is deliberately absent: that's a server-side cert
|
|
120
|
+
// problem, not interception, and the hint would mislead.
|
|
121
|
+
const TLS_INTERCEPTION_RE = /self.signed certificate|unable to verify the first certificate|unable to get local issuer certificate|SELF_SIGNED_CERT_IN_CHAIN|UNABLE_TO_VERIFY_LEAF_SIGNATURE|UNABLE_TO_GET_ISSUER_CERT_LOCALLY|DEPTH_ZERO_SELF_SIGNED_CERT|CERT_UNTRUSTED/i;
|
|
122
|
+
/**
|
|
123
|
+
* When `text` (an error message, code, or capture.log detail) indicates a
|
|
124
|
+
* TLS-trust failure from SSL inspection, return the remediation hint;
|
|
125
|
+
* otherwise null.
|
|
126
|
+
*/
|
|
127
|
+
function tlsInterceptionHint(text, host) {
|
|
128
|
+
if (!TLS_INTERCEPTION_RE.test(text))
|
|
129
|
+
return null;
|
|
130
|
+
return (`your network is intercepting TLS (corporate proxy/VPN). Node ignores the OS cert store — ` +
|
|
131
|
+
`set NODE_EXTRA_CA_CERTS to your corporate root CA file, or ask IT to bypass SSL inspection for ${host}`);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Flatten a thrown fetch/network error to "<message> (<code>)". undici's
|
|
135
|
+
* fetch wraps the real failure in TypeError("fetch failed") with the cause
|
|
136
|
+
* chained on `.cause` — walk to the deepest cause so doctor reports the
|
|
137
|
+
* actual TLS/socket error instead of "fetch failed".
|
|
138
|
+
*/
|
|
139
|
+
function describeFetchError(err) {
|
|
140
|
+
if (err === null || err === undefined)
|
|
141
|
+
return "unknown error";
|
|
142
|
+
if (typeof err !== "object")
|
|
143
|
+
return String(err);
|
|
144
|
+
let deepest = err;
|
|
145
|
+
while (deepest.cause && typeof deepest.cause === "object") {
|
|
146
|
+
deepest = deepest.cause;
|
|
147
|
+
}
|
|
148
|
+
const message = deepest.message || String(deepest);
|
|
149
|
+
return deepest.code ? `${message} (${deepest.code})` : message;
|
|
150
|
+
}
|
|
112
151
|
/**
|
|
113
152
|
* Derive remediation text and status from the last failing upload-ish line.
|
|
114
153
|
* Inspects the `detail` field of the log line for http= and msg= tokens.
|
|
115
154
|
*/
|
|
116
|
-
function failDetail(rawLine, outcome, lineDetail) {
|
|
155
|
+
function failDetail(rawLine, outcome, lineDetail, backendHost) {
|
|
117
156
|
const d = lineDetail ?? "";
|
|
118
157
|
const truncated = rawLine.slice(0, 140);
|
|
158
|
+
const tlsHint = tlsInterceptionHint(d, backendHost ?? "the capture host");
|
|
119
159
|
let hint;
|
|
120
|
-
if (
|
|
160
|
+
if (tlsHint) {
|
|
161
|
+
hint = tlsHint;
|
|
162
|
+
}
|
|
163
|
+
else if (/http=40[13]/.test(d)) {
|
|
121
164
|
hint = `token invalid or expired — run \`codiedev connect\` with a fresh token`;
|
|
122
165
|
}
|
|
123
166
|
else if (/http=5/.test(d) || /fetch|network|ECONN|ETIMEDOUT/i.test(d)) {
|
|
@@ -131,7 +174,7 @@ function failDetail(rawLine, outcome, lineDetail) {
|
|
|
131
174
|
}
|
|
132
175
|
return withSuffix(`${hint} (${truncated})`);
|
|
133
176
|
}
|
|
134
|
-
function summarizeCaptureActivity(tailLines, pendingJobCount) {
|
|
177
|
+
function summarizeCaptureActivity(tailLines, pendingJobCount, backendHost) {
|
|
135
178
|
const parsed = [];
|
|
136
179
|
for (const raw of tailLines) {
|
|
137
180
|
const m = STRUCTURED_LINE_RE.exec(raw);
|
|
@@ -148,7 +191,7 @@ function summarizeCaptureActivity(tailLines, pendingJobCount) {
|
|
|
148
191
|
if (last.outcome === "ERROR" || last.outcome === "gave-up") {
|
|
149
192
|
return {
|
|
150
193
|
status: "fail",
|
|
151
|
-
detail: failDetail(last.raw, last.outcome, last.detail),
|
|
194
|
+
detail: failDetail(last.raw, last.outcome, last.detail, backendHost),
|
|
152
195
|
};
|
|
153
196
|
}
|
|
154
197
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -154,7 +154,17 @@ async function runDoctor(_args) {
|
|
|
154
154
|
status: "pass",
|
|
155
155
|
detail: `connected to ${config.companyName}`,
|
|
156
156
|
});
|
|
157
|
-
// 2. Token still valid + backend reachable (re-validate)
|
|
157
|
+
// 2. Token still valid + backend reachable (re-validate). The detail names
|
|
158
|
+
// the backend host so users (and their IT) can confirm which domain the CLI
|
|
159
|
+
// talks to — the same host capture uploads use, so this check exercises the
|
|
160
|
+
// exact TLS path a corporate proxy would intercept.
|
|
161
|
+
let backendHost = "the capture host";
|
|
162
|
+
try {
|
|
163
|
+
backendHost = new URL(config.backendUrl).host;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// malformed backendUrl in config — keep the generic placeholder
|
|
167
|
+
}
|
|
158
168
|
try {
|
|
159
169
|
const res = await fetch(`${config.backendUrl}/api/cli/validateToken`, {
|
|
160
170
|
method: "POST",
|
|
@@ -168,21 +178,27 @@ async function runDoctor(_args) {
|
|
|
168
178
|
checks.push({
|
|
169
179
|
name: "Backend reachable + token valid",
|
|
170
180
|
status: "pass",
|
|
181
|
+
detail: backendHost,
|
|
171
182
|
});
|
|
172
183
|
}
|
|
173
184
|
else {
|
|
174
185
|
checks.push({
|
|
175
186
|
name: "Backend reachable + token valid",
|
|
176
187
|
status: "fail",
|
|
177
|
-
detail: `HTTP ${res.status} — run \`codiedev connect\` with a fresh token`,
|
|
188
|
+
detail: `HTTP ${res.status} from ${backendHost} — run \`codiedev connect\` with a fresh token`,
|
|
178
189
|
});
|
|
179
190
|
}
|
|
180
191
|
}
|
|
181
192
|
catch (err) {
|
|
193
|
+
// fetch buries the real TLS/socket error in .cause ("fetch failed" alone
|
|
194
|
+
// is undiagnosable); unwrap it and, when it's an SSL-inspection signature,
|
|
195
|
+
// say what actually fixes it.
|
|
196
|
+
const desc = (0, captureReadiness_1.describeFetchError)(err);
|
|
197
|
+
const tlsHint = (0, captureReadiness_1.tlsInterceptionHint)(desc, backendHost);
|
|
182
198
|
checks.push({
|
|
183
199
|
name: "Backend reachable + token valid",
|
|
184
200
|
status: "fail",
|
|
185
|
-
detail:
|
|
201
|
+
detail: tlsHint ? `${desc} — ${tlsHint}` : `${desc} (host: ${backendHost})`,
|
|
186
202
|
});
|
|
187
203
|
}
|
|
188
204
|
// 3. Authenticated endpoint smoke test (inbox query)
|
|
@@ -322,7 +338,7 @@ async function runDoctor(_args) {
|
|
|
322
338
|
catch {
|
|
323
339
|
pendingJobCount = 0;
|
|
324
340
|
}
|
|
325
|
-
const captureActivity = (0, captureReadiness_1.summarizeCaptureActivity)(logTail, pendingJobCount);
|
|
341
|
+
const captureActivity = (0, captureReadiness_1.summarizeCaptureActivity)(logTail, pendingJobCount, backendHost);
|
|
326
342
|
checks.push({
|
|
327
343
|
name: "Recent capture activity",
|
|
328
344
|
status: captureActivity.status,
|
package/dist/commands/pull.d.ts
CHANGED
package/dist/commands/pull.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.candidateKeys = candidateKeys;
|
|
36
37
|
exports.runPull = runPull;
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
38
39
|
const path = __importStar(require("path"));
|
|
@@ -69,21 +70,45 @@ function parseArgs(args) {
|
|
|
69
70
|
}
|
|
70
71
|
return { key, version, out };
|
|
71
72
|
}
|
|
73
|
+
// The backend stores skills under extension-bearing keys (e.g. "pr-push.md"),
|
|
74
|
+
// and lookup is exact-match, so a bare "pr-push" 404s. Make pull forgiving: try
|
|
75
|
+
// the key as given, then a ".md" fallback when it has no extension, so the
|
|
76
|
+
// intuitive bare name (and the hook's suggested key) both resolve.
|
|
77
|
+
function candidateKeys(key) {
|
|
78
|
+
if (/\.[a-z0-9]+$/i.test(key))
|
|
79
|
+
return [key];
|
|
80
|
+
return [key, `${key}.md`];
|
|
81
|
+
}
|
|
72
82
|
async function runPull(args) {
|
|
73
83
|
const { key, version, out } = parseArgs(args);
|
|
74
84
|
const config = (0, shared_1.requireConfig)();
|
|
85
|
+
const keys = candidateKeys(key);
|
|
75
86
|
const start = Date.now();
|
|
76
87
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
let res;
|
|
89
|
+
for (let i = 0; i < keys.length; i++) {
|
|
90
|
+
try {
|
|
91
|
+
res = await (0, shared_1.apiRequest)("GET", "/api/cli/pull", {
|
|
92
|
+
config,
|
|
93
|
+
query: { key: keys[i], version },
|
|
94
|
+
});
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
const e = err;
|
|
99
|
+
// A 404 on a non-final candidate is retryable (fall through to ".md");
|
|
100
|
+
// anything else — or the last candidate — propagates to the handler.
|
|
101
|
+
if (e.status === 404 && i < keys.length - 1)
|
|
102
|
+
continue;
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const a = res.artifact;
|
|
81
107
|
(0, shared_1.recordAgentEvent)(config, {
|
|
82
108
|
tool: "codiedev_pull",
|
|
83
109
|
ok: true,
|
|
84
110
|
latencyMs: Date.now() - start,
|
|
85
111
|
});
|
|
86
|
-
const a = res.artifact;
|
|
87
112
|
if (out) {
|
|
88
113
|
const absolute = path.isAbsolute(out) ? out : path.resolve(process.cwd(), out);
|
|
89
114
|
fs.writeFileSync(absolute, a.markdown, "utf8");
|
|
@@ -108,7 +133,8 @@ async function runPull(args) {
|
|
|
108
133
|
error: e.message,
|
|
109
134
|
});
|
|
110
135
|
if (e.status === 404) {
|
|
111
|
-
|
|
136
|
+
const also = keys.length > 1 ? ` (also tried ${keys.slice(1).join(", ")})` : "";
|
|
137
|
+
console.error(`Not found: ${key}${version ? ` v${version}` : ""}${also}`);
|
|
112
138
|
}
|
|
113
139
|
else {
|
|
114
140
|
console.error(`Pull failed: ${e.message}`);
|
package/dist/hook.d.ts
CHANGED
package/dist/hook.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
};
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.buildDiscoveryContext = buildDiscoveryContext;
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
38
39
|
const os = __importStar(require("os"));
|
|
39
40
|
const path = __importStar(require("path"));
|
|
@@ -74,6 +75,26 @@ function getJson(url, bearerToken) {
|
|
|
74
75
|
req.end();
|
|
75
76
|
});
|
|
76
77
|
}
|
|
78
|
+
// Pure builder for the discovery additionalContext. Returns null when there are
|
|
79
|
+
// no matches. The suggested `codiedev pull` command carries the EXACT top key
|
|
80
|
+
// (e.g. "pr-push.md") rather than a "<key>" placeholder — otherwise agents
|
|
81
|
+
// paraphrase the key (pr-push) and the extension-sensitive `pull` 404s.
|
|
82
|
+
function buildDiscoveryContext(skills) {
|
|
83
|
+
if (skills.length === 0)
|
|
84
|
+
return null;
|
|
85
|
+
// Don't trust the API's ordering — rank by score so the suggested pull key is
|
|
86
|
+
// always the highest-confidence match.
|
|
87
|
+
const top = [...skills].sort((a, b) => b.score - a.score).slice(0, 3);
|
|
88
|
+
const lines = top
|
|
89
|
+
.map((s) => `- ${s.key} — ${(s.score * 100).toFixed(0)}% match`)
|
|
90
|
+
.join("\n");
|
|
91
|
+
return ("Your team has a CodieDev skill that fits this task — prefer it over improvising:\n" +
|
|
92
|
+
lines +
|
|
93
|
+
"\nPull and follow it now: `codiedev pull " +
|
|
94
|
+
top[0].key +
|
|
95
|
+
"`. It encodes the team's vetted " +
|
|
96
|
+
"approach. Skip it only if it clearly doesn't apply.");
|
|
97
|
+
}
|
|
77
98
|
// UserPromptSubmit: query skill discovery for the prompt and, if a team skill
|
|
78
99
|
// clears the confidence threshold, inject it as additionalContext so the agent
|
|
79
100
|
// can offer to pull+run it. Fully silent on miss/error — never blocks input.
|
|
@@ -86,15 +107,8 @@ async function runDiscover(prompt) {
|
|
|
86
107
|
try {
|
|
87
108
|
const res = await getJson(`${config.backendUrl}/api/cli/skill-discovery?q=${encodeURIComponent(prompt)}`, config.token);
|
|
88
109
|
const skills = res?.skills ?? [];
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
.slice(0, 3)
|
|
92
|
-
.map((s) => `- ${s.key} — ${(s.score * 100).toFixed(0)}% match`)
|
|
93
|
-
.join("\n");
|
|
94
|
-
const additionalContext = "Your team has a CodieDev skill that fits this task — prefer it over improvising:\n" +
|
|
95
|
-
lines +
|
|
96
|
-
"\nPull and follow it now: `codiedev pull <key>`. It encodes the team's vetted " +
|
|
97
|
-
"approach. Skip it only if it clearly doesn't apply.";
|
|
110
|
+
const additionalContext = buildDiscoveryContext(skills);
|
|
111
|
+
if (additionalContext) {
|
|
98
112
|
process.stdout.write(JSON.stringify({
|
|
99
113
|
hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext },
|
|
100
114
|
}));
|
package/package.json
CHANGED