@tiny-fish/cli 0.46.1-next.360 → 0.46.1-next.363

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.
Files changed (38) hide show
  1. package/dist/commands/connect.js +1 -1
  2. package/dist/commands/doctor.js +1 -1
  3. package/dist/commands/onboard.js +1 -1
  4. package/dist/commands/profile.js +74 -15
  5. package/dist/lib/config/connect-config-file.js +1 -1
  6. package/dist/lib/config/opencode-config.d.ts +1 -1
  7. package/dist/lib/config/opencode-config.js +2 -2
  8. package/dist/lib/connect-all-auth.js +2 -2
  9. package/dist/lib/connect-all-summary.js +2 -2
  10. package/dist/lib/connect-all-uninstall.d.ts +1 -1
  11. package/dist/lib/connect-all-uninstall.js +1 -1
  12. package/dist/lib/connect-all.d.ts +1 -1
  13. package/dist/lib/connect-all.js +2 -2
  14. package/dist/lib/connect-clients.d.ts +1 -1
  15. package/dist/lib/connect-clients.js +2 -2
  16. package/dist/lib/connect-harness.js +1 -1
  17. package/dist/lib/connect-native.js +1 -1
  18. package/dist/lib/connect-openclaw.js +1 -1
  19. package/dist/lib/connect-picker.d.ts +1 -1
  20. package/dist/lib/connect-picker.js +1 -1
  21. package/dist/lib/connect-runtime.d.ts +2 -2
  22. package/dist/lib/doctor-checks.js +1 -1
  23. package/dist/lib/doctor-repairs.js +1 -1
  24. package/dist/lib/doctor-report.d.ts +1 -1
  25. package/dist/lib/doctor-report.js +1 -1
  26. package/dist/lib/{harness-detect.js → harness/harness-detect.js} +2 -2
  27. package/dist/lib/harness/harness-messages.d.ts +16 -0
  28. package/dist/lib/harness/harness-messages.js +58 -0
  29. package/dist/lib/{harness-spec.js → harness/harness-spec.js} +2 -58
  30. package/dist/lib/registration-detect.d.ts +1 -1
  31. package/dist/lib/registration-detect.js +2 -2
  32. package/dist/lib/setup-telemetry.d.ts +1 -1
  33. package/dist/lib/setup-telemetry.js +1 -1
  34. package/dist/lib/skill-paths.d.ts +1 -1
  35. package/dist/lib/skill-vendor.d.ts +1 -1
  36. package/package.json +1 -1
  37. /package/dist/lib/{harness-detect.d.ts → harness/harness-detect.d.ts} +0 -0
  38. /package/dist/lib/{harness-spec.d.ts → harness/harness-spec.d.ts} +0 -0
@@ -2,7 +2,7 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { saveConnectConfigDir, validateKeyFormat, validatedApiKey } from '../lib/auth.js';
4
4
  import { runConnectAll } from '../lib/connect-all.js';
5
- import { ALL_HARNESSES } from '../lib/harness-detect.js';
5
+ import { ALL_HARNESSES } from '../lib/harness/harness-detect.js';
6
6
  import { detectHumanInitiated } from '../lib/harness.js';
7
7
  import { emitNotice } from '../lib/notice.js';
8
8
  import { errLine } from '../lib/output.js';
