@co0ontty/wand 4.10.3 → 4.11.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 +4 -4
- package/dist/build-info.json +3 -3
- package/dist/config.d.ts +4 -3
- package/dist/config.js +20 -3
- package/dist/models.d.ts +3 -0
- package/dist/models.js +38 -1
- package/dist/path-repair.js +6 -3
- package/dist/process-manager.js +17 -1
- package/dist/provider-cli-updater.d.ts +1 -1
- package/dist/provider-cli-updater.js +9 -1
- package/dist/server-session-routes.js +3 -3
- package/dist/server-settings-routes.js +5 -0
- package/dist/server-update-routes.js +1 -1
- package/dist/server.js +6 -1
- package/dist/session-ai-context.d.ts +2 -2
- package/dist/session-ai-context.js +8 -2
- package/dist/session-registry.js +3 -1
- package/dist/storage.js +5 -1
- package/dist/structured-provider-common.js +5 -1
- package/dist/structured-qoder-adapter.d.ts +10 -0
- package/dist/structured-qoder-adapter.js +124 -0
- package/dist/structured-session-manager.d.ts +2 -0
- package/dist/structured-session-manager.js +32 -13
- package/dist/system-ai.js +1 -1
- package/dist/types.d.ts +4 -2
- package/dist/web-ui/content/scripts.js +26 -26
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
### Overview
|
|
13
13
|
|
|
14
|
-
Wand is a web console for remotely accessing and managing local CLI tools from a browser. It supports [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Codex](https://github.com/openai/codex),
|
|
14
|
+
Wand is a web console for remotely accessing and managing local CLI tools from a browser. It supports [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Codex](https://github.com/openai/codex), [OpenCode](https://opencode.ai/), [Grok Build](https://grok.com/), and [Qoder CLI](https://docs.qoder.com/en/cli/quick-start), with terminal and structured conversation views, persistent resumable sessions, permission controls, file browsing, and native clients for multiple platforms.
|
|
15
15
|
|
|
16
16
|
The browser password manager extension source lives in `browser-extension/`; setup and supported baseline features are documented in [docs/browser-extension.md](docs/browser-extension.md).
|
|
17
17
|
|
|
@@ -56,7 +56,7 @@ wand config:password
|
|
|
56
56
|
#### Core
|
|
57
57
|
|
|
58
58
|
- **Dual view modes** — switch between raw terminal output and a structured conversation view for the same session
|
|
59
|
-
- **Multiple providers** — create PTY or structured sessions for Claude Code, Codex, and
|
|
59
|
+
- **Multiple providers** — create PTY or structured sessions for Claude Code, Codex, OpenCode, Grok, and Qoder CLI
|
|
60
60
|
- **Session management** — create, archive, and resume sessions; restore Claude native history; show summaries in the session list
|
|
61
61
|
- **Permission control** — visual permission prompts with one-time approval, per-turn memory, and related policies
|
|
62
62
|
|
|
@@ -155,7 +155,7 @@ npm install -g opencode-ai@latest
|
|
|
155
155
|
|
|
156
156
|
### 概览
|
|
157
157
|
|
|
158
|
-
通过浏览器远程访问和管理本地 CLI 工具的 Web 控制台。支持 [Claude Code](https://docs.anthropic.com/en/docs/claude-code)、[Codex](https://github.com/openai/codex)
|
|
158
|
+
通过浏览器远程访问和管理本地 CLI 工具的 Web 控制台。支持 [Claude Code](https://docs.anthropic.com/en/docs/claude-code)、[Codex](https://github.com/openai/codex)、[OpenCode](https://opencode.ai/)、[Grok Build](https://grok.com/) 和 [Qoder CLI](https://docs.qoder.com/zh/cli/quick-start),提供终端和结构化对话双视图、会话持久化与恢复、权限管控、文件浏览和多平台客户端。
|
|
159
159
|
|
|
160
160
|
<p align="center">
|
|
161
161
|
<img src="docs/screenshots/clients-overview.png" width="100%" alt="Web 居中,iOS 和 Android 客户端在两侧的多端详情总览" />
|
|
@@ -205,7 +205,7 @@ npm install -g opencode-ai@latest
|
|
|
205
205
|
#### 核心
|
|
206
206
|
|
|
207
207
|
- **双视图模式** — 终端原始输出和结构化对话视图可随时切换,同一会话两种呈现
|
|
208
|
-
- **多 Provider 支持** — Claude Code、Codex 和
|
|
208
|
+
- **多 Provider 支持** — Claude Code、Codex、OpenCode、Grok 和 Qoder CLI 均可创建 PTY 或结构化会话
|
|
209
209
|
- **会话管理** — 创建、归档、恢复会话;支持从 Claude 原生历史记录恢复;会话列表显示摘要
|
|
210
210
|
- **权限控制** — 可视化权限提示,支持逐次确认、单次批准、本轮记忆等策略
|
|
211
211
|
|
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-07-
|
|
4
|
-
"version": "4.
|
|
2
|
+
"commit": "439abee1c0b8d5601059d7eee4a142adf6cc0c7b",
|
|
3
|
+
"builtAt": "2026-07-17T09:40:00.200Z",
|
|
4
|
+
"version": "4.11.0",
|
|
5
5
|
"channel": "stable"
|
|
6
6
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { WandStorage } from "./storage.js";
|
|
|
7
7
|
* 升级路径:老 JSON 里仍存有这些字段时,首次启动会被搬到 DB(见 migrateLegacyPreferencesToDb),
|
|
8
8
|
* 然后下一次 saveConfig 写回 JSON 时它们会被剥离(见 stripPreferenceFields)。
|
|
9
9
|
*/
|
|
10
|
-
export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "commitCli", "commitModel", "commitAiSource", "systemAi", "defaultThinkingEffort", "structuredRunner", "language", "cardDefaults", "inheritEnv"];
|
|
10
|
+
export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "commitCli", "commitModel", "commitAiSource", "systemAi", "defaultThinkingEffort", "structuredRunner", "language", "cardDefaults", "inheritEnv"];
|
|
11
11
|
export type PreferenceKey = (typeof PREFERENCE_KEYS)[number];
|
|
12
12
|
export declare function isPreferenceKey(key: string): key is PreferenceKey;
|
|
13
13
|
export declare const defaultConfig: () => WandConfig;
|
|
@@ -44,11 +44,12 @@ export declare function writePreferenceToStorage(config: WandConfig, storage: Wa
|
|
|
44
44
|
export declare function validateCommitAiConfig(config: Pick<WandConfig, "commitAiSource" | "systemAi">): void;
|
|
45
45
|
export declare function normalizeCardDefaults(input: unknown): CardExpandDefaults;
|
|
46
46
|
export declare function isExecutionMode(value: unknown): value is ExecutionMode;
|
|
47
|
-
export declare function getProviderDefaultModels(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel">): {
|
|
47
|
+
export declare function getProviderDefaultModels(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel">): {
|
|
48
48
|
claude: string;
|
|
49
49
|
codex: string;
|
|
50
50
|
opencode: string;
|
|
51
51
|
grok: string;
|
|
52
|
+
qoder: string;
|
|
52
53
|
};
|
|
53
|
-
export declare function getDefaultModelForProvider(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel">, provider: SessionProvider | undefined): string;
|
|
54
|
+
export declare function getDefaultModelForProvider(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel">, provider: SessionProvider | undefined): string;
|
|
54
55
|
export declare function normalizeMode(input: string | undefined, fallback: ExecutionMode): ExecutionMode;
|
package/dist/config.js
CHANGED
|
@@ -30,6 +30,7 @@ export const PREFERENCE_KEYS = [
|
|
|
30
30
|
"defaultCodexModel",
|
|
31
31
|
"defaultOpenCodeModel",
|
|
32
32
|
"defaultGrokModel",
|
|
33
|
+
"defaultQoderModel",
|
|
33
34
|
"commitCli",
|
|
34
35
|
"commitModel",
|
|
35
36
|
"commitAiSource",
|
|
@@ -73,6 +74,7 @@ export const defaultConfig = () => ({
|
|
|
73
74
|
defaultCodexModel: "",
|
|
74
75
|
defaultOpenCodeModel: "",
|
|
75
76
|
defaultGrokModel: "",
|
|
77
|
+
defaultQoderModel: "",
|
|
76
78
|
commitCli: "claude",
|
|
77
79
|
commitModel: "",
|
|
78
80
|
commitAiSource: "cli",
|
|
@@ -282,7 +284,7 @@ export function applyStoragePreferences(config, storage) {
|
|
|
282
284
|
const defaults = defaultConfig();
|
|
283
285
|
if (storage.hasPreference(preferenceStorageKey("defaultProvider"))) {
|
|
284
286
|
const v = storage.getPreference(preferenceStorageKey("defaultProvider"), defaults.defaultProvider ?? "claude");
|
|
285
|
-
if (v === "claude" || v === "codex" || v === "opencode" || v === "grok")
|
|
287
|
+
if (v === "claude" || v === "codex" || v === "opencode" || v === "grok" || v === "qoder")
|
|
286
288
|
config.defaultProvider = v;
|
|
287
289
|
}
|
|
288
290
|
if (storage.hasPreference(preferenceStorageKey("defaultSessionKind"))) {
|
|
@@ -320,6 +322,11 @@ export function applyStoragePreferences(config, storage) {
|
|
|
320
322
|
if (typeof v === "string")
|
|
321
323
|
config.defaultGrokModel = v.trim();
|
|
322
324
|
}
|
|
325
|
+
if (storage.hasPreference(preferenceStorageKey("defaultQoderModel"))) {
|
|
326
|
+
const v = storage.getPreference(preferenceStorageKey("defaultQoderModel"), defaults.defaultQoderModel ?? "");
|
|
327
|
+
if (typeof v === "string")
|
|
328
|
+
config.defaultQoderModel = v.trim();
|
|
329
|
+
}
|
|
323
330
|
if (storage.hasPreference(preferenceStorageKey("commitCli"))) {
|
|
324
331
|
const v = storage.getPreference(preferenceStorageKey("commitCli"), defaults.commitCli ?? "claude");
|
|
325
332
|
if (v === "claude" || v === "codex" || v === "opencode")
|
|
@@ -370,7 +377,7 @@ export function writePreferenceToStorage(config, storage, key, value, options =
|
|
|
370
377
|
const dbKey = preferenceStorageKey(key);
|
|
371
378
|
switch (key) {
|
|
372
379
|
case "defaultProvider": {
|
|
373
|
-
if (value !== "claude" && value !== "codex" && value !== "opencode" && value !== "grok")
|
|
380
|
+
if (value !== "claude" && value !== "codex" && value !== "opencode" && value !== "grok" && value !== "qoder")
|
|
374
381
|
throw new Error(`无效 Provider: ${value}`);
|
|
375
382
|
storage.setPreference(dbKey, value);
|
|
376
383
|
config.defaultProvider = value;
|
|
@@ -420,6 +427,12 @@ export function writePreferenceToStorage(config, storage, key, value, options =
|
|
|
420
427
|
config.defaultGrokModel = v;
|
|
421
428
|
break;
|
|
422
429
|
}
|
|
430
|
+
case "defaultQoderModel": {
|
|
431
|
+
const v = typeof value === "string" ? value.trim() : "";
|
|
432
|
+
storage.setPreference(dbKey, v);
|
|
433
|
+
config.defaultQoderModel = v;
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
423
436
|
case "commitCli": {
|
|
424
437
|
if (value !== "claude" && value !== "codex" && value !== "opencode")
|
|
425
438
|
throw new Error(`无效 commit CLI: ${value}`);
|
|
@@ -641,12 +654,13 @@ function mergeWithDefaults(input) {
|
|
|
641
654
|
android: normalizeAndroidApkConfig(input.android) ?? defaults.android,
|
|
642
655
|
macos: normalizeMacosDmgConfig(input.macos) ?? defaults.macos,
|
|
643
656
|
cardDefaults: normalizeCardDefaults(input.cardDefaults),
|
|
644
|
-
defaultProvider: input.defaultProvider === "codex" || input.defaultProvider === "opencode" || input.defaultProvider === "grok" ? input.defaultProvider : "claude",
|
|
657
|
+
defaultProvider: input.defaultProvider === "codex" || input.defaultProvider === "opencode" || input.defaultProvider === "grok" || input.defaultProvider === "qoder" ? input.defaultProvider : "claude",
|
|
645
658
|
defaultSessionKind: input.defaultSessionKind === "pty" ? "pty" : "structured",
|
|
646
659
|
defaultModel: typeof input.defaultModel === "string" ? input.defaultModel.trim() : defaults.defaultModel,
|
|
647
660
|
defaultCodexModel: typeof input.defaultCodexModel === "string" ? input.defaultCodexModel.trim() : defaults.defaultCodexModel,
|
|
648
661
|
defaultOpenCodeModel: typeof input.defaultOpenCodeModel === "string" ? input.defaultOpenCodeModel.trim() : defaults.defaultOpenCodeModel,
|
|
649
662
|
defaultGrokModel: typeof input.defaultGrokModel === "string" ? input.defaultGrokModel.trim() : defaults.defaultGrokModel,
|
|
663
|
+
defaultQoderModel: typeof input.defaultQoderModel === "string" ? input.defaultQoderModel.trim() : defaults.defaultQoderModel,
|
|
650
664
|
commitCli: input.commitCli === "codex" || input.commitCli === "opencode" ? input.commitCli : "claude",
|
|
651
665
|
commitModel: typeof input.commitModel === "string" ? input.commitModel.trim() : defaults.commitModel,
|
|
652
666
|
commitAiSource: input.commitAiSource === "api" ? "api" : "cli",
|
|
@@ -664,6 +678,7 @@ export function getProviderDefaultModels(config) {
|
|
|
664
678
|
codex: (config.defaultCodexModel ?? "").trim(),
|
|
665
679
|
opencode: (config.defaultOpenCodeModel ?? "").trim(),
|
|
666
680
|
grok: (config.defaultGrokModel ?? "").trim(),
|
|
681
|
+
qoder: (config.defaultQoderModel ?? "").trim(),
|
|
667
682
|
};
|
|
668
683
|
}
|
|
669
684
|
export function getDefaultModelForProvider(config, provider) {
|
|
@@ -674,6 +689,8 @@ export function getDefaultModelForProvider(config, provider) {
|
|
|
674
689
|
return defaults.opencode;
|
|
675
690
|
if (provider === "grok")
|
|
676
691
|
return defaults.grok;
|
|
692
|
+
if (provider === "qoder")
|
|
693
|
+
return defaults.qoder;
|
|
677
694
|
return defaults.claude;
|
|
678
695
|
}
|
|
679
696
|
export function normalizeMode(input, fallback) {
|
package/dist/models.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export interface ModelCache {
|
|
|
35
35
|
codexModels: ClaudeModelInfo[];
|
|
36
36
|
opencodeModels: ClaudeModelInfo[];
|
|
37
37
|
grokModels: ClaudeModelInfo[];
|
|
38
|
+
qoderModels: ClaudeModelInfo[];
|
|
38
39
|
claudeVersion: string | null;
|
|
39
40
|
opencodeVersion: string | null;
|
|
40
41
|
refreshedAt: string;
|
|
@@ -47,6 +48,8 @@ export interface ModelCache {
|
|
|
47
48
|
* * grok-4.5 (default)
|
|
48
49
|
*/
|
|
49
50
|
export declare function parseGrokModels(stdout: string): ClaudeModelInfo[];
|
|
51
|
+
/** Parse `qodercli --list-models`, whose custom entries end in their selectable model ID. */
|
|
52
|
+
export declare function parseQoderModels(stdout: string): ClaudeModelInfo[];
|
|
50
53
|
/** Parse `opencode models`, whose stable machine-friendly output is one provider/model id per line. */
|
|
51
54
|
export declare function parseOpenCodeModels(stdout: string): ClaudeModelInfo[];
|
|
52
55
|
/** Parse the machine-readable model registry emitted by the installed Codex CLI. */
|
package/dist/models.js
CHANGED
|
@@ -33,6 +33,14 @@ const GROK_FALLBACK_MODELS = [
|
|
|
33
33
|
{ id: "default", label: "跟随 Grok 默认", alias: true },
|
|
34
34
|
{ id: "grok-4.5", label: "grok-4.5" },
|
|
35
35
|
];
|
|
36
|
+
const QODER_FALLBACK_MODELS = [
|
|
37
|
+
{ id: "default", label: "跟随 Qoder 默认", alias: true },
|
|
38
|
+
{ id: "lite", label: "Lite" },
|
|
39
|
+
{ id: "efficient", label: "Efficient" },
|
|
40
|
+
{ id: "auto", label: "Auto" },
|
|
41
|
+
{ id: "performance", label: "Performance" },
|
|
42
|
+
{ id: "ultimate", label: "Ultimate" },
|
|
43
|
+
];
|
|
36
44
|
let cache = null;
|
|
37
45
|
function cloneModels(models) {
|
|
38
46
|
return models.map((model) => ({ ...model }));
|
|
@@ -198,6 +206,7 @@ function createInitialCache(options) {
|
|
|
198
206
|
codexModels: cloneModels(CODEX_FALLBACK_MODELS),
|
|
199
207
|
opencodeModels: cloneModels(OPENCODE_FALLBACK_MODELS),
|
|
200
208
|
grokModels: cloneModels(GROK_FALLBACK_MODELS),
|
|
209
|
+
qoderModels: cloneModels(QODER_FALLBACK_MODELS),
|
|
201
210
|
claudeVersion: null,
|
|
202
211
|
opencodeVersion: null,
|
|
203
212
|
refreshedAt: now.toISOString(),
|
|
@@ -267,6 +276,15 @@ async function probeGrokModels(runner, env) {
|
|
|
267
276
|
return cloneModels(GROK_FALLBACK_MODELS);
|
|
268
277
|
}
|
|
269
278
|
}
|
|
279
|
+
async function probeQoderModels(runner, env) {
|
|
280
|
+
try {
|
|
281
|
+
const { stdout } = await runner("qodercli", ["--list-models"], { env, timeout: 8000 });
|
|
282
|
+
return parseQoderModels(stdout);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return cloneModels(QODER_FALLBACK_MODELS);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
270
288
|
function createOfficialModelsApi(apiKey) {
|
|
271
289
|
const client = new Anthropic({ apiKey });
|
|
272
290
|
return {
|
|
@@ -374,6 +392,23 @@ export function parseGrokModels(stdout) {
|
|
|
374
392
|
...unique.map((id) => ({ id, label: id })),
|
|
375
393
|
];
|
|
376
394
|
}
|
|
395
|
+
/** Parse `qodercli --list-models`, whose custom entries end in their selectable model ID. */
|
|
396
|
+
export function parseQoderModels(stdout) {
|
|
397
|
+
const discovered = [];
|
|
398
|
+
const seen = new Set(QODER_FALLBACK_MODELS.map((model) => model.id));
|
|
399
|
+
for (const rawLine of stdout.split(/\r?\n/)) {
|
|
400
|
+
const line = rawLine.replace(/\x1b\[[0-?]*[ -\/]*[@-~]/g, "").trim();
|
|
401
|
+
const match = line.match(/^(.*?)\s+\(([A-Za-z0-9][A-Za-z0-9._:-]*\/[A-Za-z0-9][A-Za-z0-9._:/-]*)\)\s*$/);
|
|
402
|
+
if (!match)
|
|
403
|
+
continue;
|
|
404
|
+
const [, displayName, id] = match;
|
|
405
|
+
if (!displayName || !id || seen.has(id))
|
|
406
|
+
continue;
|
|
407
|
+
seen.add(id);
|
|
408
|
+
discovered.push({ id, label: displayName.trim() || id });
|
|
409
|
+
}
|
|
410
|
+
return [...cloneModels(QODER_FALLBACK_MODELS), ...discovered];
|
|
411
|
+
}
|
|
377
412
|
/** Parse `opencode models`, whose stable machine-friendly output is one provider/model id per line. */
|
|
378
413
|
export function parseOpenCodeModels(stdout) {
|
|
379
414
|
const ids = Array.from(new Set(stdout
|
|
@@ -452,11 +487,12 @@ export async function refreshModels(options = {}) {
|
|
|
452
487
|
const now = options.now?.() ?? new Date();
|
|
453
488
|
const env = resolveProbeEnv(options);
|
|
454
489
|
const runner = options.commandRunner ?? defaultCommandRunner;
|
|
455
|
-
const [claudeVersion, codexModels, opencode, grokModels, apiModels] = await Promise.all([
|
|
490
|
+
const [claudeVersion, codexModels, opencode, grokModels, qoderModels, apiModels] = await Promise.all([
|
|
456
491
|
probeClaudeVersion(runner, env),
|
|
457
492
|
probeCodexModels(runner, env),
|
|
458
493
|
probeOpenCode(runner, env),
|
|
459
494
|
probeGrokModels(runner, env),
|
|
495
|
+
probeQoderModels(runner, env),
|
|
460
496
|
listClaudeModelsFromApi(options, env),
|
|
461
497
|
]);
|
|
462
498
|
const priorVerifications = loadClaudeVerifications(options.storage);
|
|
@@ -484,6 +520,7 @@ export async function refreshModels(options = {}) {
|
|
|
484
520
|
codexModels,
|
|
485
521
|
opencodeModels: opencode.models,
|
|
486
522
|
grokModels,
|
|
523
|
+
qoderModels,
|
|
487
524
|
claudeVersion,
|
|
488
525
|
opencodeVersion: opencode.version,
|
|
489
526
|
refreshedAt: now.toISOString(),
|
package/dist/path-repair.js
CHANGED
|
@@ -6,7 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import process from "node:process";
|
|
7
7
|
import { spawn, spawnSync } from "node:child_process";
|
|
8
8
|
/** 关键的 CLI 工具,会被诊断输出。 */
|
|
9
|
-
const PROBE_COMMANDS = ["claude", "codex", "opencode", "grok"];
|
|
9
|
+
const PROBE_COMMANDS = ["claude", "codex", "opencode", "grok", "qodercli"];
|
|
10
10
|
const DEEP_PROBE_TIMEOUT_MS = 4000;
|
|
11
11
|
/**
|
|
12
12
|
* 构造候选 bin 目录列表(按优先级,前面的更可信)。
|
|
@@ -261,7 +261,8 @@ function probeLoginShell(shell, timeoutMs) {
|
|
|
261
261
|
`printf 'CLAUDE\\x1f%s\\n' "$(command -v claude 2>/dev/null)"; ` +
|
|
262
262
|
`printf 'CODEX\\x1f%s\\n' "$(command -v codex 2>/dev/null)"; ` +
|
|
263
263
|
`printf 'OPENCODE\\x1f%s\\n' "$(command -v opencode 2>/dev/null)"; ` +
|
|
264
|
-
`printf 'GROK\\x1f%s\\n' "$(command -v grok 2>/dev/null)"
|
|
264
|
+
`printf 'GROK\\x1f%s\\n' "$(command -v grok 2>/dev/null)"; ` +
|
|
265
|
+
`printf 'QODERCLI\\x1f%s\\n' "$(command -v qodercli 2>/dev/null)"`;
|
|
265
266
|
return new Promise((resolve, reject) => {
|
|
266
267
|
const child = spawn(shell, ["-l", "-c", script], {
|
|
267
268
|
env: {
|
|
@@ -301,7 +302,7 @@ function probeLoginShell(shell, timeoutMs) {
|
|
|
301
302
|
reject(new Error(`login shell exited ${code}: ${stderr.trim().slice(0, 200)}`));
|
|
302
303
|
return;
|
|
303
304
|
}
|
|
304
|
-
const out = { path: "", claude: null, codex: null, opencode: null, grok: null };
|
|
305
|
+
const out = { path: "", claude: null, codex: null, opencode: null, grok: null, qodercli: null };
|
|
305
306
|
for (const line of stdout.split("\n")) {
|
|
306
307
|
const idx = line.indexOf("\x1f");
|
|
307
308
|
if (idx < 0)
|
|
@@ -320,6 +321,8 @@ function probeLoginShell(shell, timeoutMs) {
|
|
|
320
321
|
out.opencode = val;
|
|
321
322
|
else if (key === "GROK")
|
|
322
323
|
out.grok = val;
|
|
324
|
+
else if (key === "QODERCLI")
|
|
325
|
+
out.qodercli = val;
|
|
323
326
|
}
|
|
324
327
|
resolve(out);
|
|
325
328
|
});
|
package/dist/process-manager.js
CHANGED
|
@@ -24,7 +24,9 @@ function resolveProviderFromCommand(command) {
|
|
|
24
24
|
return "codex";
|
|
25
25
|
if (/^opencode\b/.test(command.trim()))
|
|
26
26
|
return "opencode";
|
|
27
|
-
|
|
27
|
+
if (/^grok\b/.test(command.trim()))
|
|
28
|
+
return "grok";
|
|
29
|
+
return /^qodercli\b/.test(command.trim()) ? "qoder" : "claude";
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* Tokenize the restricted shell-command subset accepted by the command
|
|
@@ -2027,6 +2029,20 @@ export class ProcessManager extends EventEmitter {
|
|
|
2027
2029
|
}
|
|
2028
2030
|
return result;
|
|
2029
2031
|
}
|
|
2032
|
+
if (provider === "qoder") {
|
|
2033
|
+
let result = command;
|
|
2034
|
+
const trimmedModel = model?.trim();
|
|
2035
|
+
if (trimmedModel && trimmedModel !== "default" && !/--model(?:\s|=)/.test(result)) {
|
|
2036
|
+
result += ` --model '${trimmedModel.replace(/'/g, "'\\''")}'`;
|
|
2037
|
+
}
|
|
2038
|
+
if ((mode === "managed" || mode === "full-access") && !/--(?:yolo|dangerously-skip-permissions|permission-mode)(?:\s|=|$)/.test(result)) {
|
|
2039
|
+
result += " --permission-mode bypass_permissions";
|
|
2040
|
+
}
|
|
2041
|
+
else if (mode === "auto-edit" && !/--permission-mode(?:\s|=)/.test(result)) {
|
|
2042
|
+
result += " --permission-mode accept_edits";
|
|
2043
|
+
}
|
|
2044
|
+
return result;
|
|
2045
|
+
}
|
|
2030
2046
|
const isClaudeCmd = /^(?:claude|npx\s+claude|[^\s]+\/claude)(?:\s|$)/.test(command);
|
|
2031
2047
|
if (!isClaudeCmd)
|
|
2032
2048
|
return command;
|
|
@@ -36,6 +36,14 @@ const PROVIDER_CLI_SPECS = [
|
|
|
36
36
|
versionArgs: ["--version"],
|
|
37
37
|
updateArgs: ["upgrade"],
|
|
38
38
|
},
|
|
39
|
+
{
|
|
40
|
+
id: "qoder",
|
|
41
|
+
label: "Qoder CLI",
|
|
42
|
+
command: "qodercli",
|
|
43
|
+
npmPackage: "@qoder-ai/qodercli",
|
|
44
|
+
versionArgs: ["--version"],
|
|
45
|
+
updateArgs: ["update"],
|
|
46
|
+
},
|
|
39
47
|
];
|
|
40
48
|
function childEnv(options) {
|
|
41
49
|
return options.env ?? buildChildEnv(options.inheritEnv !== false);
|
|
@@ -66,7 +74,7 @@ function resolveInstallKind(executable, id, version) {
|
|
|
66
74
|
return "npm";
|
|
67
75
|
if (normalized.includes("/cellar/") || normalized.includes("/caskroom/") || normalized.includes("/homebrew/"))
|
|
68
76
|
return "brew";
|
|
69
|
-
if (normalized.includes("/.claude/") || normalized.includes("/.codex/") || normalized.includes("/.opencode/"))
|
|
77
|
+
if (normalized.includes("/.claude/") || normalized.includes("/.codex/") || normalized.includes("/.opencode/") || normalized.includes("/.qoder/"))
|
|
70
78
|
return "native";
|
|
71
79
|
return "unknown";
|
|
72
80
|
}
|
|
@@ -325,11 +325,11 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
325
325
|
app.post("/api/structured-sessions", asyncRoute(async (req, res) => {
|
|
326
326
|
const body = req.body;
|
|
327
327
|
try {
|
|
328
|
-
if (body.provider && body.provider !== "claude" && body.provider !== "codex" && body.provider !== "opencode" && body.provider !== "grok") {
|
|
329
|
-
res.status(400).json({ error: "结构化会话当前仅支持 Claude、Codex、OpenCode 或
|
|
328
|
+
if (body.provider && body.provider !== "claude" && body.provider !== "codex" && body.provider !== "opencode" && body.provider !== "grok" && body.provider !== "qoder") {
|
|
329
|
+
res.status(400).json({ error: "结构化会话当前仅支持 Claude、Codex、OpenCode、Grok 或 Qoder provider。" });
|
|
330
330
|
return;
|
|
331
331
|
}
|
|
332
|
-
const provider = body.provider === "codex" || body.provider === "opencode" || body.provider === "grok" ? body.provider : "claude";
|
|
332
|
+
const provider = body.provider === "codex" || body.provider === "opencode" || body.provider === "grok" || body.provider === "qoder" ? body.provider : "claude";
|
|
333
333
|
const rawModel = typeof body.model === "string" ? body.model.trim() : "";
|
|
334
334
|
const origin = parseSessionCreationOrigin(body);
|
|
335
335
|
const snapshot = structured.createSession({
|
|
@@ -21,6 +21,7 @@ function publicConfig(config) {
|
|
|
21
21
|
defaultCodexModel: defaultModels.codex,
|
|
22
22
|
defaultOpenCodeModel: defaultModels.opencode,
|
|
23
23
|
defaultGrokModel: defaultModels.grok,
|
|
24
|
+
defaultQoderModel: defaultModels.qoder,
|
|
24
25
|
defaultModels,
|
|
25
26
|
};
|
|
26
27
|
}
|
|
@@ -187,6 +188,8 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
187
188
|
stagePreference("defaultOpenCodeModel", body.defaultModels.opencode);
|
|
188
189
|
if (Object.hasOwn(body.defaultModels, "grok"))
|
|
189
190
|
stagePreference("defaultGrokModel", body.defaultModels.grok);
|
|
191
|
+
if (Object.hasOwn(body.defaultModels, "qoder"))
|
|
192
|
+
stagePreference("defaultQoderModel", body.defaultModels.qoder);
|
|
190
193
|
}
|
|
191
194
|
if (body.systemAi !== undefined) {
|
|
192
195
|
if (!body.systemAi || typeof body.systemAi !== "object" || Array.isArray(body.systemAi)) {
|
|
@@ -256,6 +259,7 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
256
259
|
defaultCodexModel: defaults.codex,
|
|
257
260
|
defaultOpenCodeModel: defaults.opencode,
|
|
258
261
|
defaultGrokModel: defaults.grok,
|
|
262
|
+
defaultQoderModel: defaults.qoder,
|
|
259
263
|
defaultModels: defaults,
|
|
260
264
|
});
|
|
261
265
|
});
|
|
@@ -269,6 +273,7 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
269
273
|
defaultCodexModel: defaults.codex,
|
|
270
274
|
defaultOpenCodeModel: defaults.opencode,
|
|
271
275
|
defaultGrokModel: defaults.grok,
|
|
276
|
+
defaultQoderModel: defaults.qoder,
|
|
272
277
|
defaultModels: defaults,
|
|
273
278
|
});
|
|
274
279
|
}
|
|
@@ -127,7 +127,7 @@ export function registerAdminUpdateRoutes(app, deps) {
|
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
129
|
const rawIds = Array.isArray(req.body?.ids) ? req.body.ids : [];
|
|
130
|
-
const ids = rawIds.filter((value) => value === "claude" || value === "codex" || value === "opencode");
|
|
130
|
+
const ids = rawIds.filter((value) => value === "claude" || value === "codex" || value === "opencode" || value === "qoder");
|
|
131
131
|
state.providerCliUpdateInFlight = true;
|
|
132
132
|
try {
|
|
133
133
|
const before = await refreshProviderCliUpdateState(state, config);
|
package/dist/server.js
CHANGED
|
@@ -198,6 +198,7 @@ const CONNECTED_APP_PREFERENCE_KEYS = new Set([
|
|
|
198
198
|
"defaultCodexModel",
|
|
199
199
|
"defaultOpenCodeModel",
|
|
200
200
|
"defaultGrokModel",
|
|
201
|
+
"defaultQoderModel",
|
|
201
202
|
"defaultModels",
|
|
202
203
|
"defaultThinkingEffort",
|
|
203
204
|
"defaultProvider",
|
|
@@ -719,6 +720,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
719
720
|
defaultCodexModel: defaultModels.codex,
|
|
720
721
|
defaultOpenCodeModel: defaultModels.opencode,
|
|
721
722
|
defaultGrokModel: defaultModels.grok,
|
|
723
|
+
defaultQoderModel: defaultModels.qoder,
|
|
722
724
|
defaultModels,
|
|
723
725
|
defaultThinkingEffort: config.defaultThinkingEffort ?? "off",
|
|
724
726
|
commandPresets: config.commandPresets,
|
|
@@ -728,6 +730,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
728
730
|
{ label: "Codex Structured", runner: "codex-cli-exec" },
|
|
729
731
|
{ label: "OpenCode Structured", runner: "opencode-cli-run" },
|
|
730
732
|
{ label: "Grok Structured", runner: "grok-cli-headless" },
|
|
733
|
+
{ label: "Qoder Structured", runner: "qoder-cli-print" },
|
|
731
734
|
],
|
|
732
735
|
structuredChatPersona,
|
|
733
736
|
cardDefaults: config.cardDefaults,
|
|
@@ -955,7 +958,9 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
955
958
|
? "opencode"
|
|
956
959
|
: body.provider === "grok" || /^grok\b/.test(body.command.trim())
|
|
957
960
|
? "grok"
|
|
958
|
-
: "
|
|
961
|
+
: body.provider === "qoder" || /^qodercli\b/.test(body.command.trim())
|
|
962
|
+
? "qoder"
|
|
963
|
+
: "claude";
|
|
959
964
|
const effectiveModel = rawModel || getDefaultModelForProvider(config, provider) || undefined;
|
|
960
965
|
const reqCols = typeof body.cols === "number" && Number.isFinite(body.cols) ? body.cols : undefined;
|
|
961
966
|
const reqRows = typeof body.rows === "number" && Number.isFinite(body.rows) ? body.rows : undefined;
|
|
@@ -13,6 +13,6 @@ export interface SessionAiContext {
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function resolveSessionProvider(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command">): SessionProvider;
|
|
15
15
|
/** Build the provider-specific settings used by session-adjacent AI actions. */
|
|
16
|
-
export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultThinkingEffort" | "inheritEnv">): SessionAiContext;
|
|
16
|
+
export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultThinkingEffort" | "inheritEnv">): SessionAiContext;
|
|
17
17
|
/** Build the AI context for quick-commit actions from their global preferences. */
|
|
18
|
-
export declare function resolveCommitAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultThinkingEffort" | "inheritEnv" | "commitCli" | "commitModel" | "commitAiSource" | "systemAi">): SessionAiContext;
|
|
18
|
+
export declare function resolveCommitAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultThinkingEffort" | "inheritEnv" | "commitCli" | "commitModel" | "commitAiSource" | "systemAi">): SessionAiContext;
|
|
@@ -8,13 +8,15 @@ export function resolveSessionProvider(snapshot) {
|
|
|
8
8
|
if (snapshot.provider === "claude"
|
|
9
9
|
|| snapshot.provider === "codex"
|
|
10
10
|
|| snapshot.provider === "opencode"
|
|
11
|
-
|| snapshot.provider === "grok"
|
|
11
|
+
|| snapshot.provider === "grok"
|
|
12
|
+
|| snapshot.provider === "qoder") {
|
|
12
13
|
return snapshot.provider;
|
|
13
14
|
}
|
|
14
15
|
if (snapshot.structuredState?.provider === "claude"
|
|
15
16
|
|| snapshot.structuredState?.provider === "codex"
|
|
16
17
|
|| snapshot.structuredState?.provider === "opencode"
|
|
17
|
-
|| snapshot.structuredState?.provider === "grok"
|
|
18
|
+
|| snapshot.structuredState?.provider === "grok"
|
|
19
|
+
|| snapshot.structuredState?.provider === "qoder") {
|
|
18
20
|
return snapshot.structuredState.provider;
|
|
19
21
|
}
|
|
20
22
|
const runner = snapshot.runner ?? snapshot.structuredState?.runner;
|
|
@@ -24,6 +26,8 @@ export function resolveSessionProvider(snapshot) {
|
|
|
24
26
|
return "opencode";
|
|
25
27
|
if (runner === "grok-cli-headless")
|
|
26
28
|
return "grok";
|
|
29
|
+
if (runner === "qoder-cli-print")
|
|
30
|
+
return "qoder";
|
|
27
31
|
if (runner === "claude-cli" || runner === "claude-cli-print" || runner === "claude-sdk")
|
|
28
32
|
return "claude";
|
|
29
33
|
if (/^codex\b/i.test(snapshot.command.trim()))
|
|
@@ -32,6 +36,8 @@ export function resolveSessionProvider(snapshot) {
|
|
|
32
36
|
return "opencode";
|
|
33
37
|
if (/^grok\b/i.test(snapshot.command.trim()))
|
|
34
38
|
return "grok";
|
|
39
|
+
if (/^qodercli\b/i.test(snapshot.command.trim()))
|
|
40
|
+
return "qoder";
|
|
35
41
|
return "claude";
|
|
36
42
|
}
|
|
37
43
|
function normalizeModel(value) {
|
package/dist/session-registry.js
CHANGED
|
@@ -129,7 +129,9 @@ export class SessionRegistry {
|
|
|
129
129
|
?? snapshot.structuredState?.provider
|
|
130
130
|
?? (/^codex\b/i.test(snapshot.command.trim())
|
|
131
131
|
? "codex"
|
|
132
|
-
: /^opencode\b/i.test(snapshot.command.trim()) ? "opencode"
|
|
132
|
+
: /^opencode\b/i.test(snapshot.command.trim()) ? "opencode"
|
|
133
|
+
: /^grok\b/i.test(snapshot.command.trim()) ? "grok"
|
|
134
|
+
: /^qodercli\b/i.test(snapshot.command.trim()) ? "qoder" : "claude");
|
|
133
135
|
if (provider === "claude") {
|
|
134
136
|
this.processes.deleteClaudeHistoryFiles([{ claudeSessionId: providerSessionId, cwd: snapshot.cwd }]);
|
|
135
137
|
}
|
package/dist/storage.js
CHANGED
|
@@ -169,7 +169,7 @@ function parseQueuedMessages(raw) {
|
|
|
169
169
|
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : undefined;
|
|
170
170
|
}
|
|
171
171
|
function inferSessionProvider(row) {
|
|
172
|
-
if (row.provider === "claude" || row.provider === "codex" || row.provider === "opencode" || row.provider === "grok") {
|
|
172
|
+
if (row.provider === "claude" || row.provider === "codex" || row.provider === "opencode" || row.provider === "grok" || row.provider === "qoder") {
|
|
173
173
|
return row.provider;
|
|
174
174
|
}
|
|
175
175
|
if (row.runner === "claude-cli" || row.runner === "claude-cli-print") {
|
|
@@ -183,12 +183,16 @@ function inferSessionProvider(row) {
|
|
|
183
183
|
}
|
|
184
184
|
if (row.runner === "grok-cli-headless")
|
|
185
185
|
return "grok";
|
|
186
|
+
if (row.runner === "qoder-cli-print")
|
|
187
|
+
return "qoder";
|
|
186
188
|
if (/^codex\b/i.test(row.command.trim()))
|
|
187
189
|
return "codex";
|
|
188
190
|
if (/^opencode\b/i.test(row.command.trim()))
|
|
189
191
|
return "opencode";
|
|
190
192
|
if (/^grok\b/i.test(row.command.trim()))
|
|
191
193
|
return "grok";
|
|
194
|
+
if (/^qodercli\b/i.test(row.command.trim()))
|
|
195
|
+
return "qoder";
|
|
192
196
|
return /^claude\b/i.test(row.command.trim()) ? "claude" : undefined;
|
|
193
197
|
}
|
|
194
198
|
function parseWorktreeInfo(raw) {
|
|
@@ -5,7 +5,9 @@ export function isStructuredRunnerForProvider(provider, runner) {
|
|
|
5
5
|
return runner === "codex-cli-exec";
|
|
6
6
|
if (provider === "opencode")
|
|
7
7
|
return runner === "opencode-cli-run";
|
|
8
|
-
|
|
8
|
+
if (provider === "grok")
|
|
9
|
+
return runner === "grok-cli-headless";
|
|
10
|
+
return runner === "qoder-cli-print";
|
|
9
11
|
}
|
|
10
12
|
export function defaultStructuredRunner(provider, configuredClaudeRunner = "cli") {
|
|
11
13
|
if (provider === "codex")
|
|
@@ -14,6 +16,8 @@ export function defaultStructuredRunner(provider, configuredClaudeRunner = "cli"
|
|
|
14
16
|
return "opencode-cli-run";
|
|
15
17
|
if (provider === "grok")
|
|
16
18
|
return "grok-cli-headless";
|
|
19
|
+
if (provider === "qoder")
|
|
20
|
+
return "qoder-cli-print";
|
|
17
21
|
return configuredClaudeRunner === "sdk" ? "claude-sdk" : "claude-cli-print";
|
|
18
22
|
}
|
|
19
23
|
export function resolveStructuredRunner(provider, requestedRunner, configuredClaudeRunner = "cli") {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import type { StructuredRunnerAdapter, StructuredRunnerContext, StructuredRunnerExecution, StructuredRunnerObserver } from "./structured-runner.js";
|
|
3
|
+
import type { SessionSnapshot } from "./types.js";
|
|
4
|
+
export declare function buildQoderArgs(session: SessionSnapshot, prompt: string): string[];
|
|
5
|
+
/** Owns the official Qoder CLI print process and its stream-json protocol. */
|
|
6
|
+
export declare class QoderRunner implements StructuredRunnerAdapter {
|
|
7
|
+
private readonly spawnProcess;
|
|
8
|
+
constructor(spawnProcess?: typeof spawn);
|
|
9
|
+
start(context: StructuredRunnerContext, observer: StructuredRunnerObserver): StructuredRunnerExecution;
|
|
10
|
+
}
|