@yanzc/claude-pro 0.0.0-stage → 0.4.1

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 (58) hide show
  1. package/.env.example +41 -0
  2. package/LICENSE +21 -0
  3. package/README.md +314 -3
  4. package/bin/claude-pro.js +32 -0
  5. package/package.json +40 -6
  6. package/src/agent.js +752 -0
  7. package/src/agents.js +186 -0
  8. package/src/api.js +529 -0
  9. package/src/cache.js +150 -0
  10. package/src/checkpoints.js +184 -0
  11. package/src/claude-code.js +60 -0
  12. package/src/codex/client.js +307 -0
  13. package/src/codex/translate-request.js +239 -0
  14. package/src/codex/translate-stream.js +328 -0
  15. package/src/commands.js +839 -0
  16. package/src/config.js +453 -0
  17. package/src/context.js +109 -0
  18. package/src/filestate.js +57 -0
  19. package/src/gateway.js +143 -0
  20. package/src/hooks.js +81 -0
  21. package/src/main.js +193 -0
  22. package/src/mcp.js +290 -0
  23. package/src/modes.js +154 -0
  24. package/src/oauth.js +287 -0
  25. package/src/peers.js +157 -0
  26. package/src/print.js +106 -0
  27. package/src/prompts.js +179 -0
  28. package/src/ratelimit.js +244 -0
  29. package/src/restart.js +115 -0
  30. package/src/runtime.js +162 -0
  31. package/src/selfcheck.js +71 -0
  32. package/src/session.js +139 -0
  33. package/src/supervisor.js +110 -0
  34. package/src/tasks.js +76 -0
  35. package/src/tools/bash.js +227 -0
  36. package/src/tools/effort.js +41 -0
  37. package/src/tools/files.js +352 -0
  38. package/src/tools/index.js +52 -0
  39. package/src/tools/interaction.js +157 -0
  40. package/src/tools/notebook.js +91 -0
  41. package/src/tools/peers.js +45 -0
  42. package/src/tools/restart.js +50 -0
  43. package/src/tools/search.js +458 -0
  44. package/src/tools/skill.js +32 -0
  45. package/src/tools/task.js +139 -0
  46. package/src/tools/web.js +283 -0
  47. package/src/ui/ansi.js +228 -0
  48. package/src/ui/dialogs.js +122 -0
  49. package/src/ui/editor.js +389 -0
  50. package/src/ui/markdown.js +249 -0
  51. package/src/ui/render.js +155 -0
  52. package/src/ui/repl.js +1460 -0
  53. package/src/ui/screen.js +259 -0
  54. package/src/updater.js +93 -0
  55. package/src/utils/clipboard.js +197 -0
  56. package/src/utils/diff.js +95 -0
  57. package/src/utils/paths.js +37 -0
  58. package/src/utils/shell.js +251 -0