@@ -3,7 +3,7 @@ import { checkAuthCall, checkCliVersion, checkConnectivity, checkCredential, che
3
3
  import { DOCTOR_COULD_NOT_RUN, DOCTOR_INVALID_INPUT, DOCTOR_SCHEMA_VERSION, doctorReportSchema, exitCodeFor, renderPretty, verdictFor, } from '../lib/doctor-report.js';
4
4
  import { applyRepairs, repairsFor } from '../lib/doctor-repairs.js';
5
5
  import { couldNotRunPayload, telemetryKey, telemetryPayload } from '../lib/doctor-telemetry.js';
6
- import { ALL_HARNESSES } from '../lib/harness-detect.js';
6
+ import { ALL_HARNESSES } from '../lib/harness/harness-detect.js';
7
7
  import { err, errLine, out, outLine } from '../lib/output.js';
8
8
  import { detectRegistrations } from '../lib/registration-detect.js';
9
9
  import { sendDoctorCompleted } from '../lib/setup-telemetry.js';
@@ -4,7 +4,7 @@ import { fetchContentGet, searchQuery } from '../lib/client.js';
4
4
  import { BASE_URL, TINYFISH_API_KEY_VAR } from '../lib/constants.js';
5
5
  import { errLine, handleApiError, outLine } from '../lib/output.js';
6
6
  import { connectHarness, launchAgent } from '../lib/connect-harness.js';
7
- import { HARNESS_DISPLAY_NAMES } from '../lib/harness-detect.js';
7
+ import { HARNESS_DISPLAY_NAMES } from '../lib/harness/harness-detect.js';
8
8
  const EXAMPLE_QUERIES = [
9
9
  'What are recent changes in the AI dev stack? Give me the top 5 changes that matter, why they matter, and any action needed.',
10
10
  'Find a new-grad software engineering role posted in the last 24 hours and fetch the job description.',
@@ -2,22 +2,75 @@ import * as fs from 'fs';
2
2
  import * as os from 'os';
3
3
  import * as path from 'path';
4
4
  import { createRequire } from 'module';
5
+ import { z } from 'zod';
5
6
  import { getApiKey } from '../lib/auth.js';
6
7
  import { deriveChromeKey, listAuthRelevantHostKeys, openCookiesDb, readCookiesForHost, } from '../lib/chrome-cookies.js';
7
8
  import { profileCreate, profileUpload } from '../lib/client.js';
8
9
  import { err, errLine, handleApiError, out, outLine } from '../lib/output.js';
9
10
  // ── Helpers ───────────────────────────────────────────────────────────────────
10
- function getChromeCookiesPath() {
11
+ const chromeLocalStateSchema = z.object({
12
+ profile: z.object({ last_used: z.string().optional() }).optional(),
13
+ });
14
+ function getChromeRoot() {
11
15
  const platform = process.platform;
12
16
  if (platform === 'darwin') {
13
- return path.join(os.homedir(), 'Library', 'Application Support', 'Google', 'Chrome', 'Default', 'Cookies');
17
+ return path.join(os.homedir(), 'Library', 'Application Support', 'Google', 'Chrome');
14
18
  }
15
19
  if (platform === 'linux') {
16
- return path.join(os.homedir(), '.config', 'google-chrome', 'Default', 'Cookies');
20
+ return path.join(os.homedir(), '.config', 'google-chrome');
17
21
  }
18
22
  // Windows guard — handled in withCookiesDb
19
23
  return '';
20
24
  }
25
+ function sanitizeChromeProfile(profile) {
26
+ if (!profile || profile === '.' || profile === '..' || path.basename(profile) !== profile) {
27
+ throw new Error('Chrome profile must be a directory name such as "Default" or "Profile 1"');
28
+ }
29
+ return profile;
30
+ }
31
+ function assertChromePathContained(targetPath) {
32
+ const chromeRoot = fs.realpathSync(getChromeRoot());
33
+ const resolvedTarget = fs.realpathSync(targetPath);
34
+ const relative = path.relative(chromeRoot, resolvedTarget);
35
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
36
+ throw new Error('Chrome data path resolves outside the Chrome data directory');
37
+ }
38
+ }
39
+ function getChromeCookiesPath(requestedProfile) {
40
+ const chromeRoot = getChromeRoot();
41
+ if (requestedProfile !== undefined) {
42
+ return path.join(chromeRoot, sanitizeChromeProfile(requestedProfile), 'Cookies');
43
+ }
44
+ const localStatePath = path.join(chromeRoot, 'Local State');
45
+ try {
46
+ fs.lstatSync(localStatePath);
47
+ }
48
+ catch (error) {
49
+ if (error.code === 'ENOENT') {
50
+ return path.join(chromeRoot, 'Default', 'Cookies');
51
+ }
52
+ throw error;
53
+ }
54
+ try {
55
+ assertChromePathContained(localStatePath);
56
+ }
57
+ catch (error) {
58
+ if (error.code === 'ENOENT') {
59
+ throw new Error('Could not read Chrome profile state. Use --chrome-profile to select a profile.');
60
+ }
61
+ throw error;
62
+ }
63
+ try {
64
+ const localState = chromeLocalStateSchema.parse(JSON.parse(fs.readFileSync(localStatePath, 'utf8')));
65
+ const lastUsed = localState.profile?.last_used;
66
+ if (!lastUsed)
67
+ throw new Error('missing last-used profile');
68
+ return path.join(chromeRoot, sanitizeChromeProfile(lastUsed), 'Cookies');
69
+ }
70
+ catch {
71
+ throw new Error('Could not read Chrome profile state. Use --chrome-profile to select a profile.');
72
+ }
73
+ }
21
74
  function validateDomain(domain) {
22
75
  if (domain.includes('/') || domain.includes('://') || /\s/.test(domain) || /:\d/.test(domain)) {
23
76
  err({ error: 'domain must be a plain hostname (e.g. github.com)' });
@@ -25,14 +78,15 @@ function validateDomain(domain) {
25
78
  }
26
79
  }
27
80
  // Opens a private copy of Chrome's cookie DB, derives the decryption key, and calls fn.
28
- function withCookiesDb(fn) {
81
+ function withCookiesDb(chromeProfile, fn) {
29
82
  if (process.platform === 'win32') {
30
83
  throw new Error('Cookie extraction is not supported on Windows yet');
31
84
  }
32
- const cookiesPath = getChromeCookiesPath();
85
+ const cookiesPath = getChromeCookiesPath(chromeProfile);
33
86
  if (!fs.existsSync(cookiesPath)) {
34
- throw new Error(`Chrome cookies file not found at ${cookiesPath}. Is Google Chrome installed?`);
87
+ throw new Error(`Chrome cookies file not found at ${cookiesPath}. Use --chrome-profile to select another Chrome profile.`);
35
88
  }
89
+ assertChromePathContained(cookiesPath);
36
90
  // Copy the DB out of the profile directory so we avoid the Chrome file lock.
37
91
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tinyfish-chrome-cookies-'));
38
92
  let db;
@@ -41,8 +95,11 @@ function withCookiesDb(fn) {
41
95
  fs.copyFileSync(cookiesPath, dbPath);
42
96
  // Committed rows can sit in Cookies-wal until Chrome checkpoints.
43
97
  for (const ext of ['-wal', '-shm']) {
44
- if (fs.existsSync(cookiesPath + ext))
45
- fs.copyFileSync(cookiesPath + ext, dbPath + ext);
98
+ const source = cookiesPath + ext;
99
+ if (fs.existsSync(source)) {
100
+ assertChromePathContained(source);
101
+ fs.copyFileSync(source, dbPath + ext);
102
+ }
46
103
  }
47
104
  try {
48
105
  db = openCookiesDb(dbPath);
@@ -67,11 +124,11 @@ function withCookiesDb(fn) {
67
124
  }
68
125
  }
69
126
  }
70
- function extractChromeCookies(domain, stats) {
71
- return withCookiesDb((db, key) => readCookiesForHost(db, key, domain, stats));
127
+ function extractChromeCookies(domain, chromeProfile, stats) {
128
+ return withCookiesDb(chromeProfile, (db, key) => readCookiesForHost(db, key, domain, stats));
72
129
  }
73
- function extractAllChromeCookies(stats) {
74
- return withCookiesDb((db, key) => {
130
+ function extractAllChromeCookies(chromeProfile, stats) {
131
+ return withCookiesDb(chromeProfile, (db, key) => {
75
132
  let hostKeys;
76
133
  try {
77
134
  hostKeys = listAuthRelevantHostKeys(db);
@@ -162,8 +219,8 @@ function extractCookiesOrThrow(opts) {
162
219
  // if extraction fails (Windows, no Chrome, no cookies).
163
220
  const stats = { keyringSkipped: 0 };
164
221
  const cookies = opts.allDomains
165
- ? extractAllChromeCookies(stats)
166
- : extractChromeCookies(opts.domain, stats);
222
+ ? extractAllChromeCookies(opts.chromeProfile, stats)
223
+ : extractChromeCookies(opts.domain, opts.chromeProfile, stats);
167
224
  // Warn even on partial success — surviving plaintext rows can mask
168
225
  // that every auth cookie was keyring-encrypted and skipped.
169
226
  if (stats.keyringSkipped > 0) {
@@ -232,6 +289,7 @@ export function registerProfile(program) {
232
289
  .description('Import Chrome cookies into a profile. Use --domain for a single domain or --all-domains for every domain in your Chrome profile. Exactly one must be provided.')
233
290
  .option('--domain <domain>', 'Domain to import cookies for (e.g. github.com)')
234
291
  .option('--all-domains', 'Import cookies for all registrable domains (e.g. github.com) found in your Chrome profile. IPs and non-resolvable hostnames (e.g. localhost) are skipped.')
292
+ .option('--chrome-profile <name>', 'Chrome profile directory (auto-detects the last-used profile by default)')
235
293
  .option('--profile-id <id>', 'Profile ID to import cookies into (creates new profile if omitted)')
236
294
  .option('--pretty', 'Human-readable output')
237
295
  .action(async (opts) => {
@@ -256,9 +314,10 @@ export function registerProfile(program) {
256
314
  const created = await profileCreate(name, apiKey);
257
315
  profileId = created.id;
258
316
  }
259
- const result = opts.allDomains
317
+ const uploadResult = opts.allDomains
260
318
  ? await uploadAllDomains(profileId, cookies, apiKey)
261
319
  : await profileUpload(profileId, cookies, apiKey);
320
+ const result = { ...uploadResult, profile_id: profileId };
262
321
  if (result.domains_failed && result.domains_failed.length > 0) {
263
322
  errLine(`Warning: failed to update cookies for domains: ${result.domains_failed.join(', ')}`);
264
323
  }
@@ -7,7 +7,7 @@ import { ConnectStepError, createConnectTelemetry, runGuarded, settle, } from '.
7
7
  import { cursorInstallDeeplink, cursorMcpPath, readCursorTinyfishEntry, writeCursorMcpConfig, } from './cursor-config.js';
8
8
  import { ompMcpPath, readOmpTinyfishEntry, writeOmpMcpConfig } from './omp-config.js';
9
9
  import { PI_ADAPTER_INSTALL_COMMAND, piMcpAdapterState, piSkillDirMismatch, piMcpPath, readPiTinyfishEntry, writePiMcpConfig, } from './pi-config.js';
10
- import { AuthMode, HARNESS_DISPLAY_NAMES } from '../harness-detect.js';
10
+ import { AuthMode, HARNESS_DISPLAY_NAMES } from '../harness/harness-detect.js';
11
11
  import { detectHumanInitiated } from '../harness.js';
12
12
  import { errLine } from '../output.js';
13
13
  import { verifyMcpAuth } from '../verify.js';
@@ -1,4 +1,4 @@
1
- import { type Harness } from '../harness-detect.js';
1
+ import { type Harness } from '../harness/harness-detect.js';
2
2
  import { type McpJsonServerEntry } from './mcp-json-config.js';
3
3
  export type OpencodeStyleHarness = 'kilo' | 'opencode';
4
4
  /** Narrows before any path is built, so a future removal-less harness cannot borrow these. */
@@ -1,8 +1,8 @@
1
1
  // OpenCode and its Kilo fork share a config file the CLI reads but never writes.
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
- import { harnessConfigPath } from '../harness-detect.js';
5
- import { harnessSpec } from '../harness-spec.js';
4
+ import { harnessConfigPath } from '../harness/harness-detect.js';
5
+ import { harnessSpec } from '../harness/harness-spec.js';
6
6
  import { readTinyfishEntry, } from './mcp-json-config.js';
7
7
  /** Narrows before any path is built, so a future removal-less harness cannot borrow these. */
8
8
  export function isOpencodeStyle(harness) {
@@ -1,7 +1,7 @@
1
1
  import { NATIVE_BY_HARNESS } from './connect-clients.js';
2
- import { harnessSpec } from './harness-spec.js';
2
+ import { harnessSpec } from './harness/harness-spec.js';
3
3
  import { ConnectInterruptedError, ConnectStepError, SignInTimeoutError, } from './connect-runtime.js';
4
- import { ALL_HARNESSES, HARNESS_DISPLAY_NAMES as DISPLAY_NAMES, } from './harness-detect.js';
4
+ import { ALL_HARNESSES, HARNESS_DISPLAY_NAMES as DISPLAY_NAMES, } from './harness/harness-detect.js';
5
5
  import { errLine } from './output.js';
6
6
  // Must undercut agent tool-call budgets (Claude Code Bash defaults to 120s).
7
7
  export const OAUTH_SIGN_IN_TIMEOUT_MS = 90_000;
@@ -1,6 +1,6 @@
1
1
  import { finishSetupCommand } from './connect-runtime.js';
2
- import { harnessSpec } from './harness-spec.js';
3
- import { HARNESS_COMMANDS, HARNESS_DISPLAY_NAMES as DISPLAY_NAMES, RELOAD_ACTION, } from './harness-detect.js';
2
+ import { harnessSpec } from './harness/harness-spec.js';
3
+ import { HARNESS_COMMANDS, HARNESS_DISPLAY_NAMES as DISPLAY_NAMES, RELOAD_ACTION, } from './harness/harness-detect.js';
4
4
  // Naming a harness the user does not have is the noise PF-3708 cut.
5
5
  const HIDDEN_ONCE_SOMETHING_WORKED = new Set([
6
6
  'deselected',
@@ -1,4 +1,4 @@
1
- import { type Harness } from './harness-detect.js';
1
+ import { type Harness } from './harness/harness-detect.js';
2
2
  import type { HarnessRunResult } from './connect-all.js';
3
3
  export declare function planText(harness: Harness, mcpUrl: string, apiKey?: string): string;
4
4
  export declare function uninstallPlanText(harness: Harness): string;
@@ -7,7 +7,7 @@ import { planOmpWrite, removeOmpMcpServer } from './config/omp-config.js';
7
7
  import { piMcpPath, planPiWrite, removePiMcpServer } from './config/pi-config.js';
8
8
  import { HERMES_KEY_VAR, hermesEnvPath, removeHermesKey, resolveHermesHome } from './hermes-env.js';
9
9
  import { clearHermesWebBackends } from './hermes-plugin.js';
10
- import { HARNESS_DISPLAY_NAMES as DISPLAY_NAMES, harnessDisplayPath, } from './harness-detect.js';
10
+ import { HARNESS_DISPLAY_NAMES as DISPLAY_NAMES, harnessDisplayPath, } from './harness/harness-detect.js';
11
11
  import { isOpencodeStyle, opencodeStyleConfigPath } from './config/opencode-config.js';
12
12
  import { errLine } from './output.js';
13
13
  export function planText(harness, mcpUrl, apiKey) {
@@ -1,4 +1,4 @@
1
- import { type Harness, type HarnessDetection } from './harness-detect.js';
1
+ import { type Harness, type HarnessDetection } from './harness/harness-detect.js';
2
2
  import { type VerifyDepth } from './verify.js';
3
3
  export type Outcome = 'not_detected' | 'not_installed' | 'stale_config' | 'needs_first_run' | 'installed' | 'failed' | 'harness_too_old' | 'interrupted' | 'auth_pending' | 'no_tty_auth_skip' | 'uninstalled' | 'uninstall_noop' | 'uninstall_pointer' | 'dry_run' | 'not_reached' | 'deselected';
4
4
  export type HarnessResultBase = {
@@ -1,5 +1,5 @@
1
1
  import * as readline from 'node:readline/promises';
2
- import { harnessSpec } from './harness-spec.js';
2
+ import { harnessSpec } from './harness/harness-spec.js';
3
3
  import spawn from 'cross-spawn';
4
4
  import { connectHarness } from './connect-harness.js';
5
5
  import { loadConfig, validatedApiKey } from './auth.js';
@@ -12,7 +12,7 @@ import { KEYLESS_ONBOARDING_PROMPT } from './connect-clients.js';
12
12
  import { gateApiKey } from './connect-preflight.js';
13
13
  import { ConnectInterruptedError } from './connect-runtime.js';
14
14
  import { pickHarnesses } from './connect-picker.js';
15
- import { commandOnPath, detectInstalledHarnesses, AuthMode, HARNESS_COMMANDS, HARNESS_DISPLAY_NAMES as DISPLAY_NAMES, ALL_HARNESSES, } from './harness-detect.js';
15
+ import { commandOnPath, detectInstalledHarnesses, AuthMode, HARNESS_COMMANDS, HARNESS_DISPLAY_NAMES as DISPLAY_NAMES, ALL_HARNESSES, } from './harness/harness-detect.js';
16
16
  import { detectHumanInitiated } from './harness.js';
17
17
  import { emitNotice } from './notice.js';
18
18
  import { errLine, outLine, setErrIndent } from './output.js';
@@ -1,6 +1,6 @@
1
1
  import spawn from 'cross-spawn';
2
2
  import { type AgentClient, type SupportedCommand } from './connect-runtime.js';
3
- import { type HarnessSpec, type SkillAgent } from './harness-spec.js';
3
+ import { type HarnessSpec, type SkillAgent } from './harness/harness-spec.js';
4
4
  export declare function openclawSkillInstallArgs(ackFlag: string | undefined): string[];
5
5
  /** `openclaw skills` has no uninstall; ClawHub removes from the global dir. */
6
6
  export declare function openclawSkillUninstall(): {
@@ -1,11 +1,11 @@
1
1
  import spawn from 'cross-spawn';
2
2
  import { SKILL_INSTALL_TIMEOUT_MS } from './cli-install.js';
3
3
  import { detectHumanInitiated } from './harness.js';
4
- import { harnessConfigPath } from './harness-detect.js';
4
+ import { harnessConfigPath } from './harness/harness-detect.js';
5
5
  import { errLine, sanitizeLine, warnLine } from './output.js';
6
6
  import { ConnectInterruptedError, ConnectStepError, spawnStepError, } from './connect-runtime.js';
7
7
  import { TINYFISH_ACCESS_MODE_HEADER, TINYFISH_API_KEY_VAR, TINYFISH_KEYLESS_ACCESS_MODE, } from './constants.js';
8
- import { HARNESS_SPECS, NATIVE_HARNESSES, harnessSpec, } from './harness-spec.js';
8
+ import { HARNESS_SPECS, NATIVE_HARNESSES, harnessSpec, } from './harness/harness-spec.js';
9
9
  import { HERMES_KEY_VAR, captureHermesKeyRestore, hermesEnvPath, resolveHermesHome, writeHermesKey, } from './hermes-env.js';
10
10
  import { hermesConfigPath, readHermesEntry } from './config/hermes-config.js';
11
11
  import { installHermesPlugin, removeHermesMcpEntry, setHermesWebBackends, writeHermesMcpEntry, } from './hermes-plugin.js';
@@ -1,5 +1,5 @@
1
1
  import { KEYLESS_ONBOARDING_PROMPT, NATIVE_BY_HARNESS, launchNativeMcpClient, launchOmpWalkthrough, launchPiWalkthrough, launchOpenClawWalkthrough, } from './connect-clients.js';
2
- import { AuthMode } from './harness-detect.js';
2
+ import { AuthMode } from './harness/harness-detect.js';
3
3
  import { errLine } from './output.js';
4
4
  import { connectNativeMcpClient } from './connect-native.js';
5
5
  import { connectOpenClaw } from './connect-openclaw.js';
@@ -6,7 +6,7 @@ import { ensureCliAuthenticated } from './connect-auth.js';
6
6
  import { installWebSkill } from './skill-install.js';
7
7
  import { ConnectInterruptedError, ConnectStepError, createConnectTelemetry, runGuarded, settle, SignInTimeoutError, spawnRemoval, spawnStepError, } from './connect-runtime.js';
8
8
  import { TINYFISH_API_KEY_VAR } from './constants.js';
9
- import { AuthMode, RELOAD_ACTION } from './harness-detect.js';
9
+ import { AuthMode, RELOAD_ACTION } from './harness/harness-detect.js';
10
10
  import { SIGN_IN_PROBES } from './registration-detect.js';
11
11
  import { errLine, warnLine } from './output.js';
12
12
  import { verifyMcpAuth } from './verify.js';
@@ -4,7 +4,7 @@ import { OPENCLAW, openclawSkillInstallArgs, launchOpenClawWalkthrough, } from '
4
4
  import { captureStdio, capturedOutput, replay } from './cli-install.js';
5
5
  import { ensureCliAuthenticated } from './connect-auth.js';
6
6
  import { createConnectTelemetry, runGuarded, settle, spawnStepError, } from './connect-runtime.js';
7
- import { RELOAD_ACTION } from './harness-detect.js';
7
+ import { RELOAD_ACTION } from './harness/harness-detect.js';
8
8
  import { errLine } from './output.js';
9
9
  import { finishSetupHint, requireSupportOrRescue, runCliInstallStep, trackPostInstallFailure, walkthroughPhase, } from './connect-steps.js';
10
10
  /** Skill install only: no key handoff or post-install callback to honour. */
@@ -1,5 +1,5 @@
1
1
  import type { Readable } from 'node:stream';
2
- import { type Harness, type HarnessDetection } from './harness-detect.js';
2
+ import { type Harness, type HarnessDetection } from './harness/harness-detect.js';
3
3
  type PickSelection = Harness[] | 'cancel';
4
4
  export interface PickResult {
5
5
  selection: PickSelection;
@@ -1,5 +1,5 @@
1
1
  import * as readline from 'node:readline';
2
- import { HARNESS_DISPLAY_NAMES } from './harness-detect.js';
2
+ import { HARNESS_DISPLAY_NAMES, } from './harness/harness-detect.js';
3
3
  const KEY_HELP = ' ↑↓ move space toggle one a none/all q cancel';
4
4
  function header(count) {
5
5
  return `Found ${count} agent config${count === 1 ? '' : 's'} on this machine.\n\n`;
@@ -1,5 +1,5 @@
1
- import { AuthMode } from './harness-detect.js';
2
- import type { Harness, HarnessSupportCheck } from './harness-spec.js';
1
+ import { AuthMode } from './harness/harness-detect.js';
2
+ import type { Harness, HarnessSupportCheck } from './harness/harness-spec.js';
3
3
  export declare const NON_INTERACTIVE_TIMEOUT_MS = 10000;
4
4
  export type AgentClient = Harness;
5
5
  type ConnectStage = 'started' | 'checkpoint' | 'completed' | 'failed' | 'aborted' | 'post_install_failed';
@@ -1,6 +1,6 @@
1
1
  import { apiKeyStatus } from './auth.js';
2
2
  import { CLI_VERSION } from './constants.js';
3
- import { AuthMode, Registered } from './harness-detect.js';
3
+ import { AuthMode, Registered } from './harness/harness-detect.js';
4
4
  import { resolveHermesHome } from './hermes-env.js';
5
5
  import { hermesWebBackendsOurs, readHermesPluginStatus, } from './hermes-plugin.js';
6
6
  import { endpointOf, isDefaultEndpoint } from './mcp-endpoint.js';
@@ -1,7 +1,7 @@
1
1
  import spawn from 'cross-spawn';
2
2
  import { connectHarness } from './connect-harness.js';
3
3
  import { OAUTH_SIGN_IN_TIMEOUT_MS } from './connect-all-auth.js';
4
- import { Registered } from './harness-detect.js';
4
+ import { Registered } from './harness/harness-detect.js';
5
5
  import { detectHumanInitiated } from './harness.js';
6
6
  import { errLine } from './output.js';
7
7
  export function repairsFor(checks, statuses) {
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { AuthMode, type Harness, Registered } from './harness-detect.js';
2
+ import { AuthMode, type Harness, Registered } from './harness/harness-detect.js';
3
3
  /** Bumped whenever a consumer could misread the payload; the cookbook skill releases separately. */
4
4
  export declare const DOCTOR_SCHEMA_VERSION = 3;
5
5
  /** Closed: three consumers match these ids by literal. */
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { ALL_HARNESSES, AuthMode, Registered } from './harness-detect.js';
2
+ import { ALL_HARNESSES, AuthMode, Registered } from './harness/harness-detect.js';
3
3
  /** Bumped whenever a consumer could misread the payload; the cookbook skill releases separately. */
4
4
  export const DOCTOR_SCHEMA_VERSION = 3;
5
5
  /** Closed: three consumers match these ids by literal. */
@@ -3,8 +3,8 @@ import * as os from 'os';
3
3
  import * as path from 'path';
4
4
  import which from 'which';
5
5
  import { ALL_HARNESSES, harnessSpec } from './harness-spec.js';
6
- import { clineConfigDir } from './config/cline-config.js';
7
- import { piAgentDir, piBinaryIsPi } from './config/pi-config.js';
6
+ import { clineConfigDir } from '../config/cline-config.js';
7
+ import { piAgentDir, piBinaryIsPi } from '../config/pi-config.js';
8
8
  export { ALL_HARNESSES };
9
9
  export const HARNESS_DISPLAY_NAMES = Object.fromEntries(ALL_HARNESSES.map((harness) => [harness, harnessSpec(harness).displayName]));
10
10
  export const RELOAD_ACTION = Object.fromEntries(ALL_HARNESSES.map((harness) => [harness, harnessSpec(harness).reloadAction]));
@@ -0,0 +1,16 @@
1
+ export declare const MCP_ADD_UNAVAILABLE_MESSAGE: string;
2
+ export declare const CLAUDE_CODE_KEYED_FALLBACK_NOTE: string;
3
+ export declare const CLAUDE_CODE_DEFERRED_AUTH_NOTE: string;
4
+ export declare const CODEX_MCP_URL_UNAVAILABLE_MESSAGE: string;
5
+ export declare const CODEX_KEYED_FALLBACK_NOTE: string;
6
+ export declare const CODEX_DEFERRED_AUTH_NOTE: string;
7
+ export declare const HERMES_OAUTH_UNAVAILABLE_MESSAGE: string;
8
+ export declare const GROK_HTTP_UNAVAILABLE_MESSAGE: string;
9
+ export declare const GROK_SIGN_IN_HINT: string;
10
+ export declare const OPENCLAW_SKILL_UNAVAILABLE_MESSAGE: string;
11
+ export declare const OPENCODE_MCP_ADD_UNAVAILABLE_MESSAGE: string;
12
+ export declare const OPENCODE_MODEL_NOTE: string;
13
+ export declare const KILO_MCP_ADD_UNAVAILABLE_MESSAGE: string;
14
+ export declare const HERMES_RESTART_NOTE = "Restart your Hermes session to pick up TinyFish \u2014 Hermes discovers MCP servers at startup.";
15
+ export declare const CLINE_MCP_ADD_UNAVAILABLE_MESSAGE: string;
16
+ export declare const COMMAND_CODE_ADD_JSON_UNAVAILABLE_MESSAGE: string;
@@ -0,0 +1,58 @@
1
+ // User-facing copy for the harness specs; kept apart so the spec file stays structure.
2
+ // An old install is only one reason a flag can go unseen, so no message asserts the cause.
3
+ const SUPPORT_CHECK_DEBUG_HINT = ' Set TINYFISH_DEBUG=1 and retry to print the help output TinyFish read.';
4
+ export const MCP_ADD_UNAVAILABLE_MESSAGE = 'Could not confirm this Claude Code installation exposes `claude mcp add`: `claude mcp ' +
5
+ '--help` did not list it. Update Claude Code with `claude update` and retry.' +
6
+ SUPPORT_CHECK_DEBUG_HINT;
7
+ // `claude mcp login` only exists upstream from Claude Code 2.1.186; older installs still register.
8
+ export const CLAUDE_CODE_KEYED_FALLBACK_NOTE = 'Using your stored TinyFish API key: this Claude Code predates one-command sign-in ' +
9
+ '(`claude mcp login`), so no browser sign-in is needed.';
10
+ export const CLAUDE_CODE_DEFERRED_AUTH_NOTE = 'TinyFish is registered in Claude Code but not signed in: this Claude Code predates ' +
11
+ 'one-command sign-in (`claude mcp login`). Open Claude Code and run `/mcp` to sign in to ' +
12
+ 'TinyFish. `claude update` gets you one-command sign-in. Already have a TinyFish API key? ' +
13
+ 'Run `tinyfish connect claude-code --api-key <key>` instead.';
14
+ export const CODEX_MCP_URL_UNAVAILABLE_MESSAGE = 'Could not confirm this Codex installation supports remote MCP servers: `codex mcp add ' +
15
+ '--help` did not list `--url`. Update Codex and retry, or add TinyFish manually with ' +
16
+ '`codex mcp add`.' +
17
+ SUPPORT_CHECK_DEBUG_HINT;
18
+ export const CODEX_KEYED_FALLBACK_NOTE = 'Using your stored TinyFish API key: this Codex cannot report whether an OAuth sign-in ' +
19
+ 'finished, so no browser sign-in is attempted.';
20
+ export const CODEX_DEFERRED_AUTH_NOTE = 'TinyFish is registered in Codex but not signed in: this Codex predates reliable sign-in ' +
21
+ 'reporting. Run `codex mcp login tinyfish` to sign in, or update Codex with ' +
22
+ '`npm install -g @openai/codex@latest` for one-command sign-in.';
23
+ export const HERMES_OAUTH_UNAVAILABLE_MESSAGE = 'Could not confirm this Hermes installation supports one-command MCP authentication: `hermes ' +
24
+ 'mcp add --help` did not list `--auth oauth`. Update Hermes and retry, or add TinyFish ' +
25
+ 'manually with `hermes mcp add`.' +
26
+ SUPPORT_CHECK_DEBUG_HINT;
27
+ export const GROK_HTTP_UNAVAILABLE_MESSAGE = 'Could not confirm this Grok installation supports remote MCP servers: `grok mcp add --help` ' +
28
+ 'did not list `--transport`. Update Grok and retry, or add TinyFish manually with ' +
29
+ '`grok mcp add`.' +
30
+ SUPPORT_CHECK_DEBUG_HINT;
31
+ // Grok has no `mcp login`; sign-in only happens inside the TUI. No key nudge here: a revoked
32
+ // key leaves Grok with no OAuth fallback, and doctor cannot spot that yet (PF-3618).
33
+ export const GROK_SIGN_IN_HINT = 'TinyFish is registered in Grok. Open Grok, run `/mcps`, select tinyfish, and press `i` to ' +
34
+ 'sign in.';
35
+ export const OPENCLAW_SKILL_UNAVAILABLE_MESSAGE = 'Could not confirm this OpenClaw installation supports global skill installation: `openclaw ' +
36
+ 'skills install --help` did not list `--global`. Update OpenClaw and retry, or install ' +
37
+ 'manually with `openclaw skills install @tinyfish/tinyfish --global`.' +
38
+ SUPPORT_CHECK_DEBUG_HINT;
39
+ export const OPENCODE_MCP_ADD_UNAVAILABLE_MESSAGE = 'Could not confirm this OpenCode installation supports one-command MCP setup: `opencode mcp ' +
40
+ 'add --help` did not list `--url`. Update OpenCode and retry, or add TinyFish manually with ' +
41
+ '`opencode mcp add`.' +
42
+ SUPPORT_CHECK_DEBUG_HINT;
43
+ // OpenCode is model-agnostic (unlike Claude Code / Codex), so a fresh install can default to a
44
+ // model with no tool support — TinyFish runs entirely on tool calls, so it would fail there.
45
+ export const OPENCODE_MODEL_NOTE = 'Note: TinyFish runs on tool calls, so OpenCode needs a model that supports tool use. Image-only ' +
46
+ 'models (e.g. Nano Banana Pro) will show "No endpoints found that support tool use" — switch ' +
47
+ "OpenCode's model if the walkthrough can't start.";
48
+ export const KILO_MCP_ADD_UNAVAILABLE_MESSAGE = 'Could not confirm this Kilo Code installation supports remote MCP servers: `kilo mcp add ' +
49
+ '--help` did not list `--url`. Update Kilo Code with `kilo upgrade` and retry, or add ' +
50
+ 'TinyFish manually with `kilo mcp add`.' +
51
+ SUPPORT_CHECK_DEBUG_HINT;
52
+ export const HERMES_RESTART_NOTE = 'Restart your Hermes session to pick up TinyFish — Hermes discovers MCP servers at startup.';
53
+ export const CLINE_MCP_ADD_UNAVAILABLE_MESSAGE = 'Could not confirm this Cline installation supports non-interactive MCP setup: `cline mcp add ' +
54
+ '--help` did not list `--transport` and `--yes`. Update Cline with `cline --update` and retry.' +
55
+ SUPPORT_CHECK_DEBUG_HINT;
56
+ export const COMMAND_CODE_ADD_JSON_UNAVAILABLE_MESSAGE = 'Could not confirm this Command Code installation supports keyed MCP setup: `commandcode mcp ' +
57
+ '--help` did not list `add-json`. Run `commandcode update` and retry.' +
58
+ SUPPORT_CHECK_DEBUG_HINT;
@@ -1,61 +1,5 @@
1
- import { HERMES_PLUGIN_NAME } from './constants.js';
2
- // An old install is only one reason a flag can go unseen, so no message asserts the cause.
3
- const SUPPORT_CHECK_DEBUG_HINT = ' Set TINYFISH_DEBUG=1 and retry to print the help output TinyFish read.';
4
- const MCP_ADD_UNAVAILABLE_MESSAGE = 'Could not confirm this Claude Code installation exposes `claude mcp add`: `claude mcp ' +
5
- '--help` did not list it. Update Claude Code with `claude update` and retry.' +
6
- SUPPORT_CHECK_DEBUG_HINT;
7
- // `claude mcp login` only exists upstream from Claude Code 2.1.186; older installs still register.
8
- const CLAUDE_CODE_KEYED_FALLBACK_NOTE = 'Using your stored TinyFish API key: this Claude Code predates one-command sign-in ' +
9
- '(`claude mcp login`), so no browser sign-in is needed.';
10
- const CLAUDE_CODE_DEFERRED_AUTH_NOTE = 'TinyFish is registered in Claude Code but not signed in: this Claude Code predates ' +
11
- 'one-command sign-in (`claude mcp login`). Open Claude Code and run `/mcp` to sign in to ' +
12
- 'TinyFish. `claude update` gets you one-command sign-in. Already have a TinyFish API key? ' +
13
- 'Run `tinyfish connect claude-code --api-key <key>` instead.';
14
- const CODEX_MCP_URL_UNAVAILABLE_MESSAGE = 'Could not confirm this Codex installation supports remote MCP servers: `codex mcp add ' +
15
- '--help` did not list `--url`. Update Codex and retry, or add TinyFish manually with ' +
16
- '`codex mcp add`.' +
17
- SUPPORT_CHECK_DEBUG_HINT;
18
- const CODEX_KEYED_FALLBACK_NOTE = 'Using your stored TinyFish API key: this Codex cannot report whether an OAuth sign-in ' +
19
- 'finished, so no browser sign-in is attempted.';
20
- const CODEX_DEFERRED_AUTH_NOTE = 'TinyFish is registered in Codex but not signed in: this Codex predates reliable sign-in ' +
21
- 'reporting. Run `codex mcp login tinyfish` to sign in, or update Codex with ' +
22
- '`npm install -g @openai/codex@latest` for one-command sign-in.';
23
- const HERMES_OAUTH_UNAVAILABLE_MESSAGE = 'Could not confirm this Hermes installation supports one-command MCP authentication: `hermes ' +
24
- 'mcp add --help` did not list `--auth oauth`. Update Hermes and retry, or add TinyFish ' +
25
- 'manually with `hermes mcp add`.' +
26
- SUPPORT_CHECK_DEBUG_HINT;
27
- const GROK_HTTP_UNAVAILABLE_MESSAGE = 'Could not confirm this Grok installation supports remote MCP servers: `grok mcp add --help` ' +
28
- 'did not list `--transport`. Update Grok and retry, or add TinyFish manually with ' +
29
- '`grok mcp add`.' +
30
- SUPPORT_CHECK_DEBUG_HINT;
31
- // Grok has no `mcp login`; sign-in only happens inside the TUI. No key nudge here: a revoked
32
- // key leaves Grok with no OAuth fallback, and doctor cannot spot that yet (PF-3618).
33
- const GROK_SIGN_IN_HINT = 'TinyFish is registered in Grok. Open Grok, run `/mcps`, select tinyfish, and press `i` to ' +
34
- 'sign in.';
35
- const OPENCLAW_SKILL_UNAVAILABLE_MESSAGE = 'Could not confirm this OpenClaw installation supports global skill installation: `openclaw ' +
36
- 'skills install --help` did not list `--global`. Update OpenClaw and retry, or install ' +
37
- 'manually with `openclaw skills install @tinyfish/tinyfish --global`.' +
38
- SUPPORT_CHECK_DEBUG_HINT;
39
- const OPENCODE_MCP_ADD_UNAVAILABLE_MESSAGE = 'Could not confirm this OpenCode installation supports one-command MCP setup: `opencode mcp ' +
40
- 'add --help` did not list `--url`. Update OpenCode and retry, or add TinyFish manually with ' +
41
- '`opencode mcp add`.' +
42
- SUPPORT_CHECK_DEBUG_HINT;
43
- // OpenCode is model-agnostic (unlike Claude Code / Codex), so a fresh install can default to a
44
- // model with no tool support — TinyFish runs entirely on tool calls, so it would fail there.
45
- const OPENCODE_MODEL_NOTE = 'Note: TinyFish runs on tool calls, so OpenCode needs a model that supports tool use. Image-only ' +
46
- 'models (e.g. Nano Banana Pro) will show "No endpoints found that support tool use" — switch ' +
47
- "OpenCode's model if the walkthrough can't start.";
48
- const KILO_MCP_ADD_UNAVAILABLE_MESSAGE = 'Could not confirm this Kilo Code installation supports remote MCP servers: `kilo mcp add ' +
49
- '--help` did not list `--url`. Update Kilo Code with `kilo upgrade` and retry, or add ' +
50
- 'TinyFish manually with `kilo mcp add`.' +
51
- SUPPORT_CHECK_DEBUG_HINT;
52
- const HERMES_RESTART_NOTE = 'Restart your Hermes session to pick up TinyFish — Hermes discovers MCP servers at startup.';
53
- const CLINE_MCP_ADD_UNAVAILABLE_MESSAGE = 'Could not confirm this Cline installation supports non-interactive MCP setup: `cline mcp add ' +
54
- '--help` did not list `--transport` and `--yes`. Update Cline with `cline --update` and retry.' +
55
- SUPPORT_CHECK_DEBUG_HINT;
56
- const COMMAND_CODE_ADD_JSON_UNAVAILABLE_MESSAGE = 'Could not confirm this Command Code installation supports keyed MCP setup: `commandcode mcp ' +
57
- '--help` did not list `add-json`. Run `commandcode update` and retry.' +
58
- SUPPORT_CHECK_DEBUG_HINT;
1
+ import { HERMES_PLUGIN_NAME } from '../constants.js';
2
+ import { CLAUDE_CODE_DEFERRED_AUTH_NOTE, CLAUDE_CODE_KEYED_FALLBACK_NOTE, CLINE_MCP_ADD_UNAVAILABLE_MESSAGE, CODEX_DEFERRED_AUTH_NOTE, CODEX_KEYED_FALLBACK_NOTE, CODEX_MCP_URL_UNAVAILABLE_MESSAGE, COMMAND_CODE_ADD_JSON_UNAVAILABLE_MESSAGE, GROK_HTTP_UNAVAILABLE_MESSAGE, GROK_SIGN_IN_HINT, HERMES_OAUTH_UNAVAILABLE_MESSAGE, HERMES_RESTART_NOTE, KILO_MCP_ADD_UNAVAILABLE_MESSAGE, MCP_ADD_UNAVAILABLE_MESSAGE, OPENCLAW_SKILL_UNAVAILABLE_MESSAGE, OPENCODE_MCP_ADD_UNAVAILABLE_MESSAGE, OPENCODE_MODEL_NOTE, } from './harness-messages.js';
59
3
  const HEADER_FLAG = /(?:^|\s)--header(?:[\s<=]|$)/m;
60
4
  const ADD_JSON_COMMAND = /^\s*add-json(?:\s|\[)/m;
61
5
  /** One entry per harness; every per-harness list derives from it. */
@@ -1,6 +1,6 @@
1
1
  import { type RecordedAuthMode } from './auth.js';
2
2
  import type { AgentClient } from './connect-runtime.js';
3
- import { AuthMode, type Harness, Registered } from './harness-detect.js';
3
+ import { AuthMode, type Harness, Registered } from './harness/harness-detect.js';
4
4
  export interface RegistrationStatus {
5
5
  harness: Harness;
6
6
  detected: boolean;
@@ -12,10 +12,10 @@ import { readCursorTinyfishEntry } from './config/cursor-config.js';
12
12
  import { readOmpTinyfishEntry } from './config/omp-config.js';
13
13
  import { readOpencodeStyleEntry } from './config/opencode-config.js';
14
14
  import { readPiTinyfishEntry } from './config/pi-config.js';
15
- import { harnessSpec } from './harness-spec.js';
15
+ import { harnessSpec } from './harness/harness-spec.js';
16
16
  import { readHermesKey, resolveHermesHome } from './hermes-env.js';
17
17
  import { readHermesEntry } from './config/hermes-config.js';
18
- import { detectInstalledHarnesses, harnessConfigPath, harnessDisplayPath, AuthMode, Registered, } from './harness-detect.js';
18
+ import { detectInstalledHarnesses, harnessConfigPath, harnessDisplayPath, AuthMode, Registered, } from './harness/harness-detect.js';
19
19
  const NOT_REGISTERED = Object.freeze({
20
20
  registered: Registered.No,
21
21
  authMode: AuthMode.Unknown,
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { AuthMode, Registered } from './harness-detect.js';
2
+ import { AuthMode, Registered } from './harness/harness-detect.js';
3
3
  /**
4
4
  * `TINYFISH_NO_TELEMETRY` suppresses ALL CLI-emitted network analytics
5
5
  * (setup_completed, connect-event, update check). Any value except `0`/`false`
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  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
- import { ALL_HARNESSES, AuthMode, Registered } from './harness-detect.js';
5
+ import { ALL_HARNESSES, AuthMode, Registered } from './harness/harness-detect.js';
6
6
  import { detectHarness, detectRuntimeEnv } from './harness.js';
7
7
  import { captureNotice } from './notice.js';
8
8
  import { errLine } from './output.js';
@@ -1,4 +1,4 @@
1
- import type { SkillAgent } from './harness-spec.js';
1
+ import type { SkillAgent } from './harness/harness-spec.js';
2
2
  /** The skill's own dir under where skills@1.5.15 wrote for this agent. */
3
3
  export declare function skillTargetDir(agent: SkillAgent): string;
4
4
  /** Mirrors skills@1.5.15 getSkillLockPath(); missing the XDG branch strands entries. */
@@ -1,4 +1,4 @@
1
- import type { SkillAgent } from './harness-spec.js';
1
+ import type { SkillAgent } from './harness/harness-spec.js';
2
2
  /**
3
3
  * Writes the bundled skill where each agent reads it; true iff bytes changed.
4
4
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiny-fish/cli",
3
- "version": "0.46.1-next.360",
3
+ "version": "0.46.1-next.363",
4
4
  "description": "TinyFish CLI — run web automations from your terminal",
5
5
  "type": "module",
6
6
  "bin": {