@tiny-fish/cli 0.33.1-next.277 → 0.33.1-next.280
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/search.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { getApiKey } from '../lib/auth.js';
|
|
2
2
|
import { searchQuery } from '../lib/client.js';
|
|
3
|
-
import { handleApiError, out, outLine } from '../lib/output.js';
|
|
3
|
+
import { err, handleApiError, out, outLine } from '../lib/output.js';
|
|
4
4
|
function printPrettySearch(response) {
|
|
5
5
|
outLine(`Query: ${response.query}`);
|
|
6
6
|
outLine(`Total results: ${response.total_results}`);
|
|
7
|
+
outLine(`Page: ${response.page}`);
|
|
7
8
|
outLine('');
|
|
8
9
|
if (response.results.length === 0) {
|
|
9
10
|
outLine('No results.');
|
|
@@ -16,6 +17,16 @@ function printPrettySearch(response) {
|
|
|
16
17
|
outLine('');
|
|
17
18
|
}
|
|
18
19
|
}
|
|
20
|
+
// Blank strings coerce to 0, a valid page.
|
|
21
|
+
function parsePageOption(value) {
|
|
22
|
+
if (value === undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (!/^\d+$/.test(value) || Number(value) > 10) {
|
|
25
|
+
err({ error: `Invalid --page "${value}". Must be an integer between 0 and 10.` });
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
return Number(value);
|
|
29
|
+
}
|
|
19
30
|
export function registerSearch(program) {
|
|
20
31
|
const searchCmd = program
|
|
21
32
|
.command('search')
|
|
@@ -29,8 +40,10 @@ export function registerSearch(program) {
|
|
|
29
40
|
.option('--language <value>', 'Search language hint')
|
|
30
41
|
.option('--include-domains <value>', 'Comma-separated list of domains to restrict results to')
|
|
31
42
|
.option('--exclude-domains <value>', 'Comma-separated list of domains to exclude from results')
|
|
43
|
+
.option('--page <value>', 'Page number for pagination, starting from 0')
|
|
32
44
|
.option('--pretty', 'Human-readable output')
|
|
33
45
|
.action(async (query, opts) => {
|
|
46
|
+
const page = parsePageOption(opts.page);
|
|
34
47
|
const apiKey = getApiKey();
|
|
35
48
|
try {
|
|
36
49
|
const response = await searchQuery({
|
|
@@ -39,6 +52,7 @@ export function registerSearch(program) {
|
|
|
39
52
|
...(opts.language !== undefined ? { language: opts.language } : {}),
|
|
40
53
|
...(opts.includeDomains !== undefined ? { include_domains: opts.includeDomains } : {}),
|
|
41
54
|
...(opts.excludeDomains !== undefined ? { exclude_domains: opts.excludeDomains } : {}),
|
|
55
|
+
...(page !== undefined ? { page } : {}),
|
|
42
56
|
}, apiKey);
|
|
43
57
|
if (opts.pretty) {
|
|
44
58
|
printPrettySearch(response);
|
|
@@ -2,7 +2,7 @@ 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
4
|
import { CLI_INVOCATION_ID, CLI_VERSION } from './constants.js';
|
|
5
|
-
import { detectHarness, detectHumanInitiated } from './harness.js';
|
|
5
|
+
import { detectHarness, detectHumanInitiated, detectRuntimeEnv } from './harness.js';
|
|
6
6
|
import { errLine, sanitizeLine } from './output.js';
|
|
7
7
|
import { postConnectEvent, telemetryDisabled } from './setup-telemetry.js';
|
|
8
8
|
import { installSignalGuard } from './signals.js';
|
|
@@ -290,6 +290,7 @@ export function createConnectTelemetry(mcpUrl, client, opts) {
|
|
|
290
290
|
// "none", not absent — absent has to keep meaning "CLI too old".
|
|
291
291
|
calling_harness: detectHarness() ?? 'none',
|
|
292
292
|
cli_invocation_id: CLI_INVOCATION_ID,
|
|
293
|
+
runtime_env: detectRuntimeEnv(),
|
|
293
294
|
})).catch((error) => {
|
|
294
295
|
// postConnectEvent is total, so this only fires if something inside deliver starts
|
|
295
296
|
// throwing. Same channel postConnectEvent uses for its own failures.
|
package/dist/lib/harness.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
type Env = Record<string, string | undefined>;
|
|
2
2
|
/** Returns the detected harness name, or null when none match. */
|
|
3
3
|
export declare function detectHarness(env?: Env): string | null;
|
|
4
|
+
export type RuntimeEnv = 'container' | 'desktop';
|
|
5
|
+
interface FsApi {
|
|
6
|
+
existsSync: (path: string) => boolean;
|
|
7
|
+
readFileSync: (path: string, encoding: 'utf8') => string;
|
|
8
|
+
}
|
|
9
|
+
/** Fails open to desktop: a probe error must never break telemetry. */
|
|
10
|
+
export declare function detectRuntimeEnv(env?: Env, fsApi?: FsApi): RuntimeEnv;
|
|
4
11
|
interface HumanInitiatedStreams {
|
|
5
12
|
stdin: {
|
|
6
13
|
isTTY?: boolean;
|
package/dist/lib/harness.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// Order matters: wrappers that embed another agent must precede the embedded
|
|
8
8
|
// one. OpenClaw drives Claude Code via `sdk-cli`, so its env carries both
|
|
9
9
|
// CLAUDECODE=1 and OPENCLAW_MCP_*; it must be checked before claude-code.
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
11
|
const hasVarWithPrefix = (env, prefix) => Object.keys(env).some((key) => key.startsWith(prefix));
|
|
11
12
|
// A new name here must also join KNOWN_CALLING_HARNESSES in
|
|
12
13
|
// frontend/app/api/cli/connect-event/route.ts, or connect telemetry buckets it as `other`.
|
|
@@ -34,6 +35,77 @@ const HARNESS_FINGERPRINTS = [
|
|
|
34
35
|
export function detectHarness(env = process.env) {
|
|
35
36
|
return HARNESS_FINGERPRINTS.find((fingerprint) => fingerprint.matches(env))?.name ?? null;
|
|
36
37
|
}
|
|
38
|
+
// Markers verified live (PF-3778); exact names only — substring "SANDBOX" misfires.
|
|
39
|
+
const SANDBOX_ENV_VARS = [
|
|
40
|
+
// Codex cloud task container.
|
|
41
|
+
'CODEX_PROXY_CERT',
|
|
42
|
+
// ChatGPT agent-mode container; absent in some sessions, NETWORK below backstops.
|
|
43
|
+
'CAAS_ARTIFACTORY_BASE_URL',
|
|
44
|
+
'CLAUDE_CODE_REMOTE_SESSION_ID',
|
|
45
|
+
'E2B_SANDBOX',
|
|
46
|
+
'MODAL_TASK_ID',
|
|
47
|
+
'MODAL_SANDBOX_ID',
|
|
48
|
+
];
|
|
49
|
+
// Real humans in containerized dev clouds; still container.
|
|
50
|
+
const DEV_CLOUD_ENV_VARS = [
|
|
51
|
+
'CODESPACES',
|
|
52
|
+
'GITPOD_WORKSPACE_ID',
|
|
53
|
+
'CLOUD_SHELL',
|
|
54
|
+
'REPL_ID',
|
|
55
|
+
'KUBERNETES_SERVICE_HOST',
|
|
56
|
+
];
|
|
57
|
+
const CONTAINER_FILES = ['/.dockerenv', '/run/.containerenv'];
|
|
58
|
+
// Container-side line shape only: host mountinfo carries .../containers/<id>/mounts/shm.
|
|
59
|
+
const MOUNTINFO_MARKER = /\/docker\/containers\/[0-9a-f]{64}\/(?:resolv\.conf|hostname|hosts)/;
|
|
60
|
+
const CGROUP_MARKERS = /docker|containerd|kubepods|libpod|lxc/;
|
|
61
|
+
// Per-read isolation: a missing mountinfo must not skip the cgroup probe.
|
|
62
|
+
const safeRead = (fsApi, path) => {
|
|
63
|
+
try {
|
|
64
|
+
return fsApi.readFileSync(path, 'utf8');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
// One verdict per process, like CLI_INVOCATION_ID: events must never disagree.
|
|
71
|
+
let cachedRuntimeEnv;
|
|
72
|
+
// CODEX_CI / CODEX_SANDBOX* / CLAUDECODE mean agent-spawned, not containerized: never here.
|
|
73
|
+
/** Fails open to desktop: a probe error must never break telemetry. */
|
|
74
|
+
export function detectRuntimeEnv(env, fsApi) {
|
|
75
|
+
// Injected args bypass the cache so tests stay independent.
|
|
76
|
+
if (env || fsApi) {
|
|
77
|
+
return computeRuntimeEnv(env ?? process.env, fsApi ?? { existsSync, readFileSync });
|
|
78
|
+
}
|
|
79
|
+
cachedRuntimeEnv ??= computeRuntimeEnv(process.env, { existsSync, readFileSync });
|
|
80
|
+
return cachedRuntimeEnv;
|
|
81
|
+
}
|
|
82
|
+
function computeRuntimeEnv(env, fsApi) {
|
|
83
|
+
// Docs guarantee it is never true locally.
|
|
84
|
+
if (env['CLAUDE_CODE_REMOTE'] === 'true')
|
|
85
|
+
return 'container';
|
|
86
|
+
// Generic name, so match the value (ChatGPT agent mode).
|
|
87
|
+
if (env['NETWORK'] === 'caas_packages_only')
|
|
88
|
+
return 'container';
|
|
89
|
+
if (SANDBOX_ENV_VARS.some((name) => env[name]))
|
|
90
|
+
return 'container';
|
|
91
|
+
if (hasVarWithPrefix(env, 'CODEX_ENV_'))
|
|
92
|
+
return 'container';
|
|
93
|
+
if (DEV_CLOUD_ENV_VARS.some((name) => env[name]))
|
|
94
|
+
return 'container';
|
|
95
|
+
try {
|
|
96
|
+
if (CONTAINER_FILES.some((file) => fsApi.existsSync(file)))
|
|
97
|
+
return 'container';
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// A failed probe is not evidence of a container.
|
|
101
|
+
}
|
|
102
|
+
// Modern docker cgroup is bare 0::/; mountinfo still carries the path.
|
|
103
|
+
if (MOUNTINFO_MARKER.test(safeRead(fsApi, '/proc/self/mountinfo')))
|
|
104
|
+
return 'container';
|
|
105
|
+
if (CGROUP_MARKERS.test(safeRead(fsApi, '/proc/self/cgroup')))
|
|
106
|
+
return 'container';
|
|
107
|
+
return 'desktop';
|
|
108
|
+
}
|
|
37
109
|
/**
|
|
38
110
|
* True only when both ends are a real terminal — a human at a prompt. Agents
|
|
39
111
|
* shell out without a TTY, so this reads false for every harness above and is
|
|
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { validateKeyFormat } from './auth.js';
|
|
4
4
|
import { BASE_URL, CLI_INVOCATION_ID, CLI_VERSION } from './constants.js';
|
|
5
5
|
import { ALL_HARNESSES, AuthMode, Registered } from './harness-detect.js';
|
|
6
|
-
import { detectHarness } from './harness.js';
|
|
6
|
+
import { detectHarness, detectRuntimeEnv } from './harness.js';
|
|
7
7
|
import { captureNotice } from './notice.js';
|
|
8
8
|
import { errLine } from './output.js';
|
|
9
9
|
const CONNECT_EVENT_TIMEOUT_MS = 2_000;
|
|
@@ -116,6 +116,7 @@ export async function sendSetupStarted(mcpUrl, attemptId, apiKey) {
|
|
|
116
116
|
runtime_platform: process.platform,
|
|
117
117
|
...(stamped ? { connect_attempt_id: stamped } : {}),
|
|
118
118
|
cli_invocation_id: CLI_INVOCATION_ID,
|
|
119
|
+
runtime_env: detectRuntimeEnv(),
|
|
119
120
|
}),
|
|
120
121
|
attempts: 1,
|
|
121
122
|
label: 'setup',
|
|
@@ -145,6 +146,7 @@ export async function sendUpgradeCompleted(fromVersion, toVersion, outcome, scop
|
|
|
145
146
|
outcome,
|
|
146
147
|
scope,
|
|
147
148
|
cli_invocation_id: CLI_INVOCATION_ID,
|
|
149
|
+
runtime_env: detectRuntimeEnv(),
|
|
148
150
|
}),
|
|
149
151
|
attempts: 1,
|
|
150
152
|
label: 'upgrade',
|
|
@@ -173,6 +175,7 @@ export async function sendSetupCompleted(mcpUrl, harnesses, apiKey, mode, attemp
|
|
|
173
175
|
event: 'setup_completed',
|
|
174
176
|
...payload,
|
|
175
177
|
cli_invocation_id: CLI_INVOCATION_ID,
|
|
178
|
+
runtime_env: detectRuntimeEnv(),
|
|
176
179
|
});
|
|
177
180
|
}
|
|
178
181
|
catch (e) {
|
|
@@ -237,6 +240,7 @@ export async function sendDoctorCompleted(mcpUrl, payload, apiKey) {
|
|
|
237
240
|
node_version: process.version,
|
|
238
241
|
calling_harness: detectHarness() ?? 'none',
|
|
239
242
|
cli_invocation_id: CLI_INVOCATION_ID,
|
|
243
|
+
runtime_env: detectRuntimeEnv(),
|
|
240
244
|
});
|
|
241
245
|
}
|
|
242
246
|
catch (e) {
|