@liustack/modlens 2.1.0 → 2.2.0

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/README.md CHANGED
@@ -111,7 +111,7 @@ Reach for `-m gemini-3.1-pro-high` on dense screenshots or tricky documents. Out
111
111
 
112
112
  ## Providers and config
113
113
 
114
- ModLens ships four vision providers. `antigravity-cli` stays the default: zero keys, pure free quota.
114
+ ModLens ships five vision providers. `antigravity-cli` stays the default: zero keys, pure free quota.
115
115
 
116
116
  | Provider | Needs | Typical speed | Notes |
117
117
  | :-- | :-- | :-- | :-- |
@@ -119,6 +119,7 @@ ModLens ships four vision providers. `antigravity-cli` stays the default: zero k
119
119
  | `gemini-api` | free AI Studio key | 5-10s | fastest free route, schema enforced server-side |
120
120
  | `openai` | baseUrl + apiKey + model | endpoint-dependent | any OpenAI-compatible multimodal endpoint (qwen-vl, GLM, ...) |
121
121
  | `anthropic` | `ANTHROPIC_API_KEY` | a few seconds | Claude Haiku by default, schema via forced tool call |
122
+ | `claude-cli` | Claude Code signed in | 20-45s | no key, rides your Claude subscription, Read-only permissions |
122
123
 
123
124
  Config lives in `~/.modlens/config.json`. Environment variables override the file (`GEMINI_API_KEY`, `OPENAI_API_KEY`, `OPENAI_BASE_URL`, `ANTHROPIC_API_KEY`), and CLI flags override everything.
124
125
 
package/README.zh-CN.md CHANGED
@@ -111,7 +111,7 @@ modlens -i <图片路径或 URL> [选项]
111
111
 
112
112
  ## Provider 与配置
113
113
 
114
- ModLens 内置四个视觉 provider,默认还是 `antigravity-cli`:零 key,纯免费额度。
114
+ ModLens 内置五个视觉 provider,默认还是 `antigravity-cli`:零 key,纯免费额度。
115
115
 
116
116
  | Provider | 需要什么 | 速度 | 说明 |
117
117
  | :-- | :-- | :-- | :-- |
@@ -119,6 +119,7 @@ ModLens 内置四个视觉 provider,默认还是 `antigravity-cli`:零 key
119
119
  | `gemini-api` | 免费 AI Studio key | 5-10 秒 | 最快的免费路线,服务端强制 schema |
120
120
  | `openai` | baseUrl + apiKey + model | 看端点 | 任何 OpenAI 兼容的多模态端点(qwen-vl、GLM 等) |
121
121
  | `anthropic` | `ANTHROPIC_API_KEY` | 几秒 | 默认 Claude Haiku,强制工具调用保 schema |
122
+ | `claude-cli` | Claude Code 已登录 | 20-45 秒 | 零 key,吃你的 Claude 订阅额度,只放行 Read 工具 |
122
123
 
123
124
  配置放在 `~/.modlens/config.json`,环境变量能盖过它(`GEMINI_API_KEY`、`OPENAI_API_KEY`、`OPENAI_BASE_URL`、`ANTHROPIC_API_KEY`),CLI 参数最大。
124
125
 
package/dist/main.js CHANGED
@@ -79,7 +79,8 @@ const CONFIG_TEMPLATE = {
79
79
  "antigravity-cli": { model: "gemini-3.6-flash-low" },
80
80
  "gemini-api": { apiKey: "", model: "gemini-3.6-flash" },
81
81
  openai: { baseUrl: "", apiKey: "", model: "" },
82
- anthropic: { apiKey: "", model: "claude-haiku-4-5-20251001" }
82
+ anthropic: { apiKey: "", model: "claude-haiku-4-5-20251001" },
83
+ "claude-cli": { model: "haiku" }
83
84
  }
84
85
  };
85
86
  function initConfigFile(configPath = CONFIG_PATH, force = false) {
@@ -269,11 +270,11 @@ function buildAntigravityInvocation(options) {
269
270
  };
270
271
  }