package/src/gateway.js ADDED
@@ -0,0 +1,143 @@
1
+ // The codex-gateway provider: model requests go to a shared gateway (https://gw.predix.icu by default) with an
2
+ // API key of the user's gateway account, which pays for them from its balance. /login gets that key the way
3
+ // Claude Code logs in: a local callback server, the gateway's login page in the browser, and a one-time code
4
+ // exchanged for a key of this machine's own (see handleCli in codex-gateway's server.mjs).
5
+
6
+ import crypto from 'node:crypto';
7
+ import fs from 'node:fs';
8
+ import http from 'node:http';
9
+ import os from 'node:os';
10
+ import path from 'node:path';
11
+ import { spawn } from 'node:child_process';
12
+ import { CONFIG_DIR, GATEWAY_DEFAULT_URL } from './config.js';
13
+
14
+ export { GATEWAY_DEFAULT_URL };
15
+ export const gatewayLoginFile = () => path.join(process.env.CLAUDE_PRO_CONFIG_DIR || CONFIG_DIR, 'gateway.json');
16
+ const LOGIN_TIMEOUT_MS = 10 * 60_000;
17
+
18
+ export const normalizeGatewayUrl = (url) => String(url || GATEWAY_DEFAULT_URL).trim().replace(/\/+$/, '');
19
+
20
+ // {url, apiKey, account, savedAt}, or null when there is none for this gateway.
21
+ export function readGatewayLogin(url) {
22
+ try {
23
+ const data = JSON.parse(fs.readFileSync(gatewayLoginFile(), 'utf8'));
24
+ if (!data?.apiKey || (url && normalizeGatewayUrl(data.url) !== normalizeGatewayUrl(url))) return null;
25
+ return data;
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ export function saveGatewayLogin(login) {
32
+ const file = gatewayLoginFile();
33
+ fs.mkdirSync(path.dirname(file), { recursive: true });
34
+ const tmp = `${file}.${process.pid}.tmp`;
35
+ fs.writeFileSync(tmp, JSON.stringify({ ...login, url: normalizeGatewayUrl(login.url), savedAt: new Date().toISOString() }, null, 2), { mode: 0o600 });
36
+ fs.renameSync(tmp, file);
37
+ }
38
+
39
+ export function clearGatewayLogin() {
40
+ try { fs.unlinkSync(gatewayLoginFile()); return true; } catch { return false; }
41
+ }
42
+
43
+ export function openBrowser(url) {
44
+ try {
45
+ const [cmd, args] = process.platform === 'win32'
46
+ ? ['rundll32', ['url.dll,FileProtocolHandler', url]]
47
+ : process.platform === 'darwin' ? ['open', [url]] : ['xdg-open', [url]];
48
+ const child = spawn(cmd, args, { stdio: 'ignore', detached: true, windowsHide: true });
49
+ child.on('error', () => {});
50
+ child.unref();
51
+ return true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ async function gatewayJson(url, init = {}, fetchImpl = globalThis.fetch) {
58
+ const res = await fetchImpl(url, { ...init, headers: { 'content-type': 'application/json', ...(init.headers || {}) } });
59
+ const body = await res.json().catch(() => ({}));
60
+ if (!res.ok) throw Object.assign(new Error(body?.error?.message || `HTTP ${res.status}`), { status: res.status });
61
+ return body;
62
+ }
63
+
64
+ // The account behind a key: balance, usage totals (GET /v1/account).
65
+ export async function fetchGatewayAccount({ url, apiKey }, { fetchImpl = globalThis.fetch, signal } = {}) {
66
+ const body = await gatewayJson(`${normalizeGatewayUrl(url)}/v1/account`, { headers: { authorization: `Bearer ${apiKey}` }, signal }, fetchImpl);
67
+ return body.account;
68
+ }
69
+
70
+ // The models the gateway serves (GET /v1/models), fetched once per gateway and key. The reasoning fields are
71
+ // optional so older gateways still work; when present they are kept with the model for /effort and requests.
72
+ const modelLists = new Map();
73
+ export async function fetchGatewayModels({ url, apiKey }, { fetchImpl = globalThis.fetch, signal } = {}) {
74
+ const key = `${normalizeGatewayUrl(url)}\n${apiKey}`;
75
+ if (modelLists.has(key)) return modelLists.get(key);
76
+ const body = await gatewayJson(`${normalizeGatewayUrl(url)}/v1/models`, { headers: { authorization: `Bearer ${apiKey}` }, signal }, fetchImpl);
77
+ const list = (body.data || []).filter((m) => typeof m?.id === 'string').map((m) => ({
78
+ ...m,
79
+ supported_reasoning_levels: (Array.isArray(m.supported_reasoning_levels) ? m.supported_reasoning_levels : Array.isArray(m.reasoning_levels) ? m.reasoning_levels : [])
80
+ .map((r) => typeof r === 'string' ? r : r?.effort || r?.name).filter(Boolean),
81
+ default_reasoning_level: typeof m.default_reasoning_level === 'string' ? m.default_reasoning_level
82
+ : m.default_reasoning_level?.effort || m.default_effort || null,
83
+ }));
84
+ modelLists.set(key, list);
85
+ return list;
86
+ }
87
+
88
+ export function cachedGatewayModel(login, modelId) {
89
+ if (!login?.url || !login?.apiKey) return null;
90
+ return modelLists.get(`${normalizeGatewayUrl(login.url)}\n${login.apiKey}`)?.find((m) => m.id === modelId) || null;
91
+ }
92
+
93
+ const EFFORT_ORDER = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
94
+ export function gatewayEfforts(model) { return model?.supported_reasoning_levels || []; }
95
+ export function gatewayDefaultEffort(model) { return model?.default_reasoning_level || null; }
96
+ export function gatewayEffort(model, effort) {
97
+ const supported = gatewayEfforts(model);
98
+ if (!effort || !supported.length || supported.includes(effort)) return effort;
99
+ const want = EFFORT_ORDER.indexOf(effort);
100
+ return supported.reduce((best, item) => (Math.abs(EFFORT_ORDER.indexOf(item) - want) < Math.abs(EFFORT_ORDER.indexOf(best) - want) ? item : best), supported[0]);
101
+ }
102
+
103
+ const PAGE = (title, text) => `<!doctype html><meta charset="utf-8"><title>${title}</title><body style="font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;line-height:1.6"><h2>${title}</h2><p>${text}</p></body>`;
104
+
105
+ // Log in through the browser. `onUrl(url)` is told the login page's address (to show it, for when the browser
106
+ // does not open by itself); `open` opens it. Resolves with the saved login once the key is in.
107
+ export async function loginWithBrowser({ url = GATEWAY_DEFAULT_URL, onUrl = () => {}, open = openBrowser, signal, fetchImpl = globalThis.fetch, timeoutMs = LOGIN_TIMEOUT_MS } = {}) {
108
+ const base = normalizeGatewayUrl(url);
109
+ const state = crypto.randomBytes(24).toString('base64url');
110
+ let settle;
111
+ const result = new Promise((resolve, reject) => { settle = { resolve, reject }; });
112
+ const server = http.createServer((req, res) => {
113
+ const u = new URL(req.url, 'http://127.0.0.1');
114
+ if (u.pathname !== '/callback') { res.writeHead(404).end(); return; }
115
+ const ok = u.searchParams.get('state') === state && u.searchParams.get('code');
116
+ res.writeHead(ok ? 200 : 400, { 'content-type': 'text/html; charset=utf-8' });
117
+ res.end(ok ? PAGE('登录成功', '可以关掉这个页面,回到终端继续使用 claude-pro。') : PAGE('登录失败', '链接无效或已过期。请回到终端重新执行 /login。'));
118
+ if (ok) settle.resolve(u.searchParams.get('code'));
119
+ });
120
+ await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); });
121
+ const timer = setTimeout(() => settle.reject(new Error('Login timed out: the browser did not come back within 10 minutes.')), timeoutMs);
122
+ const onAbort = () => settle.reject(Object.assign(new Error('Login cancelled'), { name: 'AbortError' }));
123
+ signal?.addEventListener('abort', onAbort, { once: true });
124
+ try {
125
+ const page = `${base}/cli/login?${new URLSearchParams({ port: String(server.address().port), state, client: 'claude-pro', device: os.hostname().slice(0, 60) })}`;
126
+ onUrl(page);
127
+ Promise.resolve().then(() => open(page)).catch((e) => settle.reject(e));
128
+ const code = await result;
129
+ const token = await gatewayJson(`${base}/cli/token`, { method: 'POST', body: JSON.stringify({ code, state }) }, fetchImpl);
130
+ const login = { url: base, apiKey: token.api_key, account: { id: token.account?.id, name: token.account?.name, email: token.account?.email } };
131
+ saveGatewayLogin(login);
132
+ return { ...login, balance: token.account };
133
+ } finally {
134
+ clearTimeout(timer);
135
+ signal?.removeEventListener('abort', onAbort);
136
+ server.close();
137
+ }
138
+ }
139
+
140
+ export const formatUsd = (value) => {
141
+ const n = Number(value);
142
+ return Number.isFinite(n) ? `$${n.toFixed(n !== 0 && Math.abs(n) < 0.01 ? 4 : 2)}` : '-';
143
+ };
package/src/hooks.js ADDED
@@ -0,0 +1,81 @@
1
+ // Hooks: user-defined shell commands that run around tool calls, in the same shape as
2
+ // Claude Code's settings.json:
3
+ // "hooks": { "PreToolUse": [ { "matcher": "Bash|Edit", "hooks": [ { "type": "command", "command": "..." } ] } ],
4
+ // "PostToolUse": [...], "UserPromptSubmit": [...], "Stop": [...] }
5
+ // The event payload is passed as JSON on stdin (like Claude Code) and in $CLAUDE_PRO_HOOK_INPUT; $CLAUDE_PROJECT_DIR is set too.
6
+ // Exit code 0: fine (stdout is kept as extra context). Exit code 2: block, stderr goes to the model.
7
+ // Any other non-zero exit code is reported as a warning and ignored.
8
+
9
+ import { spawnShell, killTree, findShell, watchShell } from './utils/shell.js';
10
+ import { stripAnsi } from './ui/ansi.js';
11
+
12
+ const DEFAULT_TIMEOUT_MS = 60_000;
13
+
14
+ function matches(matcher, name) {
15
+ if (!matcher || matcher === '*') return true;
16
+ try { return new RegExp(`^(?:${matcher})$`).test(name || ''); } catch { return matcher === name; }
17
+ }
18
+
19
+ function runCommand(shell, command, { cwd, payload, timeout, signal }) {
20
+ return new Promise((resolve) => {
21
+ let out = '', err = '', timedOut = false;
22
+ let child;
23
+ try {
24
+ const json = JSON.stringify(payload);
25
+ child = spawnShell(shell, command, {
26
+ cwd,
27
+ env: { ...process.env, CLAUDE_PRO_HOOK_INPUT: json, CLAUDE_PROJECT_DIR: cwd, CLAUDE_PRO: '1' },
28
+ stdin: json,
29
+ });
30
+ } catch (e) {
31
+ resolve({ code: 1, out: '', err: e.message });
32
+ return;
33
+ }
34
+ const timer = setTimeout(() => { timedOut = true; killTree(child); }, timeout);
35
+ const onAbort = () => killTree(child);
36
+ signal?.addEventListener('abort', onAbort, { once: true });
37
+ watchShell(child, {
38
+ onOutput: (text, stream) => { if (stream === 'stderr') err += text; else out += text; },
39
+ done: (code, error) => {
40
+ clearTimeout(timer);
41
+ signal?.removeEventListener('abort', onAbort);
42
+ if (error) err += error.message;
43
+ resolve({ code: timedOut || error ? 1 : code, out: stripAnsi(out).trim(), err: stripAnsi(err + (timedOut ? `\nhook timed out after ${timeout}ms` : '')).trim() });
44
+ },
45
+ });
46
+ });
47
+ }
48
+
49
+ export class HookRunner {
50
+ constructor(config) {
51
+ this.config = config;
52
+ this.shell = null;
53
+ }
54
+
55
+ has(event) { return (this.config.settings.hooks?.[event] || []).length > 0; }
56
+
57
+ // Returns {blocked, message, context: string[], warnings: string[]}.
58
+ async run(event, payload, { cwd, signal } = {}) {
59
+ const result = { blocked: false, message: '', context: [], warnings: [] };
60
+ const entries = this.config.settings.hooks?.[event] || [];
61
+ if (!entries.length) return result;
62
+ this.shell ||= findShell(this.config);
63
+ const full = { hook_event_name: event, cwd, ...payload };
64
+ for (const entry of entries) {
65
+ if (!matches(entry.matcher, payload.tool_name)) continue;
66
+ for (const h of entry.hooks || []) {
67
+ if (h.type && h.type !== 'command') continue;
68
+ if (!h.command) continue;
69
+ const r = await runCommand(this.shell, h.command, { cwd, payload: full, timeout: h.timeout ? h.timeout * 1000 : DEFAULT_TIMEOUT_MS, signal });
70
+ if (r.code === 2) {
71
+ result.blocked = true;
72
+ result.message = r.err || r.out || `Blocked by ${event} hook: ${h.command}`;
73
+ return result;
74
+ }
75
+ if (r.code !== 0) result.warnings.push(`${event} hook "${h.command}" exited with ${r.code}${r.err ? `: ${r.err.split('\n')[0]}` : ''}`);
76
+ else if (r.out) result.context.push(r.out);
77
+ }
78
+ }
79
+ return result;
80
+ }
81
+ }
package/src/main.js ADDED
@@ -0,0 +1,193 @@
1
+ import fs from 'node:fs';
2
+ import { VERSION, KNOWN_KEYS, loadConfig, settingsFiles, updateSettingsFile, applyRememberedChoices } from './config.js';
3
+ import { SessionStore } from './session.js';
4
+ import { takeRestartState } from './restart.js';
5
+
6
+ const HELP = `Usage: claude-pro [options] [prompt]
7
+
8
+ A Claude Code style coding agent for APIs or existing local Claude Code OAuth credentials.
9
+ Starts an interactive session by default; use -p for non-interactive output.
10
+
11
+ Options:
12
+ -p, --print Print the response and exit (useful for pipes)
13
+ -c, --continue Continue the most recent conversation in this directory
14
+ -r, --resume [sessionId] Resume a conversation (picker if no id given)
15
+ --provider <backend> api (default) | claude-code (direct HTTP with existing local login)
16
+ --model <model> Model for this session (name or alias: opus, sonnet, haiku)
17
+ --effort <level> Reasoning effort: none|minimal|low|medium|high|xhigh|max
18
+ --plan Start in read-only plan mode (research first, then ExitPlanMode)
19
+ --output-format <format> text | json | stream-json (with --print)
20
+ --verify <command> After the agent edits files, run this (e.g. "npm test") and send failures back to fix
21
+ --append-system-prompt <text> Append text to the system prompt
22
+ --system-prompt <text> Replace the system prompt
23
+ --verbose Show thinking and full tool output
24
+ --no-mcp Do not start MCP servers
25
+ -v, --version Print version
26
+ -h, --help Show help
27
+
28
+ There are no permission prompts: every tool runs immediately (full access). File edits are
29
+ checkpointed per prompt, and /rewind in a session undoes them. The old flags
30
+ --dangerously-skip-permissions, --permission-mode, --allowedTools and --disallowedTools
31
+ are accepted and ignored (--permission-mode plan is the same as --plan).
32
+
33
+ Commands:
34
+ claude-pro config list Show effective configuration and where each value comes from
35
+ claude-pro config set <KEY> <VALUE> [--project|--local] Save a setting (default: ~/.claude-pro/settings.json)
36
+ claude-pro config unset <KEY> [--project|--local]
37
+ `;
38
+
39
+ function parseArgs(argv) {
40
+ const flags = {};
41
+ const positional = [];
42
+ for (let i = 0; i < argv.length; i++) {
43
+ const a = argv[i];
44
+ const next = () => argv[++i];
45
+ // Old permission flags take a list of rules (Tool, Tool(content), "A B(c)"); skip over them.
46
+ const RULES = /^\s*[\w-]+(\([^)]*\))?(\s*[,\s]\s*[\w-]+(\([^)]*\))?)*\s*$/;
47
+ const skipRules = () => {
48
+ while (i + 1 < argv.length && !argv[i + 1].startsWith('-') && RULES.test(argv[i + 1])) i++;
49
+ };
50
+ switch (a) {
51
+ case '-p': case '--print': flags.print = true; break;
52
+ case '-c': case '--continue': flags.continue = true; break;
53
+ case '-r': case '--resume':
54
+ flags.resume = true;
55
+ if (argv[i + 1] && !argv[i + 1].startsWith('-') && /^[0-9a-f-]{8,}$/i.test(argv[i + 1])) flags.resumeId = next();
56
+ break;
57
+ case '--model': flags.model = next(); break;
58
+ case '--provider': flags.provider = next(); break;
59
+ case '--effort': flags.effort = next(); break;
60
+ case '--plan': flags.plan = true; break;
61
+ case '--permission-mode': if (next() === 'plan') flags.plan = true; break;
62
+ case '--dangerously-skip-permissions': break; // nothing to skip: there are no prompts
63
+ case '--allowedTools': case '--allowed-tools': case '--disallowedTools': case '--disallowed-tools': skipRules(); break;
64
+ case '--output-format': flags.outputFormat = next(); break;
65
+ case '--verify': flags.verify = next(); break;
66
+ case '--append-system-prompt': flags.appendSystemPrompt = next(); break;
67
+ case '--system-prompt': flags.systemPrompt = next(); break;
68
+ case '--verbose': flags.verbose = true; break;
69
+ case '--no-mcp': flags.noMcp = true; break;
70
+ case '-v': case '--version': flags.version = true; break;
71
+ case '-h': case '--help': flags.help = true; break;
72
+ default:
73
+ if (a.startsWith('--') && a.includes('=')) { argv.splice(i + 1, 0, a.slice(a.indexOf('=') + 1)); argv[i] = a.slice(0, a.indexOf('=')); i--; break; }
74
+ if (a.startsWith('-') && a !== '-') { process.stderr.write(`Unknown option: ${a}\n`); process.exit(2); }
75
+ positional.push(a);
76
+ }
77
+ }
78
+ return { flags, positional };
79
+ }
80
+
81
+ function configCommand(args, cwd) {
82
+ const [sub, key, ...rest] = args;
83
+ const scopeFlag = rest.find((r) => r === '--project' || r === '--local') || (key === '--project' || key === '--local' ? key : null);
84
+ const files = settingsFiles(cwd);
85
+ const file = scopeFlag === '--project' ? files.project : scopeFlag === '--local' ? files.local : files.user;
86
+ if (!sub || sub === 'list' || sub === 'get') {
87
+ const cfg = loadConfig({ cwd });
88
+ const remembered = applyRememberedChoices(cfg);
89
+ const keys = sub === 'get' && key ? [key] : Object.keys(KNOWN_KEYS);
90
+ for (const k of keys) {
91
+ const v = cfg.display(k);
92
+ if (v === undefined && sub !== 'get') continue;
93
+ console.log(`${k}=${v ?? ''}${cfg.sources[k] ? ` # ${cfg.sources[k]}` : ''}`);
94
+ }
95
+ if (sub !== 'get') {
96
+ console.log(`\nprovider: ${cfg.provider} (${cfg.endpointLabel})`);
97
+ console.log(`main model: ${cfg.mainModel.label} (context ${cfg.mainModel.contextWindow.toLocaleString()})${cfg.effort ? `, effort ${cfg.effort}` : ''}`);
98
+ if (remembered.model || remembered.effort) {
99
+ const what = Object.entries(remembered).map(([k, v]) => `${k} ${v}`).join(', ');
100
+ console.log(`chosen with /model, /effort: ${what} (kept in ${files.user}; wins over the settings above, --model/--effort win over it)`);
101
+ }
102
+ console.log(`sub-agent model: ${cfg.subagentModel.label}; small model: ${cfg.smallModel.label}`);
103
+ console.log(`auto-compact at ${cfg.autoCompactThreshold().toLocaleString()} tokens (window ${cfg.compactWindow().toLocaleString()})`);
104
+ }
105
+ return 0;
106
+ }
107
+ if (sub === 'set') {
108
+ const value = rest.filter((r) => r !== '--project' && r !== '--local').join(' ');
109
+ if (!key || !value) { console.error('Usage: claude-pro config set <KEY> <VALUE> [--project|--local]'); return 2; }
110
+ if (!KNOWN_KEYS[key]) console.error(`note: ${key} is not a known key; saving anyway`);
111
+ updateSettingsFile(file, (json) => { json.env = { ...(json.env || {}), [key]: value }; });
112
+ console.log(`Set ${key} in ${file}`);
113
+ return 0;
114
+ }
115
+ if (sub === 'unset') {
116
+ updateSettingsFile(file, (json) => { if (json.env) delete json.env[key]; });
117
+ console.log(`Removed ${key} from ${file}`);
118
+ return 0;
119
+ }
120
+ console.error(`Unknown config command: ${sub}`);
121
+ return 2;
122
+ }
123
+
124
+ // Read piped stdin. When a prompt was given on the command line, stdin is optional: if
125
+ // nothing arrives quickly (e.g. an inherited pipe that never closes) we carry on without it.
126
+ function readStdin({ optional }) {
127
+ if (process.stdin.isTTY) return Promise.resolve('');
128
+ return new Promise((resolve) => {
129
+ const chunks = [];
130
+ let timer = null;
131
+ const done = () => {
132
+ clearTimeout(timer);
133
+ process.stdin.removeAllListeners('data');
134
+ process.stdin.removeAllListeners('end');
135
+ process.stdin.pause();
136
+ resolve(Buffer.concat(chunks).toString('utf8'));
137
+ };
138
+ if (optional) timer = setTimeout(done, 300);
139
+ process.stdin.on('data', (d) => { clearTimeout(timer); chunks.push(d); });
140
+ process.stdin.on('end', done);
141
+ process.stdin.on('error', done);
142
+ });
143
+ }
144
+
145
+ export async function main(argv) {
146
+ const cwd = process.cwd();
147
+ if (argv[0] === 'config') return configCommand(argv.slice(1), cwd);
148
+ if (argv[0] === '--self-check') return (await import('./selfcheck.js')).selfCheck(); // see restart.js
149
+ const { flags, positional } = parseArgs(argv);
150
+ if (flags.version) { console.log(`${VERSION} (claude-pro)`); return 0; }
151
+ if (flags.help) { process.stdout.write(HELP); return 0; }
152
+
153
+ let prompt = positional.join(' ');
154
+
155
+ // Started by a restart of a running session: that session is picked up again, whatever the command line
156
+ // that started the first one said (-c, --resume, a first prompt).
157
+ const interactive = !(flags.print || !process.stdout.isTTY || !process.stdin.isTTY);
158
+ const restart = interactive ? takeRestartState() : null;
159
+ if (restart) {
160
+ flags.restart = restart;
161
+ flags.continue = false;
162
+ flags.resume = false;
163
+ flags.resumeId = undefined;
164
+ prompt = '';
165
+ }
166
+
167
+ if (flags.continue || (flags.resume && flags.resumeId)) {
168
+ const sessions = SessionStore.list(cwd);
169
+ const found = flags.resumeId ? sessions.find((s) => s.id.startsWith(flags.resumeId)) : sessions[0];
170
+ if (!found) {
171
+ process.stderr.write(flags.resumeId ? `No conversation found with id ${flags.resumeId}\n` : 'No conversation found to continue\n');
172
+ return 1;
173
+ }
174
+ flags.resumeFile = found.file;
175
+ flags.resumeId = found.id;
176
+ }
177
+
178
+ // mintty (Git Bash's default window) connects programs through pipes, not a console.
179
+ if (!flags.print && !process.stdin.isTTY && process.env.TERM_PROGRAM === 'mintty') {
180
+ process.stderr.write('Interactive mode needs a real console. In mintty/Git Bash run `winpty claude-pro` (or `winpty node bin/claude-pro.js`), or use Windows Terminal. For scripts use `claude-pro -p`.\n');
181
+ return 1;
182
+ }
183
+ if (flags.print || !process.stdout.isTTY || !process.stdin.isTTY) {
184
+ const stdin = await readStdin({ optional: !!prompt.trim() });
185
+ if (stdin.trim()) prompt = prompt ? `${stdin.trimEnd()}\n\n${prompt}` : stdin;
186
+ if (!prompt.trim()) { process.stderr.write('Error: no prompt given. Usage: claude-pro -p "your prompt"\n'); return 2; }
187
+ const { runPrint } = await import('./print.js');
188
+ return await runPrint({ prompt, flags, cwd });
189
+ }
190
+ if (!fs.existsSync(cwd)) return 1;
191
+ const { runRepl } = await import('./ui/repl.js');
192
+ return await runRepl({ flags, cwd, initialPrompt: prompt });
193
+ }