@tiny-fish/cli 0.30.2-next.256 → 0.30.2-next.258
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/auth.d.ts +1 -1
- package/dist/commands/auth.js +52 -52
- package/dist/commands/batch.d.ts +1 -1
- package/dist/commands/batch.js +54 -46
- package/dist/commands/browser.d.ts +1 -1
- package/dist/commands/browser.js +14 -14
- package/dist/commands/config-claude.d.ts +1 -1
- package/dist/commands/config-claude.js +22 -22
- package/dist/commands/connect.d.ts +3 -3
- package/dist/commands/connect.js +150 -150
- package/dist/commands/doctor.d.ts +4 -4
- package/dist/commands/doctor.js +104 -104
- package/dist/commands/fetch.d.ts +1 -1
- package/dist/commands/fetch.js +24 -27
- package/dist/commands/onboard.d.ts +1 -1
- package/dist/commands/onboard.js +50 -50
- package/dist/commands/profile.d.ts +3 -3
- package/dist/commands/profile.js +45 -45
- package/dist/commands/run.d.ts +1 -1
- package/dist/commands/run.js +78 -65
- package/dist/commands/runs.d.ts +1 -1
- package/dist/commands/runs.js +63 -50
- package/dist/commands/search.d.ts +1 -1
- package/dist/commands/search.js +16 -16
- package/dist/commands/upgrade.d.ts +2 -2
- package/dist/commands/upgrade.js +36 -36
- package/dist/commands/vault.d.ts +1 -1
- package/dist/commands/vault.js +48 -45
- package/dist/commands/wallet.d.ts +1 -1
- package/dist/commands/wallet.js +14 -14
- package/dist/index.js +2 -2
- package/dist/lib/auth.d.ts +5 -5
- package/dist/lib/auth.js +56 -56
- package/dist/lib/batch-store.d.ts +1 -1
- package/dist/lib/batch-store.js +4 -4
- package/dist/lib/chrome-cookies.d.ts +1 -1
- package/dist/lib/chrome-cookies.js +24 -24
- package/dist/lib/claude-config.js +37 -37
- package/dist/lib/cli-install.js +12 -12
- package/dist/lib/client.d.ts +2 -2
- package/dist/lib/client.js +61 -61
- package/dist/lib/connect-all.js +140 -134
- package/dist/lib/connect-auth.d.ts +2 -2
- package/dist/lib/connect-auth.js +15 -15
- package/dist/lib/connect-clients.d.ts +6 -6
- package/dist/lib/connect-clients.js +143 -143
- package/dist/lib/connect-fallback.d.ts +1 -1
- package/dist/lib/connect-fallback.js +38 -38
- package/dist/lib/connect-picker.d.ts +4 -4
- package/dist/lib/connect-picker.js +28 -28
- package/dist/lib/connect-runtime.d.ts +7 -7
- package/dist/lib/connect-runtime.js +61 -61
- package/dist/lib/constants.js +13 -13
- package/dist/lib/cursor-config.d.ts +1 -1
- package/dist/lib/cursor-config.js +30 -30
- package/dist/lib/doctor-report.d.ts +2 -2
- package/dist/lib/doctor-report.js +22 -22
- package/dist/lib/harness-detect.js +39 -39
- package/dist/lib/harness.js +9 -9
- package/dist/lib/hermes-env.js +26 -23
- package/dist/lib/install-root.js +12 -12
- package/dist/lib/notice.d.ts +1 -1
- package/dist/lib/notice.js +12 -12
- package/dist/lib/output.js +14 -14
- package/dist/lib/registration-detect.d.ts +3 -3
- package/dist/lib/registration-detect.js +82 -82
- package/dist/lib/setup-telemetry.d.ts +4 -4
- package/dist/lib/setup-telemetry.js +34 -34
- package/dist/lib/signals.js +1 -1
- package/dist/lib/skill-install.d.ts +3 -3
- package/dist/lib/skill-install.js +40 -40
- package/dist/lib/types.d.ts +8 -8
- package/dist/lib/verify.d.ts +1 -1
- package/dist/lib/verify.js +28 -28
- package/dist/program.d.ts +1 -1
- package/dist/program.js +28 -28
- package/package.json +18 -9
package/dist/commands/onboard.js
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import * as readline from
|
|
2
|
-
import { getApiKey, saveConfig } from
|
|
3
|
-
import { fetchContentGet, searchQuery } from
|
|
4
|
-
import { BASE_URL, TINYFISH_API_KEY_VAR } from
|
|
5
|
-
import { errLine, handleApiError, outLine } from
|
|
6
|
-
import { connectClaudeCode, connectCodex, connectHermes, connectOpenClaw, connectOpencode, launchAgent, } from
|
|
1
|
+
import * as readline from 'readline/promises';
|
|
2
|
+
import { getApiKey, saveConfig } from '../lib/auth.js';
|
|
3
|
+
import { fetchContentGet, searchQuery } from '../lib/client.js';
|
|
4
|
+
import { BASE_URL, TINYFISH_API_KEY_VAR } from '../lib/constants.js';
|
|
5
|
+
import { errLine, handleApiError, outLine } from '../lib/output.js';
|
|
6
|
+
import { connectClaudeCode, connectCodex, connectHermes, connectOpenClaw, connectOpencode, launchAgent, } from './connect.js';
|
|
7
7
|
const EXAMPLE_QUERIES = [
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
'What are recent changes in the AI dev stack? Give me the top 5 changes that matter, why they matter, and any action needed.',
|
|
9
|
+
'Find a new-grad software engineering role posted in the last 24 hours and fetch the job description.',
|
|
10
10
|
"Analyse today's market and recommend the best investments for $5,000 with expected returns and risks.",
|
|
11
11
|
];
|
|
12
12
|
const AGENTS = [
|
|
13
|
-
{ id:
|
|
14
|
-
{ id:
|
|
15
|
-
{ id:
|
|
16
|
-
{ id:
|
|
17
|
-
{ id:
|
|
13
|
+
{ id: 'claude-code', label: 'Claude Code', connect: connectClaudeCode },
|
|
14
|
+
{ id: 'codex', label: 'Codex', connect: connectCodex },
|
|
15
|
+
{ id: 'hermes', label: 'Hermes', connect: connectHermes },
|
|
16
|
+
{ id: 'openclaw', label: 'OpenClaw', connect: connectOpenClaw },
|
|
17
|
+
{ id: 'opencode', label: 'OpenCode', connect: connectOpencode },
|
|
18
18
|
];
|
|
19
19
|
function cleanFetchedContent(text) {
|
|
20
20
|
const cleaned = text
|
|
21
|
-
.replace(/!\[[^\]]*\]\([^)]+\)/g,
|
|
22
|
-
.replace(/\[([^\]]+)\]\([^)]+\)/g,
|
|
23
|
-
.replace(/^#{1,6}\s+/gm,
|
|
24
|
-
.replace(/[ \t]+/g,
|
|
25
|
-
.replace(/\n{3,}/g,
|
|
21
|
+
.replace(/!\[[^\]]*\]\([^)]+\)/g, '')
|
|
22
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
|
23
|
+
.replace(/^#{1,6}\s+/gm, '')
|
|
24
|
+
.replace(/[ \t]+/g, ' ')
|
|
25
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
26
26
|
.trim();
|
|
27
27
|
if (cleaned.length <= 1_200)
|
|
28
28
|
return cleaned;
|
|
29
29
|
const shortened = cleaned.slice(0, 1_200);
|
|
30
|
-
return `${shortened.slice(0, shortened.lastIndexOf(
|
|
30
|
+
return `${shortened.slice(0, shortened.lastIndexOf(' '))}…`;
|
|
31
31
|
}
|
|
32
32
|
function isUsefulFetchedContent(text) {
|
|
33
|
-
const normalized = text.replace(/\s+/g,
|
|
33
|
+
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
34
34
|
return (normalized.length >= 120 &&
|
|
35
|
-
normalized.split(
|
|
35
|
+
normalized.split(' ').length >= 20 &&
|
|
36
36
|
!/about press copyright contact us creators|there was an error while loading/i.test(normalized));
|
|
37
37
|
}
|
|
38
38
|
function parseAgents(value) {
|
|
39
39
|
if (!value.trim())
|
|
40
40
|
return [];
|
|
41
|
-
const selections = value.split(
|
|
41
|
+
const selections = value.split(',').map((item) => item.trim());
|
|
42
42
|
return selections.map((selection) => {
|
|
43
43
|
const agent = AGENTS[Number(selection) - 1];
|
|
44
44
|
if (!agent)
|
|
@@ -56,7 +56,7 @@ async function ask(question) {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
function sdkSnippet(language, query) {
|
|
59
|
-
if (language ===
|
|
59
|
+
if (language === 'python') {
|
|
60
60
|
return `from tinyfish import TinyFish
|
|
61
61
|
|
|
62
62
|
client = TinyFish()
|
|
@@ -85,8 +85,8 @@ async function connectAgent(id) {
|
|
|
85
85
|
const agent = AGENTS.find((candidate) => candidate.id === id);
|
|
86
86
|
if (!agent)
|
|
87
87
|
return;
|
|
88
|
-
const mcpUrl = new URL(
|
|
89
|
-
if (id ===
|
|
88
|
+
const mcpUrl = new URL('/mcp', BASE_URL).toString();
|
|
89
|
+
if (id === 'openclaw') {
|
|
90
90
|
await connectOpenClaw({ mcpUrl, launch: false, cliInstall: false });
|
|
91
91
|
}
|
|
92
92
|
else {
|
|
@@ -98,33 +98,33 @@ function parseSelectedAgent(value, selected) {
|
|
|
98
98
|
return undefined;
|
|
99
99
|
const agent = selected[Number(value) - 1];
|
|
100
100
|
if (!agent)
|
|
101
|
-
throw new Error(
|
|
101
|
+
throw new Error('Choose one of the agents you connected.');
|
|
102
102
|
return agent;
|
|
103
103
|
}
|
|
104
104
|
async function chooseQuery(query, interactive) {
|
|
105
105
|
if (query)
|
|
106
106
|
return query;
|
|
107
107
|
if (!interactive)
|
|
108
|
-
throw new Error(
|
|
109
|
-
outLine(
|
|
108
|
+
throw new Error('Pass a query, or run `tinyfish onboard` in a terminal.');
|
|
109
|
+
outLine('\nChoose a live-web job, or type your own:');
|
|
110
110
|
EXAMPLE_QUERIES.forEach((example, index) => outLine(` ${index + 1}. ${example}`));
|
|
111
|
-
const answer = await ask(
|
|
112
|
-
return EXAMPLE_QUERIES[Number(answer ||
|
|
111
|
+
const answer = await ask('Job [1]: ');
|
|
112
|
+
return EXAMPLE_QUERIES[Number(answer || '1') - 1] ?? answer;
|
|
113
113
|
}
|
|
114
114
|
async function runSearchStep(query, apiKey, persistEnvKey) {
|
|
115
115
|
outLine(`\n1. Searching the live web for: ${query}`);
|
|
116
116
|
const search = await searchQuery({ query }, apiKey);
|
|
117
117
|
if (persistEnvKey) {
|
|
118
118
|
saveConfig(apiKey);
|
|
119
|
-
outLine(
|
|
119
|
+
outLine('✓ API key saved to ~/.tinyfish/config.json');
|
|
120
120
|
}
|
|
121
121
|
if (!search.results.length) {
|
|
122
|
-
throw new Error(
|
|
122
|
+
throw new Error('Search returned no results. Try a more specific query.');
|
|
123
123
|
}
|
|
124
|
-
outLine(
|
|
124
|
+
outLine('\nSearch results');
|
|
125
125
|
for (const result of search.results.slice(0, 3)) {
|
|
126
126
|
outLine(` ${result.position}. ${result.title}\n` +
|
|
127
|
-
` ${result.site_name}${result.date ? ` · ${result.date}` :
|
|
127
|
+
` ${result.site_name}${result.date ? ` · ${result.date}` : ''}\n` +
|
|
128
128
|
` ${result.snippet}\n` +
|
|
129
129
|
` ${result.url}`);
|
|
130
130
|
}
|
|
@@ -136,9 +136,9 @@ async function runFetchStep(results, apiKey) {
|
|
|
136
136
|
let lastFetchError;
|
|
137
137
|
for (const candidate of candidates) {
|
|
138
138
|
try {
|
|
139
|
-
const fetched = await fetchContentGet({ urls: [candidate.url], format:
|
|
139
|
+
const fetched = await fetchContentGet({ urls: [candidate.url], format: 'markdown' }, apiKey);
|
|
140
140
|
const page = fetched.results[0];
|
|
141
|
-
if (page && typeof page.text ===
|
|
141
|
+
if (page && typeof page.text === 'string' && isUsefulFetchedContent(page.text)) {
|
|
142
142
|
return { source: candidate, text: cleanFetchedContent(page.text) };
|
|
143
143
|
}
|
|
144
144
|
}
|
|
@@ -149,13 +149,13 @@ async function runFetchStep(results, apiKey) {
|
|
|
149
149
|
}
|
|
150
150
|
if (failedFetches === candidates.length)
|
|
151
151
|
throw lastFetchError;
|
|
152
|
-
throw new Error(
|
|
152
|
+
throw new Error('Search found sources, but Fetch did not return useful page content.');
|
|
153
153
|
}
|
|
154
154
|
function renderFetchedResult({ source, text }) {
|
|
155
155
|
outLine(`\n2. Fetched source\n ${source.title}\n ${source.site_name} · ${source.url}`);
|
|
156
156
|
outLine(`\nWhy it matched\n${source.snippet}`);
|
|
157
157
|
outLine(`\nFetched content\n${text}`);
|
|
158
|
-
outLine(
|
|
158
|
+
outLine('\n✓ Search found relevant sources; Fetch returned clean, usable content.');
|
|
159
159
|
}
|
|
160
160
|
function agentLabel(id) {
|
|
161
161
|
return AGENTS.find((agent) => agent.id === id)?.label ?? id;
|
|
@@ -166,9 +166,9 @@ function errorMessage(error) {
|
|
|
166
166
|
async function runAgentSteps(interactive) {
|
|
167
167
|
if (!interactive)
|
|
168
168
|
return;
|
|
169
|
-
outLine(
|
|
169
|
+
outLine('\n3. Add TinyFish to your agents (choose any, or press Enter to skip):');
|
|
170
170
|
AGENTS.forEach((agent, index) => outLine(` ${index + 1}. ${agent.label}`));
|
|
171
|
-
const selected = parseAgents(await ask(
|
|
171
|
+
const selected = parseAgents(await ask('Agents [e.g. 1,2]: '));
|
|
172
172
|
const connected = [];
|
|
173
173
|
for (const id of selected) {
|
|
174
174
|
try {
|
|
@@ -181,9 +181,9 @@ async function runAgentSteps(interactive) {
|
|
|
181
181
|
}
|
|
182
182
|
if (!connected.length)
|
|
183
183
|
return;
|
|
184
|
-
outLine(
|
|
184
|
+
outLine('\n4. Open an agent now:');
|
|
185
185
|
connected.forEach((id, index) => outLine(` ${index + 1}. ${agentLabel(id)}`));
|
|
186
|
-
const launchId = parseSelectedAgent(await ask(
|
|
186
|
+
const launchId = parseSelectedAgent(await ask('Open [number or Enter to skip]: '), connected);
|
|
187
187
|
if (!launchId)
|
|
188
188
|
return;
|
|
189
189
|
try {
|
|
@@ -196,21 +196,21 @@ async function runAgentSteps(interactive) {
|
|
|
196
196
|
async function runSdkStep(interactive, query) {
|
|
197
197
|
if (!interactive)
|
|
198
198
|
return;
|
|
199
|
-
const choice = await ask(
|
|
199
|
+
const choice = await ask('\nShow this as SDK code? [t] TypeScript, [p] Python, [Enter] skip: ');
|
|
200
200
|
if (!choice)
|
|
201
201
|
return;
|
|
202
|
-
const language = choice ===
|
|
202
|
+
const language = choice === 'p' || choice === 'python' ? 'python' : 'typescript';
|
|
203
203
|
outLine(`\n${sdkSnippet(language, query)}\n`);
|
|
204
204
|
}
|
|
205
205
|
export function registerOnboard(program) {
|
|
206
206
|
program
|
|
207
|
-
.command(
|
|
208
|
-
.description(
|
|
209
|
-
.argument(
|
|
210
|
-
.addHelpText(
|
|
207
|
+
.command('onboard')
|
|
208
|
+
.description('Run a guided Search → Fetch demo, then add TinyFish to your agents or SDK')
|
|
209
|
+
.argument('[query]', 'A real topic to research')
|
|
210
|
+
.addHelpText('after', '\nExamples:\n' +
|
|
211
211
|
' tinyfish onboard "What changed in the AI dev stack this week?"\n' +
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
'\nAgents: claude-code, codex, hermes, openclaw, opencode.\n' +
|
|
213
|
+
'Run `tinyfish connect <client> --launch` later to add one directly.')
|
|
214
214
|
.action(async (query) => {
|
|
215
215
|
const apiKey = getApiKey();
|
|
216
216
|
const persistEnvKey = Boolean(process.env[TINYFISH_API_KEY_VAR]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Command } from
|
|
2
|
-
import { type DecryptStats } from
|
|
3
|
-
import type { CookieRecord } from
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { type DecryptStats } from '../lib/chrome-cookies.js';
|
|
3
|
+
import type { CookieRecord } from '../lib/types.js';
|
|
4
4
|
export declare function extractChromeCookies(domain: string, stats?: DecryptStats): CookieRecord[];
|
|
5
5
|
export declare function extractAllChromeCookies(stats?: DecryptStats): CookieRecord[];
|
|
6
6
|
type TldtsParse = (host: string, opts: {
|
package/dist/commands/profile.js
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import * as fs from
|
|
2
|
-
import * as os from
|
|
3
|
-
import * as path from
|
|
4
|
-
import { createRequire } from
|
|
5
|
-
import { getApiKey } from
|
|
6
|
-
import { deriveChromeKey, listAuthRelevantHostKeys, openCookiesDb, readCookiesForHost, } from
|
|
7
|
-
import { profileCreate, profileUpload } from
|
|
8
|
-
import { err, errLine, handleApiError, out, outLine } from
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
import { getApiKey } from '../lib/auth.js';
|
|
6
|
+
import { deriveChromeKey, listAuthRelevantHostKeys, openCookiesDb, readCookiesForHost, } from '../lib/chrome-cookies.js';
|
|
7
|
+
import { profileCreate, profileUpload } from '../lib/client.js';
|
|
8
|
+
import { err, errLine, handleApiError, out, outLine } from '../lib/output.js';
|
|
9
9
|
// ── UserFacingError ───────────────────────────────────────────────────────────
|
|
10
10
|
// Thrown by helpers to signal a terminal error that should be printed and exit 1
|
|
11
11
|
// without going through handleApiError (which would print a second error line).
|
|
12
12
|
class UserFacingError extends Error {
|
|
13
13
|
constructor(message) {
|
|
14
14
|
super(message);
|
|
15
|
-
this.name =
|
|
15
|
+
this.name = 'UserFacingError';
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
19
19
|
function getChromeCookiesPath() {
|
|
20
20
|
const platform = process.platform;
|
|
21
|
-
if (platform ===
|
|
22
|
-
return path.join(os.homedir(),
|
|
21
|
+
if (platform === 'darwin') {
|
|
22
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Google', 'Chrome', 'Default', 'Cookies');
|
|
23
23
|
}
|
|
24
|
-
if (platform ===
|
|
25
|
-
return path.join(os.homedir(),
|
|
24
|
+
if (platform === 'linux') {
|
|
25
|
+
return path.join(os.homedir(), '.config', 'google-chrome', 'Default', 'Cookies');
|
|
26
26
|
}
|
|
27
27
|
// Windows guard — handled in withCookiesDb
|
|
28
|
-
return
|
|
28
|
+
return '';
|
|
29
29
|
}
|
|
30
30
|
function validateDomain(domain) {
|
|
31
|
-
if (domain.includes(
|
|
32
|
-
err({ error:
|
|
31
|
+
if (domain.includes('/') || domain.includes('://') || /\s/.test(domain) || /:\d/.test(domain)) {
|
|
32
|
+
err({ error: 'domain must be a plain hostname (e.g. github.com)' });
|
|
33
33
|
process.exit(1);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
// Opens a private copy of Chrome's cookie DB, derives the decryption key, and calls fn.
|
|
37
37
|
function withCookiesDb(fn) {
|
|
38
|
-
if (process.platform ===
|
|
39
|
-
throw new UserFacingError(
|
|
38
|
+
if (process.platform === 'win32') {
|
|
39
|
+
throw new UserFacingError('Cookie extraction is not supported on Windows yet');
|
|
40
40
|
}
|
|
41
41
|
const cookiesPath = getChromeCookiesPath();
|
|
42
42
|
if (!fs.existsSync(cookiesPath)) {
|
|
43
43
|
throw new UserFacingError(`Chrome cookies file not found at ${cookiesPath}. Is Google Chrome installed?`);
|
|
44
44
|
}
|
|
45
45
|
// Copy the DB out of the profile directory so we avoid the Chrome file lock.
|
|
46
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
46
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tinyfish-chrome-cookies-'));
|
|
47
47
|
let db;
|
|
48
48
|
try {
|
|
49
|
-
const dbPath = path.join(tmpDir,
|
|
49
|
+
const dbPath = path.join(tmpDir, 'Cookies'); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
|
50
50
|
fs.copyFileSync(cookiesPath, dbPath);
|
|
51
51
|
// Committed rows can sit in Cookies-wal until Chrome checkpoints.
|
|
52
|
-
for (const ext of [
|
|
52
|
+
for (const ext of ['-wal', '-shm']) {
|
|
53
53
|
if (fs.existsSync(cookiesPath + ext))
|
|
54
54
|
fs.copyFileSync(cookiesPath + ext, dbPath + ext);
|
|
55
55
|
}
|
|
@@ -92,10 +92,10 @@ export function extractAllChromeCookies(stats) {
|
|
|
92
92
|
}
|
|
93
93
|
// Canonicalize host_keys → registrable domains (mirrors frontend/app/lib/services/domain-utils.ts)
|
|
94
94
|
const require = createRequire(import.meta.url);
|
|
95
|
-
const { parse } = require(
|
|
95
|
+
const { parse } = require('tldts');
|
|
96
96
|
const registrableDomains = new Set();
|
|
97
97
|
for (const hk of hostKeys) {
|
|
98
|
-
const cleaned = hk.startsWith(
|
|
98
|
+
const cleaned = hk.startsWith('.') ? hk.slice(1) : hk;
|
|
99
99
|
const result = parse(cleaned, { allowPrivateDomains: true });
|
|
100
100
|
if (result.domain)
|
|
101
101
|
registrableDomains.add(result.domain.toLowerCase());
|
|
@@ -120,7 +120,7 @@ export function extractAllChromeCookies(stats) {
|
|
|
120
120
|
export function groupByCanonicalDomain(cookies, parse) {
|
|
121
121
|
const groups = new Map();
|
|
122
122
|
for (const c of cookies) {
|
|
123
|
-
const cleaned = c.domain.startsWith(
|
|
123
|
+
const cleaned = c.domain.startsWith('.') ? c.domain.slice(1) : c.domain;
|
|
124
124
|
const cd = parse(cleaned, { allowPrivateDomains: true }).domain?.toLowerCase() ?? cleaned;
|
|
125
125
|
const bucket = groups.get(cd) ?? [];
|
|
126
126
|
bucket.push(c);
|
|
@@ -157,7 +157,7 @@ export function buildChunks(groups) {
|
|
|
157
157
|
}
|
|
158
158
|
// ── Pretty printers ───────────────────────────────────────────────────────────
|
|
159
159
|
function printPrettyProfileCreate(profile) {
|
|
160
|
-
outLine(
|
|
160
|
+
outLine('Profile created');
|
|
161
161
|
outLine(`ID: ${profile.id}`);
|
|
162
162
|
outLine(`Name: ${profile.name}`);
|
|
163
163
|
outLine(`Created at: ${profile.created_at}`);
|
|
@@ -166,18 +166,18 @@ function printPrettyProfileUpload(result) {
|
|
|
166
166
|
if (result.profile_id)
|
|
167
167
|
outLine(`Profile ID: ${result.profile_id}`);
|
|
168
168
|
outLine(`Cookies uploaded: ${result.cookie_count}`);
|
|
169
|
-
outLine(`Domains updated: ${result.domains_updated.join(
|
|
169
|
+
outLine(`Domains updated: ${result.domains_updated.join(', ')}`);
|
|
170
170
|
}
|
|
171
171
|
export function registerProfile(program) {
|
|
172
172
|
const profileCmd = program
|
|
173
|
-
.command(
|
|
174
|
-
.description(
|
|
173
|
+
.command('profile')
|
|
174
|
+
.description('Profile commands')
|
|
175
175
|
.enablePositionalOptions();
|
|
176
176
|
profileCmd
|
|
177
|
-
.command(
|
|
178
|
-
.description(
|
|
179
|
-
.requiredOption(
|
|
180
|
-
.option(
|
|
177
|
+
.command('create')
|
|
178
|
+
.description('Create a browser profile')
|
|
179
|
+
.requiredOption('--name <name>', 'Profile name')
|
|
180
|
+
.option('--pretty', 'Human-readable output')
|
|
181
181
|
.action(async (opts) => {
|
|
182
182
|
const apiKey = getApiKey();
|
|
183
183
|
try {
|
|
@@ -194,20 +194,20 @@ export function registerProfile(program) {
|
|
|
194
194
|
}
|
|
195
195
|
});
|
|
196
196
|
profileCmd
|
|
197
|
-
.command(
|
|
198
|
-
.description(
|
|
199
|
-
.option(
|
|
200
|
-
.option(
|
|
201
|
-
.option(
|
|
202
|
-
.option(
|
|
197
|
+
.command('import-cookies')
|
|
198
|
+
.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.')
|
|
199
|
+
.option('--domain <domain>', 'Domain to import cookies for (e.g. github.com)')
|
|
200
|
+
.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.')
|
|
201
|
+
.option('--profile-id <id>', 'Profile ID to import cookies into (creates new profile if omitted)')
|
|
202
|
+
.option('--pretty', 'Human-readable output')
|
|
203
203
|
.action(async (opts) => {
|
|
204
204
|
// Mutual-exclusivity guard — must provide exactly one of --domain / --all-domains
|
|
205
205
|
if (!opts.domain && !opts.allDomains) {
|
|
206
|
-
err({ error:
|
|
206
|
+
err({ error: 'Provide exactly one of --domain or --all-domains' });
|
|
207
207
|
process.exit(1);
|
|
208
208
|
}
|
|
209
209
|
if (opts.domain && opts.allDomains) {
|
|
210
|
-
err({ error:
|
|
210
|
+
err({ error: 'Provide exactly one of --domain or --all-domains' });
|
|
211
211
|
process.exit(1);
|
|
212
212
|
}
|
|
213
213
|
const apiKey = getApiKey();
|
|
@@ -229,13 +229,13 @@ export function registerProfile(program) {
|
|
|
229
229
|
}
|
|
230
230
|
if (cookies.length === 0) {
|
|
231
231
|
throw new UserFacingError(stats.keyringSkipped > 0
|
|
232
|
-
?
|
|
232
|
+
? 'Chrome on Linux encrypts cookies with the system keyring, which is not supported yet.'
|
|
233
233
|
: opts.allDomains
|
|
234
|
-
?
|
|
234
|
+
? 'No Chrome cookies found. Is Google Chrome installed and have you logged in to any sites?'
|
|
235
235
|
: `No Chrome cookies found for ${opts.domain}. Are you logged in to this site in Chrome?`);
|
|
236
236
|
}
|
|
237
237
|
if (profileId === undefined) {
|
|
238
|
-
const name = opts.allDomains ?
|
|
238
|
+
const name = opts.allDomains ? 'chrome-import' : opts.domain;
|
|
239
239
|
const created = await profileCreate(name, apiKey);
|
|
240
240
|
profileId = created.id;
|
|
241
241
|
}
|
|
@@ -243,7 +243,7 @@ export function registerProfile(program) {
|
|
|
243
243
|
if (opts.allDomains) {
|
|
244
244
|
// Batch upload — never split a canonical domain across chunks
|
|
245
245
|
const require = createRequire(import.meta.url);
|
|
246
|
-
const { parse } = require(
|
|
246
|
+
const { parse } = require('tldts');
|
|
247
247
|
const groups = groupByCanonicalDomain(cookies, parse);
|
|
248
248
|
const chunks = buildChunks(groups);
|
|
249
249
|
let cookieCount = 0;
|
|
@@ -267,7 +267,7 @@ export function registerProfile(program) {
|
|
|
267
267
|
result = await profileUpload(profileId, cookies, apiKey);
|
|
268
268
|
}
|
|
269
269
|
if (result.domains_failed && result.domains_failed.length > 0) {
|
|
270
|
-
errLine(`Warning: failed to update cookies for domains: ${result.domains_failed.join(
|
|
270
|
+
errLine(`Warning: failed to update cookies for domains: ${result.domains_failed.join(', ')}`);
|
|
271
271
|
}
|
|
272
272
|
if (opts.pretty) {
|
|
273
273
|
printPrettyProfileUpload(result);
|
package/dist/commands/run.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Command } from
|
|
1
|
+
import { Command } from 'commander';
|
|
2
2
|
export declare function registerRun(agentCmd: Command): Command;
|