@tiny-fish/cli 0.32.1-next.273 → 0.32.1-next.276
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/commands/doctor.js
CHANGED
|
@@ -342,9 +342,12 @@ export async function applyRepairs(repairs, options) {
|
|
|
342
342
|
}
|
|
343
343
|
return outcomes;
|
|
344
344
|
}
|
|
345
|
-
/**
|
|
346
|
-
|
|
345
|
+
/** Mirrors the route's cap; over it the payload would 400 and never be retried. */
|
|
346
|
+
const MAX_DURATION_MS = 3_600_000;
|
|
347
|
+
/** Enumerated fields only; check `detail` text never leaves the machine. */
|
|
348
|
+
function telemetryPayload(report, offered, outcomes, options, durationMs) {
|
|
347
349
|
const count = (status) => report.checks.filter((check) => check.status === status).length;
|
|
350
|
+
const failed = report.checks.filter((check) => check.status === 'fail');
|
|
348
351
|
return {
|
|
349
352
|
schema_version: report.schema_version,
|
|
350
353
|
cli_version: report.cli_version,
|
|
@@ -364,6 +367,12 @@ function telemetryPayload(report, offered, outcomes, options) {
|
|
|
364
367
|
harness_scope: options.harness ?? 'all',
|
|
365
368
|
// Same signal cli_connect_stage carries, so agent and human doctor runs split.
|
|
366
369
|
is_human_initiated: detectHumanInitiated(),
|
|
370
|
+
// Two flat arrays rather than one of pairs: PostHog groups by element, not by field.
|
|
371
|
+
failed_check_ids: [...new Set(failed.map((check) => check.id))],
|
|
372
|
+
failed_harnesses: [...new Set(failed.map((check) => check.harness).filter((h) => h !== null))],
|
|
373
|
+
harnesses: report.harnesses,
|
|
374
|
+
// Clamped, not validated: a --fix parked at a prompt must not fail the parse and drop the run.
|
|
375
|
+
duration_ms: Math.min(durationMs, MAX_DURATION_MS),
|
|
367
376
|
};
|
|
368
377
|
}
|
|
369
378
|
export function registerDoctor(program) {
|
|
@@ -390,6 +399,7 @@ export function registerDoctor(program) {
|
|
|
390
399
|
let report;
|
|
391
400
|
let offered;
|
|
392
401
|
let outcomes = [];
|
|
402
|
+
const startedAt = performance.now();
|
|
393
403
|
try {
|
|
394
404
|
report = await runDoctor(doctorOptions);
|
|
395
405
|
offered = report.repairs;
|
|
@@ -420,6 +430,6 @@ export function registerDoctor(program) {
|
|
|
420
430
|
process.exitCode = exitCodeFor(report.checks);
|
|
421
431
|
// After the exit code is set: telemetry never decides how doctor exits.
|
|
422
432
|
const resolved = apiKeyStatus();
|
|
423
|
-
await sendDoctorCompleted(doctorOptions.mcpUrl, telemetryPayload(report, offered, outcomes, doctorOptions), 'key' in resolved ? resolved.key : undefined);
|
|
433
|
+
await sendDoctorCompleted(doctorOptions.mcpUrl, telemetryPayload(report, offered, outcomes, doctorOptions, Math.round(performance.now() - startedAt)), 'key' in resolved ? resolved.key : undefined);
|
|
424
434
|
});
|
|
425
435
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import spawn from 'cross-spawn';
|
|
3
3
|
import { CONNECT_ATTEMPT_ENV, isAttemptId, resolvedApiKey, takePendingConnectAttempt, } from './auth.js';
|
|
4
|
-
import { CLI_VERSION } from './constants.js';
|
|
4
|
+
import { CLI_INVOCATION_ID, CLI_VERSION } from './constants.js';
|
|
5
5
|
import { detectHarness, detectHumanInitiated } from './harness.js';
|
|
6
6
|
import { errLine, sanitizeLine } from './output.js';
|
|
7
7
|
import { postConnectEvent, telemetryDisabled } from './setup-telemetry.js';
|
|
@@ -289,6 +289,7 @@ export function createConnectTelemetry(mcpUrl, client, opts) {
|
|
|
289
289
|
is_human_initiated: detectHumanInitiated(),
|
|
290
290
|
// "none", not absent — absent has to keep meaning "CLI too old".
|
|
291
291
|
calling_harness: detectHarness() ?? 'none',
|
|
292
|
+
cli_invocation_id: CLI_INVOCATION_ID,
|
|
292
293
|
})).catch((error) => {
|
|
293
294
|
// postConnectEvent is total, so this only fires if something inside deliver starts
|
|
294
295
|
// throwing. Same channel postConnectEvent uses for its own failures.
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare const CLI_VERSION: string;
|
|
2
|
+
/** One id per CLI process; joins events across commands (PF-3779). */
|
|
3
|
+
export declare const CLI_INVOCATION_ID: string;
|
|
2
4
|
/** Unversioned: the install site appends `@latest`, the version read must not. */
|
|
3
5
|
export declare const TINYFISH_CLI_PACKAGE = "@tiny-fish/cli";
|
|
4
6
|
export declare const CLI_AGENT_IDENTITY = "tinyfish-cli";
|
package/dist/lib/constants.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { createRequire } from 'module';
|
|
2
3
|
const _require = createRequire(import.meta.url);
|
|
3
4
|
const _pkg = _require('../../package.json');
|
|
4
5
|
export const CLI_VERSION = _pkg.version;
|
|
6
|
+
/** One id per CLI process; joins events across commands (PF-3779). */
|
|
7
|
+
export const CLI_INVOCATION_ID = randomUUID();
|
|
5
8
|
/** Unversioned: the install site appends `@latest`, the version read must not. */
|
|
6
9
|
export const TINYFISH_CLI_PACKAGE = '@tiny-fish/cli';
|
|
7
10
|
// How we identify ourselves to other tools; `skills` echoes it back.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { AuthMode, Registered } from './harness-detect.js';
|
|
2
3
|
/**
|
|
3
4
|
* `TINYFISH_NO_TELEMETRY` suppresses ALL CLI-emitted network analytics
|
|
4
5
|
* (setup_completed, connect-event, update check). Any value except `0`/`false`
|
|
@@ -111,6 +112,32 @@ export declare const doctorCompletedPayloadSchema: z.ZodObject<{
|
|
|
111
112
|
all: "all";
|
|
112
113
|
}>;
|
|
113
114
|
is_human_initiated: z.ZodBoolean;
|
|
115
|
+
failed_check_ids: z.ZodArray<z.ZodString>;
|
|
116
|
+
failed_harnesses: z.ZodArray<z.ZodEnum<{
|
|
117
|
+
openclaw: "openclaw";
|
|
118
|
+
grok: "grok";
|
|
119
|
+
cursor: "cursor";
|
|
120
|
+
codex: "codex";
|
|
121
|
+
hermes: "hermes";
|
|
122
|
+
opencode: "opencode";
|
|
123
|
+
"claude-code": "claude-code";
|
|
124
|
+
}>>;
|
|
125
|
+
harnesses: z.ZodArray<z.ZodObject<{
|
|
126
|
+
harness: z.ZodEnum<{
|
|
127
|
+
openclaw: "openclaw";
|
|
128
|
+
grok: "grok";
|
|
129
|
+
cursor: "cursor";
|
|
130
|
+
codex: "codex";
|
|
131
|
+
hermes: "hermes";
|
|
132
|
+
opencode: "opencode";
|
|
133
|
+
"claude-code": "claude-code";
|
|
134
|
+
}>;
|
|
135
|
+
detected: z.ZodBoolean;
|
|
136
|
+
registered: z.ZodEnum<typeof Registered>;
|
|
137
|
+
auth_mode: z.ZodEnum<typeof AuthMode>;
|
|
138
|
+
proves_harness_reach: z.ZodBoolean;
|
|
139
|
+
}, z.core.$strip>>;
|
|
140
|
+
duration_ms: z.ZodInt;
|
|
114
141
|
}, z.core.$strip>;
|
|
115
142
|
export type DoctorCompletedPayload = z.infer<typeof doctorCompletedPayloadSchema>;
|
|
116
143
|
export declare function sendDoctorCompleted(mcpUrl: string, payload: DoctorCompletedPayload, apiKey?: string): Promise<void>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { validateKeyFormat } from './auth.js';
|
|
4
|
-
import { BASE_URL, CLI_VERSION } from './constants.js';
|
|
5
|
-
import { ALL_HARNESSES } from './harness-detect.js';
|
|
4
|
+
import { BASE_URL, CLI_INVOCATION_ID, CLI_VERSION } from './constants.js';
|
|
5
|
+
import { ALL_HARNESSES, AuthMode, Registered } from './harness-detect.js';
|
|
6
|
+
import { detectHarness } from './harness.js';
|
|
6
7
|
import { captureNotice } from './notice.js';
|
|
7
8
|
import { errLine } from './output.js';
|
|
8
9
|
const CONNECT_EVENT_TIMEOUT_MS = 2_000;
|
|
@@ -114,6 +115,7 @@ export async function sendSetupStarted(mcpUrl, attemptId, apiKey) {
|
|
|
114
115
|
cli_version: CLI_VERSION,
|
|
115
116
|
runtime_platform: process.platform,
|
|
116
117
|
...(stamped ? { connect_attempt_id: stamped } : {}),
|
|
118
|
+
cli_invocation_id: CLI_INVOCATION_ID,
|
|
117
119
|
}),
|
|
118
120
|
attempts: 1,
|
|
119
121
|
label: 'setup',
|
|
@@ -142,6 +144,7 @@ export async function sendUpgradeCompleted(fromVersion, toVersion, outcome, scop
|
|
|
142
144
|
to_version: toVersion,
|
|
143
145
|
outcome,
|
|
144
146
|
scope,
|
|
147
|
+
cli_invocation_id: CLI_INVOCATION_ID,
|
|
145
148
|
}),
|
|
146
149
|
attempts: 1,
|
|
147
150
|
label: 'upgrade',
|
|
@@ -166,7 +169,11 @@ export async function sendSetupCompleted(mcpUrl, harnesses, apiKey, mode, attemp
|
|
|
166
169
|
: {}),
|
|
167
170
|
});
|
|
168
171
|
endpoint = new URL('/api/cli/connect-event', mcpUrl).toString();
|
|
169
|
-
body = JSON.stringify({
|
|
172
|
+
body = JSON.stringify({
|
|
173
|
+
event: 'setup_completed',
|
|
174
|
+
...payload,
|
|
175
|
+
cli_invocation_id: CLI_INVOCATION_ID,
|
|
176
|
+
});
|
|
170
177
|
}
|
|
171
178
|
catch (e) {
|
|
172
179
|
if (process.env['TINYFISH_DEBUG']) {
|
|
@@ -177,8 +184,8 @@ export async function sendSetupCompleted(mcpUrl, harnesses, apiKey, mode, attemp
|
|
|
177
184
|
await postConnectEvent({ endpoint, body, attempts: 2, label: 'setup', apiKey });
|
|
178
185
|
}
|
|
179
186
|
export const DOCTOR_HARNESS_SCOPES = ['all', ...ALL_HARNESSES];
|
|
180
|
-
//
|
|
181
|
-
// user-authored or credential-shaped can ride a doctor run to PostHog.
|
|
187
|
+
// Enumerated only: check ids, harness names and auth modes are authored here, so nothing
|
|
188
|
+
// user-authored or credential-shaped can ride a doctor run to PostHog. `detail` never leaves.
|
|
182
189
|
// Caps mirror the route's schema: a payload the server would 400 is dropped here instead,
|
|
183
190
|
// because postConnectEvent never retries a 4xx and the event would vanish silently.
|
|
184
191
|
export const doctorCompletedPayloadSchema = z.object({
|
|
@@ -199,6 +206,19 @@ export const doctorCompletedPayloadSchema = z.object({
|
|
|
199
206
|
/** "all" unless --harness scoped the run; the counts above cover only that scope. */
|
|
200
207
|
harness_scope: z.enum(DOCTOR_HARNESS_SCOPES),
|
|
201
208
|
is_human_initiated: z.boolean(),
|
|
209
|
+
/** Flat and distinct: PostHog cannot group by array elements of an object. */
|
|
210
|
+
failed_check_ids: z.array(z.string().regex(/^[a-z0-9-]{1,64}$/)).max(32),
|
|
211
|
+
failed_harnesses: z.array(z.enum(ALL_HARNESSES)).max(16),
|
|
212
|
+
harnesses: z
|
|
213
|
+
.array(z.object({
|
|
214
|
+
harness: z.enum(ALL_HARNESSES),
|
|
215
|
+
detected: z.boolean(),
|
|
216
|
+
registered: z.enum(Registered),
|
|
217
|
+
auth_mode: z.enum(AuthMode),
|
|
218
|
+
proves_harness_reach: z.boolean(),
|
|
219
|
+
}))
|
|
220
|
+
.max(16),
|
|
221
|
+
duration_ms: z.int().nonnegative().max(3_600_000),
|
|
202
222
|
});
|
|
203
223
|
export async function sendDoctorCompleted(mcpUrl, payload, apiKey) {
|
|
204
224
|
if (telemetryDisabled())
|
|
@@ -209,7 +229,15 @@ export async function sendDoctorCompleted(mcpUrl, payload, apiKey) {
|
|
|
209
229
|
// Parse inside try — a schema violation must not fail a diagnosis that already ran.
|
|
210
230
|
const parsed = doctorCompletedPayloadSchema.parse(payload);
|
|
211
231
|
endpoint = new URL('/api/cli/connect-event', mcpUrl).toString();
|
|
212
|
-
|
|
232
|
+
// Stamped after the parse: process-derived and bounded, cannot trip route caps.
|
|
233
|
+
body = JSON.stringify({
|
|
234
|
+
event: 'doctor_completed',
|
|
235
|
+
...parsed,
|
|
236
|
+
runtime_platform: process.platform,
|
|
237
|
+
node_version: process.version,
|
|
238
|
+
calling_harness: detectHarness() ?? 'none',
|
|
239
|
+
cli_invocation_id: CLI_INVOCATION_ID,
|
|
240
|
+
});
|
|
213
241
|
}
|
|
214
242
|
catch (e) {
|
|
215
243
|
if (process.env['TINYFISH_DEBUG']) {
|