@duetso/agent 0.1.20
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/LICENSE +189 -0
- package/README.md +315 -0
- package/dist/package.json +84 -0
- package/dist/src/cli.d.ts +23 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +754 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/core/serializer.d.ts +3 -0
- package/dist/src/core/serializer.d.ts.map +1 -0
- package/dist/src/core/serializer.js +22 -0
- package/dist/src/core/serializer.js.map +1 -0
- package/dist/src/core/structured-output.d.ts +13 -0
- package/dist/src/core/structured-output.d.ts.map +1 -0
- package/dist/src/core/structured-output.js +41 -0
- package/dist/src/core/structured-output.js.map +1 -0
- package/dist/src/guardrails/firewall.d.ts +7 -0
- package/dist/src/guardrails/firewall.d.ts.map +1 -0
- package/dist/src/guardrails/firewall.js +31 -0
- package/dist/src/guardrails/firewall.js.map +1 -0
- package/dist/src/guardrails/pattern.d.ts +13 -0
- package/dist/src/guardrails/pattern.d.ts.map +1 -0
- package/dist/src/guardrails/pattern.js +70 -0
- package/dist/src/guardrails/pattern.js.map +1 -0
- package/dist/src/guardrails/semantic.d.ts +14 -0
- package/dist/src/guardrails/semantic.d.ts.map +1 -0
- package/dist/src/guardrails/semantic.js +47 -0
- package/dist/src/guardrails/semantic.js.map +1 -0
- package/dist/src/index.d.ts +20 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +20 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/compact-json.d.ts +11 -0
- package/dist/src/lib/compact-json.d.ts.map +1 -0
- package/dist/src/lib/compact-json.js +36 -0
- package/dist/src/lib/compact-json.js.map +1 -0
- package/dist/src/lib/xml.d.ts +3 -0
- package/dist/src/lib/xml.d.ts.map +1 -0
- package/dist/src/lib/xml.js +9 -0
- package/dist/src/lib/xml.js.map +1 -0
- package/dist/src/memory/observation-groups.d.ts +15 -0
- package/dist/src/memory/observation-groups.d.ts.map +1 -0
- package/dist/src/memory/observation-groups.js +159 -0
- package/dist/src/memory/observation-groups.js.map +1 -0
- package/dist/src/memory/observational-prompts.d.ts +27 -0
- package/dist/src/memory/observational-prompts.d.ts.map +1 -0
- package/dist/src/memory/observational-prompts.js +237 -0
- package/dist/src/memory/observational-prompts.js.map +1 -0
- package/dist/src/memory/observational.d.ts +63 -0
- package/dist/src/memory/observational.d.ts.map +1 -0
- package/dist/src/memory/observational.js +605 -0
- package/dist/src/memory/observational.js.map +1 -0
- package/dist/src/memory/storage.d.ts +3 -0
- package/dist/src/memory/storage.d.ts.map +1 -0
- package/dist/src/memory/storage.js +127 -0
- package/dist/src/memory/storage.js.map +1 -0
- package/dist/src/memory/store.d.ts +13 -0
- package/dist/src/memory/store.d.ts.map +1 -0
- package/dist/src/memory/store.js +106 -0
- package/dist/src/memory/store.js.map +1 -0
- package/dist/src/model-resolution/duet-gateway.d.ts +35 -0
- package/dist/src/model-resolution/duet-gateway.d.ts.map +1 -0
- package/dist/src/model-resolution/duet-gateway.js +56 -0
- package/dist/src/model-resolution/duet-gateway.js.map +1 -0
- package/dist/src/model-resolution/index.d.ts +31 -0
- package/dist/src/model-resolution/index.d.ts.map +1 -0
- package/dist/src/model-resolution/index.js +129 -0
- package/dist/src/model-resolution/index.js.map +1 -0
- package/dist/src/session/session-manager.d.ts +45 -0
- package/dist/src/session/session-manager.d.ts.map +1 -0
- package/dist/src/session/session-manager.js +94 -0
- package/dist/src/session/session-manager.js.map +1 -0
- package/dist/src/session/session.d.ts +113 -0
- package/dist/src/session/session.d.ts.map +1 -0
- package/dist/src/session/session.js +308 -0
- package/dist/src/session/session.js.map +1 -0
- package/dist/src/tui/app.d.ts +60 -0
- package/dist/src/tui/app.d.ts.map +1 -0
- package/dist/src/tui/app.js +742 -0
- package/dist/src/tui/app.js.map +1 -0
- package/dist/src/turn-runner/agent-events.d.ts +5 -0
- package/dist/src/turn-runner/agent-events.d.ts.map +1 -0
- package/dist/src/turn-runner/agent-events.js +59 -0
- package/dist/src/turn-runner/agent-events.js.map +1 -0
- package/dist/src/turn-runner/prompts.d.ts +13 -0
- package/dist/src/turn-runner/prompts.d.ts.map +1 -0
- package/dist/src/turn-runner/prompts.js +79 -0
- package/dist/src/turn-runner/prompts.js.map +1 -0
- package/dist/src/turn-runner/shell-state-handle.d.ts +32 -0
- package/dist/src/turn-runner/shell-state-handle.d.ts.map +1 -0
- package/dist/src/turn-runner/shell-state-handle.js +168 -0
- package/dist/src/turn-runner/shell-state-handle.js.map +1 -0
- package/dist/src/turn-runner/skill-context.d.ts +26 -0
- package/dist/src/turn-runner/skill-context.d.ts.map +1 -0
- package/dist/src/turn-runner/skill-context.js +110 -0
- package/dist/src/turn-runner/skill-context.js.map +1 -0
- package/dist/src/turn-runner/skills.d.ts +35 -0
- package/dist/src/turn-runner/skills.d.ts.map +1 -0
- package/dist/src/turn-runner/skills.js +130 -0
- package/dist/src/turn-runner/skills.js.map +1 -0
- package/dist/src/turn-runner/state-machine-controller.d.ts +90 -0
- package/dist/src/turn-runner/state-machine-controller.d.ts.map +1 -0
- package/dist/src/turn-runner/state-machine-controller.js +289 -0
- package/dist/src/turn-runner/state-machine-controller.js.map +1 -0
- package/dist/src/turn-runner/state-machine-session.d.ts +27 -0
- package/dist/src/turn-runner/state-machine-session.d.ts.map +1 -0
- package/dist/src/turn-runner/state-machine-session.js +189 -0
- package/dist/src/turn-runner/state-machine-session.js.map +1 -0
- package/dist/src/turn-runner/tools.d.ts +193 -0
- package/dist/src/turn-runner/tools.d.ts.map +1 -0
- package/dist/src/turn-runner/tools.js +509 -0
- package/dist/src/turn-runner/tools.js.map +1 -0
- package/dist/src/turn-runner/turn-runner.d.ts +160 -0
- package/dist/src/turn-runner/turn-runner.d.ts.map +1 -0
- package/dist/src/turn-runner/turn-runner.js +907 -0
- package/dist/src/turn-runner/turn-runner.js.map +1 -0
- package/dist/src/turn-runner/turn-state.d.ts +6 -0
- package/dist/src/turn-runner/turn-state.d.ts.map +1 -0
- package/dist/src/turn-runner/turn-state.js +32 -0
- package/dist/src/turn-runner/turn-state.js.map +1 -0
- package/dist/src/turn-runner/usage-accounting.d.ts +7 -0
- package/dist/src/turn-runner/usage-accounting.d.ts.map +1 -0
- package/dist/src/turn-runner/usage-accounting.js +49 -0
- package/dist/src/turn-runner/usage-accounting.js.map +1 -0
- package/dist/src/types/agent.d.ts +15 -0
- package/dist/src/types/agent.d.ts.map +1 -0
- package/dist/src/types/agent.js +2 -0
- package/dist/src/types/agent.js.map +1 -0
- package/dist/src/types/config.d.ts +37 -0
- package/dist/src/types/config.d.ts.map +1 -0
- package/dist/src/types/config.js +2 -0
- package/dist/src/types/config.js.map +1 -0
- package/dist/src/types/guardrails.d.ts +34 -0
- package/dist/src/types/guardrails.d.ts.map +1 -0
- package/dist/src/types/guardrails.js +2 -0
- package/dist/src/types/guardrails.js.map +1 -0
- package/dist/src/types/memory.d.ts +151 -0
- package/dist/src/types/memory.d.ts.map +1 -0
- package/dist/src/types/memory.js +2 -0
- package/dist/src/types/memory.js.map +1 -0
- package/dist/src/types/protocol.d.ts +426 -0
- package/dist/src/types/protocol.d.ts.map +1 -0
- package/dist/src/types/protocol.js +2 -0
- package/dist/src/types/protocol.js.map +1 -0
- package/dist/src/types/state-machine.d.ts +344 -0
- package/dist/src/types/state-machine.d.ts.map +1 -0
- package/dist/src/types/state-machine.js +2 -0
- package/dist/src/types/state-machine.js.map +1 -0
- package/package.json +84 -0
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* duet CLI
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* duet "build a todo app in React"
|
|
7
|
+
* duet --model claude-opus-4-7 "refactor auth system"
|
|
8
|
+
* echo "fix the bug in server.ts" | duet
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import { basename, join } from "node:path";
|
|
12
|
+
import { createInterface } from "node:readline/promises";
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
14
|
+
import dotenv from "dotenv";
|
|
15
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
16
|
+
import { shimDuetApiKeyToAiGateway } from "./model-resolution/duet-gateway.js";
|
|
17
|
+
import { formatCompactJson } from "./lib/compact-json.js";
|
|
18
|
+
import { describeModelResolution, resolveCliMemoryModel, resolveCliModel, } from "./model-resolution/index.js";
|
|
19
|
+
import { SessionManager } from "./session/session-manager.js";
|
|
20
|
+
import { discoverInstalledSkills, resolveSkillScope } from "./turn-runner/skills.js";
|
|
21
|
+
import { runTui } from "./tui/app.js";
|
|
22
|
+
const VERSION_CHECK_TIMEOUT_MS = 1_500;
|
|
23
|
+
const DEFAULT_RESUME_HISTORY_LINES = 40;
|
|
24
|
+
const PACKAGE_MANAGERS = ["npm", "bun", "pnpm", "yarn"];
|
|
25
|
+
const PACKAGE_METADATA = {
|
|
26
|
+
name: packageJson.name,
|
|
27
|
+
version: packageJson.version,
|
|
28
|
+
};
|
|
29
|
+
async function main() {
|
|
30
|
+
// Bridge DUET_API_KEY → AI_GATEWAY_API_KEY so the duet-gateway provider
|
|
31
|
+
// resolves auth through pi-ai's vercel-ai-gateway path. Idempotent — caller's
|
|
32
|
+
// explicit AI_GATEWAY_API_KEY wins.
|
|
33
|
+
shimDuetApiKeyToAiGateway();
|
|
34
|
+
const args = process.argv.slice(2);
|
|
35
|
+
if (args[0] === "upgrade") {
|
|
36
|
+
try {
|
|
37
|
+
await runUpgradeCommand(args.slice(1));
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error(`Fatal: ${err.message}`);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (args[0] === "skills") {
|
|
46
|
+
try {
|
|
47
|
+
runSkillsCommand(args.slice(1));
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
console.error(`Fatal: ${err.message}`);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Parse flags
|
|
56
|
+
let modelName;
|
|
57
|
+
let memoryModelName;
|
|
58
|
+
let workDir = process.cwd();
|
|
59
|
+
let resumeSessionId;
|
|
60
|
+
let systemInstructions;
|
|
61
|
+
let systemPromptFiles;
|
|
62
|
+
let resumeHistoryLines = DEFAULT_RESUME_HISTORY_LINES;
|
|
63
|
+
let resumeHistoryLinesExplicit = false;
|
|
64
|
+
let jsonOutput = false;
|
|
65
|
+
const promptParts = [];
|
|
66
|
+
const interactive = Boolean(process.stdin.isTTY ?? process.stdout.isTTY);
|
|
67
|
+
for (let i = 0; i < args.length; i++) {
|
|
68
|
+
switch (args[i]) {
|
|
69
|
+
case "--model":
|
|
70
|
+
case "-m":
|
|
71
|
+
if (!args[i + 1] || args[i + 1]?.startsWith("-"))
|
|
72
|
+
fail(`Missing value for ${args[i]}`);
|
|
73
|
+
modelName = args[++i];
|
|
74
|
+
break;
|
|
75
|
+
case "--memory-model":
|
|
76
|
+
if (!args[i + 1] || args[i + 1]?.startsWith("-"))
|
|
77
|
+
fail(`Missing value for ${args[i]}`);
|
|
78
|
+
memoryModelName = args[++i];
|
|
79
|
+
break;
|
|
80
|
+
case "--workdir":
|
|
81
|
+
case "-w":
|
|
82
|
+
if (!args[i + 1] || args[i + 1]?.startsWith("-"))
|
|
83
|
+
fail(`Missing value for ${args[i]}`);
|
|
84
|
+
workDir = args[++i];
|
|
85
|
+
break;
|
|
86
|
+
case "--resume":
|
|
87
|
+
case "-r":
|
|
88
|
+
if (!args[i + 1] || args[i + 1]?.startsWith("-"))
|
|
89
|
+
fail(`Missing value for ${args[i]}`);
|
|
90
|
+
resumeSessionId = args[++i];
|
|
91
|
+
break;
|
|
92
|
+
case "--resume-history-lines":
|
|
93
|
+
if (!args[i + 1] || args[i + 1]?.startsWith("-"))
|
|
94
|
+
fail(`Missing value for ${args[i]}`);
|
|
95
|
+
try {
|
|
96
|
+
resumeHistoryLines = parseResumeHistoryLines(args[++i], args[i - 1]);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
100
|
+
}
|
|
101
|
+
resumeHistoryLinesExplicit = true;
|
|
102
|
+
break;
|
|
103
|
+
case "--system-prompt":
|
|
104
|
+
if (!args[i + 1] || args[i + 1]?.startsWith("-"))
|
|
105
|
+
fail(`Missing value for ${args[i]}`);
|
|
106
|
+
systemInstructions = args[++i];
|
|
107
|
+
break;
|
|
108
|
+
case "--system-prompt-file":
|
|
109
|
+
if (!args[i + 1] || args[i + 1]?.startsWith("-"))
|
|
110
|
+
fail(`Missing value for ${args[i]}`);
|
|
111
|
+
systemPromptFiles = [...(systemPromptFiles ?? []), args[++i]];
|
|
112
|
+
break;
|
|
113
|
+
case "--no-system-prompt-files":
|
|
114
|
+
systemPromptFiles = [];
|
|
115
|
+
break;
|
|
116
|
+
case "--json":
|
|
117
|
+
jsonOutput = true;
|
|
118
|
+
break;
|
|
119
|
+
case "--version":
|
|
120
|
+
case "-v": {
|
|
121
|
+
console.log(PACKAGE_METADATA.version);
|
|
122
|
+
process.exit(0);
|
|
123
|
+
}
|
|
124
|
+
case "--help":
|
|
125
|
+
case "-h":
|
|
126
|
+
printHelp(PACKAGE_METADATA.name);
|
|
127
|
+
process.exit(0);
|
|
128
|
+
default:
|
|
129
|
+
if (args[i]?.startsWith("-")) {
|
|
130
|
+
fail(`Unknown option: ${args[i]}`);
|
|
131
|
+
}
|
|
132
|
+
promptParts.push(args[i]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
let prompt = promptParts.join(" ");
|
|
136
|
+
// Read from stdin if no prompt is provided
|
|
137
|
+
if (!prompt && !interactive) {
|
|
138
|
+
const chunks = [];
|
|
139
|
+
for await (const chunk of process.stdin) {
|
|
140
|
+
chunks.push(chunk);
|
|
141
|
+
}
|
|
142
|
+
prompt = Buffer.concat(chunks).toString("utf-8").trim();
|
|
143
|
+
}
|
|
144
|
+
if (!prompt && jsonOutput && interactive) {
|
|
145
|
+
prompt = await readInteractivePrompt();
|
|
146
|
+
}
|
|
147
|
+
if (!prompt && !resumeSessionId && !interactive) {
|
|
148
|
+
console.error("Usage: duet <prompt>");
|
|
149
|
+
console.error(' e.g., duet "build a todo app"');
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
const dotenvResult = dotenv.config({ path: join(workDir, ".env"), quiet: true });
|
|
153
|
+
const dotenvKeys = new Set(Object.keys(dotenvResult.parsed ?? {}));
|
|
154
|
+
const modelResolution = resolveCliModel(modelName, dotenvKeys);
|
|
155
|
+
modelName = modelResolution.modelName;
|
|
156
|
+
const memoryModelResolution = resolveCliMemoryModel(memoryModelName, dotenvKeys);
|
|
157
|
+
memoryModelName = memoryModelResolution.modelName;
|
|
158
|
+
if (modelName && modelName.indexOf(":") <= 0) {
|
|
159
|
+
throw new Error("Models must use provider:modelId syntax");
|
|
160
|
+
}
|
|
161
|
+
if (memoryModelName && memoryModelName.indexOf(":") <= 0) {
|
|
162
|
+
throw new Error("Memory model must use provider:modelId syntax");
|
|
163
|
+
}
|
|
164
|
+
// Build config
|
|
165
|
+
const config = {
|
|
166
|
+
...(modelName ? { model: modelName } : {}),
|
|
167
|
+
...(memoryModelName ? { memoryModel: memoryModelName } : {}),
|
|
168
|
+
cwd: workDir,
|
|
169
|
+
...(systemInstructions ? { systemInstructions } : {}),
|
|
170
|
+
...(systemPromptFiles ? { systemPromptFiles } : {}),
|
|
171
|
+
};
|
|
172
|
+
// The TUI owns rendering when active, so we suppress stdout step printing
|
|
173
|
+
// there to avoid corrupting the alternate-screen UI.
|
|
174
|
+
const useTui = interactive && !jsonOutput;
|
|
175
|
+
const newVersionNotice = await getNewVersionNotice();
|
|
176
|
+
if (!useTui) {
|
|
177
|
+
if (newVersionNotice)
|
|
178
|
+
process.stderr.write(`${newVersionNotice}\n`);
|
|
179
|
+
process.stderr.write(`Model: ${modelName}\n`);
|
|
180
|
+
process.stderr.write(`Source: ${describeModelResolution(modelResolution)}\n`);
|
|
181
|
+
process.stderr.write(`Memory model: ${memoryModelName}\n`);
|
|
182
|
+
process.stderr.write(`Memory source: ${describeModelResolution(memoryModelResolution)}\n`);
|
|
183
|
+
}
|
|
184
|
+
const manager = new SessionManager(config);
|
|
185
|
+
let streamedTextThisTurn = false;
|
|
186
|
+
let activeTextDelta = false;
|
|
187
|
+
let activeTextDeltaNeedsNewline = false;
|
|
188
|
+
let activeReasoningDelta = false;
|
|
189
|
+
let activeReasoningDeltaNeedsNewline = false;
|
|
190
|
+
const finishActiveDeltaStreams = () => {
|
|
191
|
+
if (activeTextDelta) {
|
|
192
|
+
if (activeTextDeltaNeedsNewline)
|
|
193
|
+
process.stdout.write("\n");
|
|
194
|
+
activeTextDelta = false;
|
|
195
|
+
activeTextDeltaNeedsNewline = false;
|
|
196
|
+
}
|
|
197
|
+
if (activeReasoningDelta) {
|
|
198
|
+
if (activeReasoningDeltaNeedsNewline)
|
|
199
|
+
process.stderr.write("\n");
|
|
200
|
+
process.stderr.write("[/reasoning]\n");
|
|
201
|
+
activeReasoningDelta = false;
|
|
202
|
+
activeReasoningDeltaNeedsNewline = false;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
manager.subscribe(({ event }) => {
|
|
206
|
+
if (jsonOutput) {
|
|
207
|
+
process.stdout.write(`${JSON.stringify(event)}\n`);
|
|
208
|
+
}
|
|
209
|
+
else if (event.type === "step" && !useTui) {
|
|
210
|
+
if (event.step.type === "text_delta") {
|
|
211
|
+
streamedTextThisTurn = true;
|
|
212
|
+
activeTextDelta = true;
|
|
213
|
+
activeTextDeltaNeedsNewline = !event.step.delta.endsWith("\n");
|
|
214
|
+
process.stdout.write(event.step.delta);
|
|
215
|
+
}
|
|
216
|
+
else if (event.step.type === "reasoning_delta") {
|
|
217
|
+
if (!activeReasoningDelta)
|
|
218
|
+
process.stderr.write("\n[reasoning]\n");
|
|
219
|
+
activeReasoningDelta = true;
|
|
220
|
+
activeReasoningDeltaNeedsNewline = !event.step.delta.endsWith("\n");
|
|
221
|
+
process.stderr.write(event.step.delta);
|
|
222
|
+
}
|
|
223
|
+
else if (event.step.type === "text") {
|
|
224
|
+
streamedTextThisTurn = true;
|
|
225
|
+
if (activeTextDelta) {
|
|
226
|
+
finishActiveDeltaStreams();
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
handleStep(event.step);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else if (event.step.type === "reasoning" && activeReasoningDelta) {
|
|
233
|
+
finishActiveDeltaStreams();
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
handleStep(event.step);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else if (event.type === "step" &&
|
|
240
|
+
(event.step.type === "text" || event.step.type === "text_delta")) {
|
|
241
|
+
// Track streaming even when the TUI is rendering, so post-TUI fallback
|
|
242
|
+
// result handling stays consistent.
|
|
243
|
+
streamedTextThisTurn = true;
|
|
244
|
+
}
|
|
245
|
+
else if (!jsonOutput && !useTui && isTerminalEvent(event)) {
|
|
246
|
+
finishActiveDeltaStreams();
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
try {
|
|
250
|
+
const session = resumeSessionId
|
|
251
|
+
? manager.resume(resumeSessionId)
|
|
252
|
+
: manager.create({
|
|
253
|
+
...(config.mode ? { mode: config.mode } : {}),
|
|
254
|
+
// Defer the prompt for TUI sessions so the user sees the prompt
|
|
255
|
+
// streamed inside the UI rather than during the pre-TUI phase.
|
|
256
|
+
...(useTui || !prompt ? {} : { prompt }),
|
|
257
|
+
});
|
|
258
|
+
let terminal;
|
|
259
|
+
let initialTuiPrompt;
|
|
260
|
+
let resumedHistory;
|
|
261
|
+
if (resumeSessionId) {
|
|
262
|
+
// Force-load the persisted state.json so setup hands the resumed
|
|
263
|
+
// state to the runner and any TUI history replays before new turns.
|
|
264
|
+
await session.hydrate();
|
|
265
|
+
if (!session.getState()) {
|
|
266
|
+
throw new Error(`Unknown session: ${resumeSessionId}`);
|
|
267
|
+
}
|
|
268
|
+
// Setup runs against the hydrated state; manager.create() already
|
|
269
|
+
// dispatched setup for fresh sessions.
|
|
270
|
+
await session.start();
|
|
271
|
+
resumedHistory = session.getState()?.agent.messages;
|
|
272
|
+
}
|
|
273
|
+
if (prompt && resumeSessionId) {
|
|
274
|
+
streamedTextThisTurn = false;
|
|
275
|
+
if (useTui) {
|
|
276
|
+
initialTuiPrompt = prompt;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
await session.prompt({ message: prompt });
|
|
280
|
+
terminal = await session.waitForTerminal();
|
|
281
|
+
handleTerminal(terminal, {
|
|
282
|
+
suppressHumanOutput: jsonOutput,
|
|
283
|
+
suppressResult: streamedTextThisTurn,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else if (prompt && !resumeSessionId) {
|
|
288
|
+
if (useTui) {
|
|
289
|
+
initialTuiPrompt = prompt;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
terminal = await session.waitForTerminal();
|
|
293
|
+
handleTerminal(terminal, {
|
|
294
|
+
suppressHumanOutput: jsonOutput,
|
|
295
|
+
suppressResult: streamedTextThisTurn,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (useTui) {
|
|
300
|
+
terminal = await runTui({
|
|
301
|
+
session,
|
|
302
|
+
...(initialTuiPrompt ? { initialPrompt: initialTuiPrompt } : {}),
|
|
303
|
+
...(resumedHistory ? { history: resumedHistory } : {}),
|
|
304
|
+
resumeHistoryLines,
|
|
305
|
+
modelName,
|
|
306
|
+
modelSource: describeModelResolution(modelResolution),
|
|
307
|
+
memoryModelName,
|
|
308
|
+
memoryModelSource: describeModelResolution(memoryModelResolution),
|
|
309
|
+
workDir,
|
|
310
|
+
sessionId: session.id,
|
|
311
|
+
packageVersion: PACKAGE_METADATA.version,
|
|
312
|
+
...(newVersionNotice ? { newVersionNotice } : {}),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
process.stderr.write(`Resume: ${resumeCommand(session.id, {
|
|
316
|
+
modelName,
|
|
317
|
+
memoryModelName,
|
|
318
|
+
workDir,
|
|
319
|
+
systemInstructions,
|
|
320
|
+
systemPromptFiles,
|
|
321
|
+
...(resumeHistoryLinesExplicit ? { resumeHistoryLines } : {}),
|
|
322
|
+
})}\n`);
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
console.error(`Fatal: ${err.message}`);
|
|
326
|
+
process.exitCode = 1;
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
await manager.dispose();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function handleTerminal(terminal, options = {}) {
|
|
333
|
+
if (options.suppressHumanOutput)
|
|
334
|
+
return;
|
|
335
|
+
if (terminal.type === "complete" && terminal.error) {
|
|
336
|
+
throw new Error(terminal.error);
|
|
337
|
+
}
|
|
338
|
+
if (terminal.type === "complete" && terminal.result && !options.suppressResult) {
|
|
339
|
+
process.stdout.write(`${terminal.result}\n`);
|
|
340
|
+
}
|
|
341
|
+
if (terminal.type === "ask") {
|
|
342
|
+
for (const question of terminal.questions) {
|
|
343
|
+
process.stdout.write(`${question.question}\n`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (terminal.type === "interrupted") {
|
|
347
|
+
process.stderr.write("Interrupted.\n");
|
|
348
|
+
}
|
|
349
|
+
if (terminal.type === "sleep") {
|
|
350
|
+
process.stderr.write(`Sleeping until ${new Date(terminal.wakeAt).toISOString()}.\n`);
|
|
351
|
+
}
|
|
352
|
+
if (terminal.usage) {
|
|
353
|
+
process.stderr.write(`${formatUsage(terminal.usage)}\n`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function isTerminalEvent(event) {
|
|
357
|
+
return (event.type === "complete" ||
|
|
358
|
+
event.type === "ask" ||
|
|
359
|
+
event.type === "interrupted" ||
|
|
360
|
+
event.type === "sleep");
|
|
361
|
+
}
|
|
362
|
+
function formatUsage(usage) {
|
|
363
|
+
const parts = [`in=${usage.inputTokens}`, `out=${usage.outputTokens}`];
|
|
364
|
+
if (usage.cachedInputTokens !== undefined)
|
|
365
|
+
parts.push(`cached=${usage.cachedInputTokens}`);
|
|
366
|
+
let line = `Tokens: ${parts.join(" ")}`;
|
|
367
|
+
if (usage.costUsd !== undefined)
|
|
368
|
+
line += ` \u00b7 Cost: $${usage.costUsd.toFixed(4)}`;
|
|
369
|
+
return line;
|
|
370
|
+
}
|
|
371
|
+
function handleStep(step) {
|
|
372
|
+
if (step.type === "text_delta") {
|
|
373
|
+
process.stdout.write(step.delta);
|
|
374
|
+
}
|
|
375
|
+
if (step.type === "reasoning_delta") {
|
|
376
|
+
process.stderr.write(step.delta);
|
|
377
|
+
}
|
|
378
|
+
if (step.type === "text") {
|
|
379
|
+
process.stdout.write(`${step.text}\n`);
|
|
380
|
+
}
|
|
381
|
+
if (step.type === "reasoning") {
|
|
382
|
+
process.stderr.write(formatReasoning(step.text));
|
|
383
|
+
}
|
|
384
|
+
if (step.type === "tool_call") {
|
|
385
|
+
process.stderr.write(formatToolCall(step));
|
|
386
|
+
}
|
|
387
|
+
if (step.type === "system") {
|
|
388
|
+
process.stderr.write(`${step.message}\n`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function formatReasoning(text) {
|
|
392
|
+
const trimmed = text.trim();
|
|
393
|
+
if (!trimmed)
|
|
394
|
+
return "";
|
|
395
|
+
return `\n[reasoning]\n${trimmed}\n[/reasoning]\n`;
|
|
396
|
+
}
|
|
397
|
+
function formatToolCall(step) {
|
|
398
|
+
const status = step.status ? ` ${step.status}` : "";
|
|
399
|
+
const input = step.input === undefined ? "" : `\n${formatCompactJson(step.input)}`;
|
|
400
|
+
let output = "";
|
|
401
|
+
if (step.output && step.output.length > 0) {
|
|
402
|
+
const text = step.output
|
|
403
|
+
.filter((b) => b.type === "text")
|
|
404
|
+
.map((b) => b.text)
|
|
405
|
+
.join("\n");
|
|
406
|
+
if (text) {
|
|
407
|
+
const label = step.status === "error" ? "output error" : "output";
|
|
408
|
+
output = `\n[${label}]\n${text}\n[/output]\n`;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return `\n[tool ${step.toolName}${status}]${input}${output}\n[/tool]\n`;
|
|
412
|
+
}
|
|
413
|
+
function fail(message) {
|
|
414
|
+
console.error(`Fatal: ${message}`);
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
export function parseResumeHistoryLines(value, optionName = "--resume-history-lines") {
|
|
418
|
+
if (!/^\d+$/.test(value)) {
|
|
419
|
+
throw new Error(`${optionName} must be a non-negative integer`);
|
|
420
|
+
}
|
|
421
|
+
return Number(value);
|
|
422
|
+
}
|
|
423
|
+
async function getNewVersionNotice() {
|
|
424
|
+
try {
|
|
425
|
+
const latestVersion = await fetchLatestPackageVersion(PACKAGE_METADATA.name);
|
|
426
|
+
if (!latestVersion)
|
|
427
|
+
return undefined;
|
|
428
|
+
if (compareSemverVersions(latestVersion, PACKAGE_METADATA.version) <= 0)
|
|
429
|
+
return undefined;
|
|
430
|
+
return formatNewVersionNotice(PACKAGE_METADATA.name, PACKAGE_METADATA.version, latestVersion);
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
// Version checks should never block CLI startup or hide the real command output.
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
export function formatNewVersionNotice(packageName, currentVersion, latestVersion) {
|
|
438
|
+
return `Update available: ${packageName} ${currentVersion} -> ${latestVersion}. Run: duet upgrade`;
|
|
439
|
+
}
|
|
440
|
+
async function fetchLatestPackageVersion(packageName) {
|
|
441
|
+
const controller = new AbortController();
|
|
442
|
+
const timeout = setTimeout(() => controller.abort(), VERSION_CHECK_TIMEOUT_MS);
|
|
443
|
+
try {
|
|
444
|
+
const metadataUrl = `https://registry.npmjs.org/${packageName.replace("/", "%2F")}`;
|
|
445
|
+
const response = await fetch(metadataUrl, { signal: controller.signal });
|
|
446
|
+
if (!response.ok)
|
|
447
|
+
return undefined;
|
|
448
|
+
const metadata = (await response.json());
|
|
449
|
+
const latest = metadata["dist-tags"]?.latest;
|
|
450
|
+
return typeof latest === "string" ? latest : undefined;
|
|
451
|
+
}
|
|
452
|
+
finally {
|
|
453
|
+
clearTimeout(timeout);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
export function compareSemverVersions(left, right) {
|
|
457
|
+
const leftParts = parseSemverVersion(left);
|
|
458
|
+
const rightParts = parseSemverVersion(right);
|
|
459
|
+
for (let i = 0; i < 3; i++) {
|
|
460
|
+
const delta = leftParts.numbers[i] - rightParts.numbers[i];
|
|
461
|
+
if (delta !== 0)
|
|
462
|
+
return Math.sign(delta);
|
|
463
|
+
}
|
|
464
|
+
if (leftParts.prerelease === rightParts.prerelease)
|
|
465
|
+
return 0;
|
|
466
|
+
if (!leftParts.prerelease)
|
|
467
|
+
return 1;
|
|
468
|
+
if (!rightParts.prerelease)
|
|
469
|
+
return -1;
|
|
470
|
+
return leftParts.prerelease.localeCompare(rightParts.prerelease);
|
|
471
|
+
}
|
|
472
|
+
function parseSemverVersion(version) {
|
|
473
|
+
const [main = "", prerelease] = version.replace(/^v/, "").split("-", 2);
|
|
474
|
+
const [major = "0", minor = "0", patch = "0"] = main.split(".");
|
|
475
|
+
return {
|
|
476
|
+
numbers: [Number(major) || 0, Number(minor) || 0, Number(patch) || 0],
|
|
477
|
+
...(prerelease ? { prerelease } : {}),
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
async function runUpgradeCommand(args) {
|
|
481
|
+
let packageManager = detectPackageManager();
|
|
482
|
+
let dryRun = false;
|
|
483
|
+
const packageName = PACKAGE_METADATA.name;
|
|
484
|
+
for (let i = 0; i < args.length; i++) {
|
|
485
|
+
switch (args[i]) {
|
|
486
|
+
case "--manager":
|
|
487
|
+
if (!args[i + 1] || args[i + 1]?.startsWith("-"))
|
|
488
|
+
fail(`Missing value for ${args[i]}`);
|
|
489
|
+
packageManager = parsePackageManager(args[++i]);
|
|
490
|
+
break;
|
|
491
|
+
case "--dry-run":
|
|
492
|
+
dryRun = true;
|
|
493
|
+
break;
|
|
494
|
+
case "--help":
|
|
495
|
+
case "-h":
|
|
496
|
+
printUpgradeHelp(packageName);
|
|
497
|
+
return;
|
|
498
|
+
default:
|
|
499
|
+
fail(`Unknown upgrade option: ${args[i]}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const command = globalUpgradeCommand(packageManager, packageName);
|
|
503
|
+
const commandText = command.map(shellQuote).join(" ");
|
|
504
|
+
if (dryRun) {
|
|
505
|
+
console.log(commandText);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
console.error(`Upgrading ${packageName} with ${packageManager}...`);
|
|
509
|
+
await runCommand(command[0], command.slice(1));
|
|
510
|
+
}
|
|
511
|
+
function runSkillsCommand(args) {
|
|
512
|
+
let workDir = process.cwd();
|
|
513
|
+
for (let i = 0; i < args.length; i++) {
|
|
514
|
+
switch (args[i]) {
|
|
515
|
+
case "--workdir":
|
|
516
|
+
case "-w":
|
|
517
|
+
if (!args[i + 1] || args[i + 1]?.startsWith("-"))
|
|
518
|
+
fail(`Missing value for ${args[i]}`);
|
|
519
|
+
workDir = args[++i];
|
|
520
|
+
break;
|
|
521
|
+
case "--help":
|
|
522
|
+
case "-h":
|
|
523
|
+
printSkillsHelp();
|
|
524
|
+
return;
|
|
525
|
+
default:
|
|
526
|
+
fail(`Unknown skills option: ${args[i]}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const { skills, collisions } = discoverInstalledSkills(workDir);
|
|
530
|
+
const output = skills.map((skill) => ({
|
|
531
|
+
name: skill.name,
|
|
532
|
+
description: skill.description,
|
|
533
|
+
path: skill.baseDir,
|
|
534
|
+
scope: resolveSkillScope(skill, workDir),
|
|
535
|
+
}));
|
|
536
|
+
process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
|
|
537
|
+
for (const collision of collisions) {
|
|
538
|
+
process.stderr.write(`[skill collision] "${collision.name}": kept ${collision.winnerPath}, ignored ${collision.loserPath}\n`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function parsePackageManager(value) {
|
|
542
|
+
if (PACKAGE_MANAGERS.includes(value))
|
|
543
|
+
return value;
|
|
544
|
+
fail(`Unsupported package manager: ${value}`);
|
|
545
|
+
}
|
|
546
|
+
function detectPackageManager() {
|
|
547
|
+
return detectPackageManagerFromContext({
|
|
548
|
+
userAgent: process.env.npm_config_user_agent,
|
|
549
|
+
runtimeExecutable: process.argv[0],
|
|
550
|
+
cliFilePath: fileURLToPath(import.meta.url),
|
|
551
|
+
scriptPath: process.argv[1],
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
export function detectPackageManagerFromContext(context) {
|
|
555
|
+
const userAgent = context.userAgent ?? "";
|
|
556
|
+
for (const packageManager of PACKAGE_MANAGERS) {
|
|
557
|
+
if (userAgent.startsWith(`${packageManager}/`))
|
|
558
|
+
return packageManager;
|
|
559
|
+
}
|
|
560
|
+
for (const rawPath of [context.cliFilePath, context.scriptPath]) {
|
|
561
|
+
const path = rawPath?.replace(/\\/g, "/");
|
|
562
|
+
if (!path)
|
|
563
|
+
continue;
|
|
564
|
+
if (path.includes("/.bun/install/global/") || path.includes("/.bun/bin/"))
|
|
565
|
+
return "bun";
|
|
566
|
+
if (path.includes("/.pnpm/") || path.includes("/share/pnpm/"))
|
|
567
|
+
return "pnpm";
|
|
568
|
+
if (path.includes("/.config/yarn/global/") || path.includes("/yarn/global/"))
|
|
569
|
+
return "yarn";
|
|
570
|
+
if (path.includes("/node_modules/"))
|
|
571
|
+
return "npm";
|
|
572
|
+
}
|
|
573
|
+
if (basename(context.runtimeExecutable ?? "").includes("bun"))
|
|
574
|
+
return "bun";
|
|
575
|
+
return "npm";
|
|
576
|
+
}
|
|
577
|
+
function globalUpgradeCommand(packageManager, packageName) {
|
|
578
|
+
const packageSpec = `${packageName}@latest`;
|
|
579
|
+
if (packageManager === "bun")
|
|
580
|
+
return ["bun", "add", "--global", packageSpec];
|
|
581
|
+
if (packageManager === "pnpm")
|
|
582
|
+
return ["pnpm", "add", "--global", packageSpec];
|
|
583
|
+
if (packageManager === "yarn")
|
|
584
|
+
return ["yarn", "global", "add", packageSpec];
|
|
585
|
+
return ["npm", "install", "--global", packageSpec];
|
|
586
|
+
}
|
|
587
|
+
async function runCommand(command, args) {
|
|
588
|
+
const child = spawn(command, args, { stdio: "inherit" });
|
|
589
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
590
|
+
child.once("error", reject);
|
|
591
|
+
child.once("exit", resolve);
|
|
592
|
+
});
|
|
593
|
+
if (exitCode !== 0) {
|
|
594
|
+
process.exitCode = exitCode ?? 1;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
async function readInteractivePrompt() {
|
|
598
|
+
const rl = createInterface({
|
|
599
|
+
input: process.stdin,
|
|
600
|
+
output: process.stderr,
|
|
601
|
+
});
|
|
602
|
+
try {
|
|
603
|
+
let prompt = "";
|
|
604
|
+
while (!prompt) {
|
|
605
|
+
prompt = (await rl.question("> ")).trim();
|
|
606
|
+
}
|
|
607
|
+
return prompt;
|
|
608
|
+
}
|
|
609
|
+
finally {
|
|
610
|
+
rl.close();
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function resumeCommand(sessionId, input) {
|
|
614
|
+
const command = [
|
|
615
|
+
detectInvocationPrefix(),
|
|
616
|
+
"--resume",
|
|
617
|
+
shellQuote(sessionId),
|
|
618
|
+
"--workdir",
|
|
619
|
+
shellQuote(input.workDir),
|
|
620
|
+
];
|
|
621
|
+
if (input.modelName) {
|
|
622
|
+
command.push("--model", shellQuote(input.modelName));
|
|
623
|
+
}
|
|
624
|
+
if (input.memoryModelName) {
|
|
625
|
+
command.push("--memory-model", shellQuote(input.memoryModelName));
|
|
626
|
+
}
|
|
627
|
+
if (input.systemInstructions) {
|
|
628
|
+
command.push("--system-prompt", shellQuote(input.systemInstructions));
|
|
629
|
+
}
|
|
630
|
+
if (input.systemPromptFiles) {
|
|
631
|
+
if (input.systemPromptFiles.length === 0) {
|
|
632
|
+
command.push("--no-system-prompt-files");
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
for (const fileName of input.systemPromptFiles) {
|
|
636
|
+
command.push("--system-prompt-file", shellQuote(fileName));
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (input.resumeHistoryLines !== undefined) {
|
|
641
|
+
command.push("--resume-history-lines", String(input.resumeHistoryLines));
|
|
642
|
+
}
|
|
643
|
+
return command.join(" ");
|
|
644
|
+
}
|
|
645
|
+
// Detect how this CLI was invoked so the resume hint copy-pastes back into
|
|
646
|
+
// the user's actual shell. `bun run cli` and `bun src/cli.ts` are common
|
|
647
|
+
// during local development; the published bin is `duet`.
|
|
648
|
+
function detectInvocationPrefix() {
|
|
649
|
+
const scriptPath = process.argv[1] ?? "";
|
|
650
|
+
const base = basename(scriptPath);
|
|
651
|
+
if (process.env.npm_lifecycle_event === "cli")
|
|
652
|
+
return "bun run cli";
|
|
653
|
+
if (base === "cli.ts" || scriptPath.includes("/src/cli.ts"))
|
|
654
|
+
return "bun src/cli.ts";
|
|
655
|
+
return "duet";
|
|
656
|
+
}
|
|
657
|
+
function shellQuote(value) {
|
|
658
|
+
if (/^[A-Za-z0-9_./:=+-]+$/.test(value))
|
|
659
|
+
return value;
|
|
660
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
661
|
+
}
|
|
662
|
+
function printHelp(packageName) {
|
|
663
|
+
console.log(`
|
|
664
|
+
duet — An opinionated full-stack agent runner
|
|
665
|
+
|
|
666
|
+
USAGE
|
|
667
|
+
duet [options] [prompt]
|
|
668
|
+
duet skills [--workdir <path>]
|
|
669
|
+
duet upgrade [--manager npm|bun|pnpm|yarn]
|
|
670
|
+
echo "prompt" | duet
|
|
671
|
+
|
|
672
|
+
COMMANDS
|
|
673
|
+
skills List installed skills as JSON (name, description, path, scope)
|
|
674
|
+
upgrade Upgrade the global ${packageName} installation
|
|
675
|
+
|
|
676
|
+
OPTIONS
|
|
677
|
+
-m, --model <name> TurnRunner model override
|
|
678
|
+
--memory-model <name> Observational memory model (default inferred from provider env)
|
|
679
|
+
-w, --workdir <path> Working directory (default: cwd)
|
|
680
|
+
-r, --resume <id> Resume a saved session
|
|
681
|
+
--resume-history-lines <n>
|
|
682
|
+
Display up to n prior-session lines in the TUI (default: ${DEFAULT_RESUME_HISTORY_LINES})
|
|
683
|
+
--system-prompt <text> Additional system instructions for the runner
|
|
684
|
+
--system-prompt-file <path>
|
|
685
|
+
Load a file into the system prompt; repeatable
|
|
686
|
+
--no-system-prompt-files Disable default AGENTS.md system prompt loading
|
|
687
|
+
--json Print streamed events as JSON lines
|
|
688
|
+
-v, --version Print the installed duet version and exit
|
|
689
|
+
-h, --help Show this help
|
|
690
|
+
|
|
691
|
+
INTERACTIVE
|
|
692
|
+
In a TTY, duet keeps one local session open after terminal events.
|
|
693
|
+
Type /exit or /quit to end the conversation.
|
|
694
|
+
|
|
695
|
+
MODELS
|
|
696
|
+
Use provider:modelId syntax, e.g. anthropic:claude-opus-4-7.
|
|
697
|
+
If omitted, duet infers a default from ANTHROPIC_API_KEY,
|
|
698
|
+
DUET_API_KEY, AI_GATEWAY_API_KEY, OPENROUTER_API_KEY, or
|
|
699
|
+
OPENAI_API_KEY after loading <workdir>/.env.
|
|
700
|
+
|
|
701
|
+
duet-gateway: routes through the Duet gateway proxy
|
|
702
|
+
(https://duet.so/api/v1/ai-gateway by default; override via
|
|
703
|
+
DUET_GATEWAY_BASE_URL). It mirrors vercel-ai-gateway's model
|
|
704
|
+
catalog and authenticates with DUET_API_KEY.
|
|
705
|
+
|
|
706
|
+
EXAMPLES
|
|
707
|
+
duet "build a REST API with Express and TypeScript"
|
|
708
|
+
duet -m openai:gpt-5.5 "analyze the performance of our test suite"
|
|
709
|
+
duet --memory-model anthropic:claude-sonnet-4-6 "summarize this repo"
|
|
710
|
+
duet -m vercel-ai-gateway:anthropic/claude-opus-4.7 "refactor the auth module"
|
|
711
|
+
duet -m duet-gateway:anthropic/claude-opus-4.7 "review this repo"
|
|
712
|
+
duet --system-prompt "Prefer concise answers." "review this repo"
|
|
713
|
+
duet --system-prompt-file TEAM.md "review this repo"
|
|
714
|
+
duet --workdir ./my-project "refactor the auth module"
|
|
715
|
+
duet --resume session_abc123 --workdir ./my-project
|
|
716
|
+
duet upgrade
|
|
717
|
+
`);
|
|
718
|
+
}
|
|
719
|
+
function printSkillsHelp() {
|
|
720
|
+
console.log(`
|
|
721
|
+
duet skills — List installed skills as JSON
|
|
722
|
+
|
|
723
|
+
USAGE
|
|
724
|
+
duet skills [--workdir <path>]
|
|
725
|
+
|
|
726
|
+
OPTIONS
|
|
727
|
+
-w, --workdir <path> Working directory for project-local skills (default: cwd)
|
|
728
|
+
-h, --help Show this help
|
|
729
|
+
|
|
730
|
+
OUTPUT
|
|
731
|
+
Prints a JSON array of installed skills. Each entry has:
|
|
732
|
+
name Skill name
|
|
733
|
+
description Skill description (from frontmatter, raw — no shell expansion)
|
|
734
|
+
path Absolute path to the skill directory
|
|
735
|
+
scope "user", "project", or "temporary"
|
|
736
|
+
`);
|
|
737
|
+
}
|
|
738
|
+
function printUpgradeHelp(packageName) {
|
|
739
|
+
console.log(`
|
|
740
|
+
duet upgrade — Upgrade the global ${packageName} installation
|
|
741
|
+
|
|
742
|
+
USAGE
|
|
743
|
+
duet upgrade [--manager npm|bun|pnpm|yarn]
|
|
744
|
+
|
|
745
|
+
OPTIONS
|
|
746
|
+
--manager <name> Package manager to use (default: detected, fallback: npm)
|
|
747
|
+
--dry-run Print the upgrade command without running it
|
|
748
|
+
-h, --help Show this help
|
|
749
|
+
`);
|
|
750
|
+
}
|
|
751
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
|
752
|
+
main();
|
|
753
|
+
}
|
|
754
|
+
//# sourceMappingURL=cli.js.map
|