271
272
  function parseAntigravityOutput(stdout) {
272
- const envelope = parseEnvelope(stdout);
273
+ const envelope = parseEnvelope$1(stdout);
273
274
  if (envelope.status && envelope.status !== "SUCCESS") {
274
275
  throw new Error(`Antigravity CLI reported status ${envelope.status}.`);
275
276
  }
276
- const result = envelope.structured_output ?? (typeof envelope.response === "string" ? tryParseJson(envelope.response) : null);
277
+ const result = envelope.structured_output ?? (typeof envelope.response === "string" ? tryParseJson$1(envelope.response) : null);
277
278
  if (result === null || result === void 0) {
278
279
  throw new Error(
279
280
  "Antigravity CLI output contains no structured result. Check that the model finished the task (auth, quota, timeout)."
@@ -288,14 +289,14 @@ function parseAntigravityOutput(stdout) {
288
289
  }
289
290
  };
290
291
  }
291
- function parseEnvelope(stdout) {
292
+ function parseEnvelope$1(stdout) {
292
293
  const trimmed = stdout.trim();
293
- let parsed = tryParseJson(trimmed);
294
+ let parsed = tryParseJson$1(trimmed);
294
295
  if (parsed === null) {
295
296
  const firstBrace = trimmed.indexOf("{");
296
297
  const lastBrace = trimmed.lastIndexOf("}");
297
298
  if (firstBrace >= 0 && lastBrace > firstBrace) {
298
- parsed = tryParseJson(trimmed.slice(firstBrace, lastBrace + 1));
299
+ parsed = tryParseJson$1(trimmed.slice(firstBrace, lastBrace + 1));
299
300
  }
300
301
  }
301
302
  if (!parsed || typeof parsed !== "object") {
@@ -303,7 +304,7 @@ function parseEnvelope(stdout) {
303
304
  }
304
305
  return parsed;
305
306
  }
306
- function tryParseJson(text) {
307
+ function tryParseJson$1(text) {
307
308
  try {
308
309
  return JSON.parse(text);
309
310
  } catch {
@@ -432,7 +433,7 @@ Report your findings by calling the ${TOOL_NAME} tool.`;
432
433
  });
433
434
  if (!response.ok) {
434
435
  const body = await response.text();
435
- throw new Error(`Anthropic API error ${response.status}: ${truncate$2(body)}`);
436
+ throw new Error(`Anthropic API error ${response.status}: ${truncate$3(body)}`);
436
437
  }
437
438
  const payload = await response.json();
438
439
  const toolUse = payload.content?.find((block) => block.type === "tool_use");
@@ -448,7 +449,7 @@ Report your findings by calling the ${TOOL_NAME} tool.`;
448
449
  }
449
450
  };
450
451
  }
451
- function truncate$2(text) {
452
+ function truncate$3(text) {
452
453
  return text.length > 300 ? `${text.slice(0, 300)}...` : text;
453
454
  }
454
455
  const anthropicApiProvider = {
@@ -456,6 +457,92 @@ const anthropicApiProvider = {
456
457
  defaultModel: ANTHROPIC_DEFAULT_MODEL,
457
458
  execute: executeAnthropicApi
458
459
  };
460
+ const CLAUDE_CLI_DEFAULT_MODEL = "haiku";
461
+ function buildClaudeCliInvocation(options) {
462
+ if (options.imageKind === "remote") {
463
+ throw new Error(
464
+ "claude-cli provider reads local files only. Download the image first, or use -p gemini-api for remote URLs."
465
+ );
466
+ }
467
+ const prompt = buildVisionPrompt({
468
+ imageSource: options.imageSource,
469
+ imageKind: "local",
470
+ extraPrompt: options.extraPrompt
471
+ });
472
+ const args = [
473
+ "-p",
474
+ prompt,
475
+ "--output-format",
476
+ "json",
477
+ "--json-schema",
478
+ visionResultSchemaJson(),
479
+ "--allowedTools",
480
+ "Read",
481
+ "--model",
482
+ options.model || options.settings?.model || CLAUDE_CLI_DEFAULT_MODEL
483
+ ];
484
+ return {
485
+ command: options.providerBin || "claude",
486
+ args,
487
+ cwd: path.resolve(options.workdir || path.dirname(options.imageSource))
488
+ };
489
+ }
490
+ function parseClaudeCliOutput(stdout) {
491
+ const envelope = parseEnvelope(stdout);
492
+ if (envelope.is_error || envelope.subtype && envelope.subtype !== "success") {
493
+ throw new Error(
494
+ `Claude CLI reported ${envelope.subtype ?? "an error"}: ${truncate$2(envelope.result ?? "")}`
495
+ );
496
+ }
497
+ if (typeof envelope.result !== "string" || !envelope.result.trim()) {
498
+ throw new Error("Claude CLI output contains no result. Check login state (run: claude).");
499
+ }
500
+ let result;
501
+ try {
502
+ result = JSON.parse(envelope.result);
503
+ } catch {
504
+ throw new Error(`Claude CLI returned non-JSON result: ${truncate$2(envelope.result)}`);
505
+ }
506
+ return {
507
+ result,
508
+ meta: {
509
+ conversationId: envelope.session_id ?? null,
510
+ durationSeconds: typeof envelope.duration_ms === "number" ? envelope.duration_ms / 1e3 : null,
511
+ usage: envelope.usage ?? null
512
+ }
513
+ };
514
+ }
515
+ function parseEnvelope(stdout) {
516
+ const trimmed = stdout.trim();
517
+ let parsed = tryParseJson(trimmed);
518
+ if (parsed === null) {
519
+ const firstBrace = trimmed.indexOf("{");
520
+ const lastBrace = trimmed.lastIndexOf("}");
521
+ if (firstBrace >= 0 && lastBrace > firstBrace) {
522
+ parsed = tryParseJson(trimmed.slice(firstBrace, lastBrace + 1));
523
+ }
524
+ }
525
+ if (!parsed || typeof parsed !== "object") {
526
+ throw new Error("Failed to parse Claude CLI JSON output.");
527
+ }
528
+ return parsed;
529
+ }
530
+ function tryParseJson(text) {
531
+ try {
532
+ return JSON.parse(text);
533
+ } catch {
534
+ return null;
535
+ }
536
+ }
537
+ function truncate$2(text) {
538
+ return text.length > 300 ? `${text.slice(0, 300)}...` : text;
539
+ }
540
+ const claudeCliProvider = {
541
+ name: "claude-cli",
542
+ defaultModel: CLAUDE_CLI_DEFAULT_MODEL,
543
+ buildInvocation: buildClaudeCliInvocation,
544
+ parseOutput: parseClaudeCliOutput
545
+ };
459
546
  const GEMINI_API_DEFAULT_MODEL = "gemini-3.6-flash";
460
547
  const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com";
461
548
  async function executeGeminiApi(options) {
@@ -615,7 +702,9 @@ const PROVIDERS = {
615
702
  openai: openaiCompatProvider,
616
703
  "openai-compat": openaiCompatProvider,
617
704
  anthropic: anthropicApiProvider,
618
- claude: anthropicApiProvider
705
+ claude: anthropicApiProvider,
706
+ "claude-cli": claudeCliProvider,
707
+ "claude-code": claudeCliProvider
619
708
  };
620
709
  function resolveProvider(providerName = "antigravity-cli") {
621
710
  const normalized = providerName.trim().toLowerCase();
@@ -755,7 +844,7 @@ function runCommand(providerName, invocation, timeoutMs) {
755
844
  });
756
845
  }
757
846
  const program = new Command();
758
- program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("2.1.0");
847
+ program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("2.2.0");
759
848
  program.command("analyze", { isDefault: true }).description("Analyze an image into structured JSON evidence (default command)").requiredOption("-i, --input <path|url>", "Input image path or https URL").option("-o, --output <path>", "Write result JSON to a file").option("-m, --model <name>", "Provider model name").option("-p, --provider <name>", `Vision provider (${listProviders().join(", ")})`).option("--prompt <text>", "Extra focus for this image").option("--timeout <ms>", "Provider timeout in milliseconds", "180000").option("--provider-bin <path>", "Provider binary path (default: agy)").option("--workdir <path>", "Working directory for the provider").action(async (options) => {
760
849
  try {
761
850
  const timeoutMs = Number.parseInt(options.timeout, 10);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liustack/modlens",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Plug-in vision for text-only LLMs, powered by the free Antigravity CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: modlens
3
- description: "Plug-in vision for text-only models. Use whenever the user shares an image (local path, screenshot, photo, chart, document scan, or image URL) and the active model cannot see images or has no vision tool. Runs the modlens CLI to convert the image into structured JSON evidence: OCR text, layout, semantics, visual clues."
3
+ description: "Plug-in vision for text-only models. Use whenever the user shares an image (local path, screenshot, photo, chart, document scan, or image URL) and the active model cannot see images or has no vision tool. Runs the modlens CLI to convert the image into structured JSON evidence: OCR text, layout, semantics, visual clues. Also use when the user asks how to install, configure, or switch modlens providers (Gemini API key, OpenAI-compatible endpoints, Claude API or Claude Code CLI)."
4
4
  allowed-tools:
5
5
  - Bash
6
6
  ---
@@ -12,6 +12,7 @@ Use this skill when:
12
12
  - The user provides an image path or image URL and asks anything about it
13
13
  - The active model has no native vision (text-only model in a coding agent)
14
14
  - You need OCR text, layout, or chart/document structure as evidence before reasoning
15
+ - The user asks how to configure modlens, get an API key for it, or switch its provider: follow `references/configure.md` and run the commands for them
15
16
 
16
17
  Do not use this skill for:
17
18
 
@@ -26,7 +27,7 @@ modlens --version
26
27
 
27
28
  If `modlens` is missing, run it via `npx @liustack/modlens` instead.
28
29
 
29
- ModLens supports four vision providers. Check what is configured:
30
+ ModLens supports five vision providers. Check what is configured:
30
31
 
31
32
  ```bash
32
33
  modlens config show
@@ -36,8 +37,9 @@ modlens config show
36
37
  - **gemini-api**: needs `GEMINI_API_KEY` env or `modlens config set gemini-api.apiKey <key>` (free key from https://aistudio.google.com).
37
38
  - **openai**: any OpenAI-compatible multimodal endpoint; needs baseUrl + apiKey + model via env (`OPENAI_BASE_URL`, `OPENAI_API_KEY`) or `modlens config set openai.<field> <value>`.
38
39
  - **anthropic**: needs `ANTHROPIC_API_KEY` env or config; defaults to Claude Haiku.
40
+ - **claude-cli**: rides an existing Claude Code login (`claude`), no key, Read-only tool permissions, local files only.
39
41
 
40
- `modlens config init` writes a starter config to `~/.modlens/config.json` when none exists.
42
+ `modlens config init` writes a starter config to `~/.modlens/config.json` when none exists. Full setup recipes per provider: `references/configure.md`.
41
43
 
42
44
  ## Command
43
45
 
@@ -55,7 +57,7 @@ Optional flags:
55
57
  modlens -i <image> -o <output.json> -m <model> --prompt "<extra focus>" --timeout <ms>
56
58
  ```
57
59
 
58
- Speed expectations: `gemini-api` typically 5-10 seconds, `antigravity-cli` 15-40 seconds (full agent loop), `openai`/`anthropic` depend on the endpoint. For dense or hard images on antigravity-cli, try `-m gemini-3.1-pro-high`.
60
+ Speed expectations: `gemini-api` typically 5-10 seconds, `antigravity-cli` 15-40 seconds and `claude-cli` 20-45 seconds (full agent loops), `openai`/`anthropic` depend on the endpoint. For dense or hard images on antigravity-cli, try `-m gemini-3.1-pro-high`.
59
61
 
60
62
  ## Finding the image path in the chat
61
63
 
@@ -85,7 +87,7 @@ Top level: `{ image, provider, result, meta }`. Inside `result`:
85
87
  - `visual`: colors and style clues
86
88
  - `uncertainty[]`: what the vision engine was unsure about
87
89
 
88
- Structure is enforced by schema on antigravity-cli (`--json-schema`), gemini-api (`responseJsonSchema`), and anthropic (forced tool call). The openai route uses a template prompt plus shape validation and fails loudly on mismatch.
90
+ Structure is enforced by schema on antigravity-cli and claude-cli (`--json-schema`), gemini-api (`responseJsonSchema`), and anthropic (forced tool call). The openai route uses a template prompt plus shape validation and fails loudly on mismatch.
89
91
 
90
92
  ## Failure Handling
91
93
 
@@ -0,0 +1,85 @@
1
+ # Configuring ModLens
2
+
3
+ Read this when the user asks how to set up, configure, or switch ModLens providers. Prefer running the commands for the user over explaining them.
4
+
5
+ ## Where config lives
6
+
7
+ `~/.modlens/config.json`, managed by the CLI. Precedence: CLI flags > environment variables > config file > built-in defaults. The default provider with zero config is `antigravity-cli`.
8
+
9
+ ```bash
10
+ modlens config init # write a starter config (refuses to overwrite; --force to redo)
11
+ modlens config show # effective file, API keys masked
12
+ modlens config set provider <name> # change the default provider
13
+ modlens config set <provider>.<field> <value> # fields: apiKey, baseUrl, model
14
+ ```
15
+
16
+ `config set` writes the file with 0600 permissions.
17
+
18
+ ## Provider setup recipes
19
+
20
+ ### antigravity-cli (default, free, no key)
21
+
22
+ Needs Antigravity CLI installed and signed in:
23
+
24
+ ```bash
25
+ curl -fsSL https://antigravity.google/cli/install.sh | bash
26
+ agy # user must complete browser sign-in themselves, then exit
27
+ ```
28
+
29
+ Any free Google account works; no Google AI Pro needed. Sign-in cannot be automated, ask the user to run `agy` once.
30
+
31
+ ### gemini-api (free key, fastest free route, 5-10s)
32
+
33
+ 1. The user creates a key at https://aistudio.google.com (three minutes, no credit card, free tier does not expire).
34
+ 2. Store it either way:
35
+
36
+ ```bash
37
+ modlens config set gemini-api.apiKey <key>
38
+ # or environment: export GEMINI_API_KEY=<key>
39
+ ```
40
+
41
+ Default model `gemini-3.6-flash` has vision on the free tier (about 10-15 requests/min, 1500/day). Free-tier data may be used by Google to improve products; mention this if the user handles sensitive images.
42
+
43
+ ### openai (any OpenAI-compatible multimodal endpoint)
44
+
45
+ Needs three values. Example for DashScope qwen:
46
+
47
+ ```bash
48
+ modlens config set openai.baseUrl https://dashscope.aliyuncs.com/compatible-mode/v1
49
+ modlens config set openai.apiKey <sk-key>
50
+ modlens config set openai.model qwen3.6-27b
51
+ ```
52
+
53
+ For official OpenAI: baseUrl `https://api.openai.com/v1`, a vision-capable model. Environment equivalents: `OPENAI_BASE_URL`, `OPENAI_API_KEY`. The model must be multimodal; text-only models will fail or hallucinate. This route has no server-side schema enforcement, so occasional shape failures are surfaced as explicit errors; retry or switch provider.
54
+
55
+ ### anthropic (Claude API key)
56
+
57
+ ```bash
58
+ modlens config set anthropic.apiKey <sk-ant-key>
59
+ # or: export ANTHROPIC_API_KEY=<key>
60
+ ```
61
+
62
+ Default model is Claude Haiku (`claude-haiku-4-5-20251001`). Schema is enforced through a forced tool call.
63
+
64
+ ### claude-cli (Claude Code login, no key)
65
+
66
+ Rides an existing `claude` sign-in, so it costs the user's Claude subscription quota, not a separate API bill. Requires Claude Code installed and logged in (`claude --version` to check). Runs with `--allowedTools Read` only. Local image files only; for remote URLs use gemini-api instead. Default model alias `haiku`.
67
+
68
+ ```bash
69
+ modlens config set provider claude-cli # make it the default if the user wants
70
+ ```
71
+
72
+ ## Choosing a provider for the user
73
+
74
+ - Wants zero setup and free: `antigravity-cli` (needs agy sign-in, 15-40s per image).
75
+ - Wants fast and free: `gemini-api` (three-minute key, 5-10s).
76
+ - Already pays for Claude: `claude-cli` (no extra key) or `anthropic` (API billing).
77
+ - Has a favorite multimodal endpoint (qwen, GLM, ...): `openai`.
78
+
79
+ ## Troubleshooting
80
+
81
+ - Error names a missing env var or `config set` command: run exactly that.
82
+ - `Provider CLI not found: agy`: install Antigravity CLI or switch provider.
83
+ - `Claude CLI reported ...` or empty result: check `claude` login state.
84
+ - openai route `does not match the vision schema`: retry once, then switch to gemini-api or anthropic.
85
+ - `config init` refusing to run: the file exists; use `modlens config show` first, `--force` only if the user agrees to overwrite.