@otto-code/cli 0.5.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/bin/otto +3 -0
- package/dist/classify.d.ts +19 -0
- package/dist/classify.js +49 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +113 -0
- package/dist/commands/agent/archive.d.ts +18 -0
- package/dist/commands/agent/archive.js +102 -0
- package/dist/commands/agent/attach.d.ts +11 -0
- package/dist/commands/agent/attach.js +159 -0
- package/dist/commands/agent/delete.d.ts +15 -0
- package/dist/commands/agent/delete.js +107 -0
- package/dist/commands/agent/import.d.ts +14 -0
- package/dist/commands/agent/import.js +122 -0
- package/dist/commands/agent/index.d.ts +3 -0
- package/dist/commands/agent/index.js +48 -0
- package/dist/commands/agent/inspect.d.ts +15 -0
- package/dist/commands/agent/inspect.js +209 -0
- package/dist/commands/agent/logs.d.ts +19 -0
- package/dist/commands/agent/logs.js +180 -0
- package/dist/commands/agent/ls.d.ts +44 -0
- package/dist/commands/agent/ls.js +204 -0
- package/dist/commands/agent/mode.d.ts +18 -0
- package/dist/commands/agent/mode.js +100 -0
- package/dist/commands/agent/reload.d.ts +15 -0
- package/dist/commands/agent/reload.js +75 -0
- package/dist/commands/agent/run.d.ts +44 -0
- package/dist/commands/agent/run.js +464 -0
- package/dist/commands/agent/send.d.ts +19 -0
- package/dist/commands/agent/send.js +187 -0
- package/dist/commands/agent/stop.d.ts +17 -0
- package/dist/commands/agent/stop.js +115 -0
- package/dist/commands/agent/update.d.ts +18 -0
- package/dist/commands/agent/update.js +139 -0
- package/dist/commands/agent/wait.d.ts +17 -0
- package/dist/commands/agent/wait.js +158 -0
- package/dist/commands/chat/create.d.ts +9 -0
- package/dist/commands/chat/create.js +23 -0
- package/dist/commands/chat/delete.d.ts +6 -0
- package/dist/commands/chat/delete.js +20 -0
- package/dist/commands/chat/index.d.ts +3 -0
- package/dist/commands/chat/index.js +47 -0
- package/dist/commands/chat/inspect.d.ts +6 -0
- package/dist/commands/chat/inspect.js +20 -0
- package/dist/commands/chat/ls.d.ts +6 -0
- package/dist/commands/chat/ls.js +20 -0
- package/dist/commands/chat/post.d.ts +9 -0
- package/dist/commands/chat/post.js +28 -0
- package/dist/commands/chat/read.d.ts +11 -0
- package/dist/commands/chat/read.js +40 -0
- package/dist/commands/chat/schema.d.ts +36 -0
- package/dist/commands/chat/schema.js +81 -0
- package/dist/commands/chat/shared.d.ts +19 -0
- package/dist/commands/chat/shared.js +118 -0
- package/dist/commands/chat/wait.d.ts +9 -0
- package/dist/commands/chat/wait.js +45 -0
- package/dist/commands/daemon/index.d.ts +3 -0
- package/dist/commands/daemon/index.js +57 -0
- package/dist/commands/daemon/local-daemon.d.ts +78 -0
- package/dist/commands/daemon/local-daemon.js +544 -0
- package/dist/commands/daemon/pair.d.ts +9 -0
- package/dist/commands/daemon/pair.js +76 -0
- package/dist/commands/daemon/restart.d.ts +12 -0
- package/dist/commands/daemon/restart.js +99 -0
- package/dist/commands/daemon/runtime-toolchain.d.ts +6 -0
- package/dist/commands/daemon/runtime-toolchain.js +87 -0
- package/dist/commands/daemon/set-password.d.ts +17 -0
- package/dist/commands/daemon/set-password.js +82 -0
- package/dist/commands/daemon/start.d.ts +6 -0
- package/dist/commands/daemon/start.js +56 -0
- package/dist/commands/daemon/status.d.ts +10 -0
- package/dist/commands/daemon/status.js +338 -0
- package/dist/commands/daemon/stop.d.ts +15 -0
- package/dist/commands/daemon/stop.js +60 -0
- package/dist/commands/hooks.d.ts +19 -0
- package/dist/commands/hooks.js +89 -0
- package/dist/commands/loop/index.d.ts +3 -0
- package/dist/commands/loop/index.js +18 -0
- package/dist/commands/loop/inspect.d.ts +13 -0
- package/dist/commands/loop/inspect.js +100 -0
- package/dist/commands/loop/logs.d.ts +8 -0
- package/dist/commands/loop/logs.js +69 -0
- package/dist/commands/loop/ls.d.ts +18 -0
- package/dist/commands/loop/ls.js +62 -0
- package/dist/commands/loop/run.d.ts +30 -0
- package/dist/commands/loop/run.js +162 -0
- package/dist/commands/loop/stop.d.ts +15 -0
- package/dist/commands/loop/stop.js +56 -0
- package/dist/commands/loop/types.d.ts +138 -0
- package/dist/commands/loop/types.js +2 -0
- package/dist/commands/onboard.d.ts +10 -0
- package/dist/commands/onboard.js +416 -0
- package/dist/commands/open.d.ts +2 -0
- package/dist/commands/open.js +81 -0
- package/dist/commands/permit/allow.d.ts +20 -0
- package/dist/commands/permit/allow.js +133 -0
- package/dist/commands/permit/deny.d.ts +12 -0
- package/dist/commands/permit/deny.js +105 -0
- package/dist/commands/permit/index.d.ts +3 -0
- package/dist/commands/permit/index.js +27 -0
- package/dist/commands/permit/ls.d.ts +18 -0
- package/dist/commands/permit/ls.js +66 -0
- package/dist/commands/provider/index.d.ts +3 -0
- package/dist/commands/provider/index.js +16 -0
- package/dist/commands/provider/ls.d.ts +19 -0
- package/dist/commands/provider/ls.js +73 -0
- package/dist/commands/provider/models.d.ts +20 -0
- package/dist/commands/provider/models.js +53 -0
- package/dist/commands/schedule/create.d.ts +18 -0
- package/dist/commands/schedule/create.js +40 -0
- package/dist/commands/schedule/delete.d.ts +10 -0
- package/dist/commands/schedule/delete.js +32 -0
- package/dist/commands/schedule/index.d.ts +3 -0
- package/dist/commands/schedule/index.js +66 -0
- package/dist/commands/schedule/inspect.d.ts +6 -0
- package/dist/commands/schedule/inspect.js +24 -0
- package/dist/commands/schedule/logs.d.ts +6 -0
- package/dist/commands/schedule/logs.js +23 -0
- package/dist/commands/schedule/ls.d.ts +5 -0
- package/dist/commands/schedule/ls.js +23 -0
- package/dist/commands/schedule/pause.d.ts +5 -0
- package/dist/commands/schedule/pause.js +23 -0
- package/dist/commands/schedule/resume.d.ts +5 -0
- package/dist/commands/schedule/resume.js +23 -0
- package/dist/commands/schedule/run-once.d.ts +5 -0
- package/dist/commands/schedule/run-once.js +23 -0
- package/dist/commands/schedule/schema.d.ts +21 -0
- package/dist/commands/schedule/schema.js +67 -0
- package/dist/commands/schedule/shared.d.ts +56 -0
- package/dist/commands/schedule/shared.js +346 -0
- package/dist/commands/schedule/types.d.ts +168 -0
- package/dist/commands/schedule/types.js +2 -0
- package/dist/commands/schedule/update.d.ts +21 -0
- package/dist/commands/schedule/update.js +39 -0
- package/dist/commands/speech/index.d.ts +3 -0
- package/dist/commands/speech/index.js +5 -0
- package/dist/commands/terminal/capture.d.ts +10 -0
- package/dist/commands/terminal/capture.js +68 -0
- package/dist/commands/terminal/create.d.ts +10 -0
- package/dist/commands/terminal/create.js +38 -0
- package/dist/commands/terminal/index.d.ts +3 -0
- package/dist/commands/terminal/index.js +43 -0
- package/dist/commands/terminal/kill.d.ts +6 -0
- package/dist/commands/terminal/kill.js +36 -0
- package/dist/commands/terminal/ls.d.ts +10 -0
- package/dist/commands/terminal/ls.js +21 -0
- package/dist/commands/terminal/schema.d.ts +18 -0
- package/dist/commands/terminal/schema.js +23 -0
- package/dist/commands/terminal/send-keys.d.ts +7 -0
- package/dist/commands/terminal/send-keys.js +76 -0
- package/dist/commands/terminal/shared.d.ts +12 -0
- package/dist/commands/terminal/shared.js +61 -0
- package/dist/commands/worktree/archive.d.ts +20 -0
- package/dist/commands/worktree/archive.js +104 -0
- package/dist/commands/worktree/create-input.d.ts +15 -0
- package/dist/commands/worktree/create-input.js +56 -0
- package/dist/commands/worktree/create.d.ts +11 -0
- package/dist/commands/worktree/create.js +58 -0
- package/dist/commands/worktree/index.d.ts +3 -0
- package/dist/commands/worktree/index.js +25 -0
- package/dist/commands/worktree/ls.d.ts +19 -0
- package/dist/commands/worktree/ls.js +97 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/output/index.d.ts +45 -0
- package/dist/output/index.js +47 -0
- package/dist/output/json.d.ts +11 -0
- package/dist/output/json.js +37 -0
- package/dist/output/quiet.d.ts +9 -0
- package/dist/output/quiet.js +22 -0
- package/dist/output/render.d.ts +15 -0
- package/dist/output/render.js +83 -0
- package/dist/output/table.d.ts +13 -0
- package/dist/output/table.js +135 -0
- package/dist/output/types.d.ts +73 -0
- package/dist/output/types.js +8 -0
- package/dist/output/with-output.d.ts +41 -0
- package/dist/output/with-output.js +87 -0
- package/dist/output/yaml.d.ts +11 -0
- package/dist/output/yaml.js +38 -0
- package/dist/run.d.ts +14 -0
- package/dist/run.js +33 -0
- package/dist/utils/client-id.d.ts +2 -0
- package/dist/utils/client-id.js +37 -0
- package/dist/utils/client.d.ts +44 -0
- package/dist/utils/client.js +330 -0
- package/dist/utils/command-options.d.ts +6 -0
- package/dist/utils/command-options.js +17 -0
- package/dist/utils/duration.d.ts +7 -0
- package/dist/utils/duration.js +38 -0
- package/dist/utils/errors.d.ts +5 -0
- package/dist/utils/errors.js +10 -0
- package/dist/utils/paths.d.ts +12 -0
- package/dist/utils/paths.js +22 -0
- package/dist/utils/provider-model.d.ts +11 -0
- package/dist/utils/provider-model.js +49 -0
- package/dist/utils/timeline.d.ts +11 -0
- package/dist/utils/timeline.js +11 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +10 -0
- package/package.json +48 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { Option } from "commander";
|
|
2
|
+
import { getStructuredAgentResponse, StructuredAgentResponseError } from "@otto-code/server";
|
|
3
|
+
import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
import { lookup } from "mime-types";
|
|
7
|
+
import { parseDuration } from "../../utils/duration.js";
|
|
8
|
+
import { collectMultiple } from "../../utils/command-options.js";
|
|
9
|
+
import { resolveProviderAndModel } from "../../utils/provider-model.js";
|
|
10
|
+
export { resolveProviderAndModel } from "../../utils/provider-model.js";
|
|
11
|
+
export function addRunOptions(cmd) {
|
|
12
|
+
return cmd
|
|
13
|
+
.description("Create and start an agent with a task")
|
|
14
|
+
.argument("<prompt>", "The task/prompt for the agent")
|
|
15
|
+
.option("-d, --detach", "Run in background (detached)")
|
|
16
|
+
.option("--title <title>", "Assign a title to the agent")
|
|
17
|
+
.addOption(new Option("--name <name>", "Hidden alias for --title").hideHelp())
|
|
18
|
+
.option("--provider <provider>", "Agent provider, or provider/model (e.g. codex or codex/gpt-5.4)")
|
|
19
|
+
.option("--model <model>", "Model to use (e.g., claude-sonnet-4-20250514, claude-3-5-haiku-20241022)")
|
|
20
|
+
.option("--thinking <id>", "Thinking option ID to use for this run")
|
|
21
|
+
.option("--mode <mode>", "Provider-specific mode (e.g., plan, default, bypass)")
|
|
22
|
+
.option("--worktree <name>", "Create agent in a new git worktree")
|
|
23
|
+
.option("--base <branch>", "Base branch for worktree (default: current branch)")
|
|
24
|
+
.option("--workspace <id>", "Run in an existing workspace (default: a new workspace is created per run; falls back to $OTTO_WORKSPACE_ID)")
|
|
25
|
+
.option("--image <path>", "Attach image(s) to the initial prompt (can be used multiple times)", collectMultiple, [])
|
|
26
|
+
.option("--cwd <path>", "Working directory (default: current)")
|
|
27
|
+
.option("--env <key=value>", "Set environment variable(s) for the agent process (can be used multiple times)", collectMultiple, [])
|
|
28
|
+
.option("--label <key=value>", "Add label(s) to the agent (can be used multiple times)", collectMultiple, [])
|
|
29
|
+
.option("--wait-timeout <duration>", "Maximum time to wait for agent to finish (e.g., 30s, 5m, 1h). Default: no limit")
|
|
30
|
+
.option("--output-schema <schema>", "Output JSON matching the provided schema file path or inline JSON schema");
|
|
31
|
+
}
|
|
32
|
+
/** Schema for agent run output */
|
|
33
|
+
export const agentRunSchema = {
|
|
34
|
+
idField: "agentId",
|
|
35
|
+
columns: [
|
|
36
|
+
{ header: "AGENT ID", field: "agentId", width: 12 },
|
|
37
|
+
{ header: "STATUS", field: "status", width: 10 },
|
|
38
|
+
{ header: "PROVIDER", field: "provider", width: 10 },
|
|
39
|
+
{ header: "CWD", field: "cwd", width: 30 },
|
|
40
|
+
{ header: "TITLE", field: "title", width: 20 },
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
function toRunResult(agent, statusOverride) {
|
|
44
|
+
return {
|
|
45
|
+
agentId: agent.id,
|
|
46
|
+
status: statusOverride ?? (agent.status === "running" ? "running" : "created"),
|
|
47
|
+
provider: agent.provider,
|
|
48
|
+
cwd: agent.cwd,
|
|
49
|
+
title: agent.title,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function loadOutputSchema(value) {
|
|
53
|
+
const trimmed = value.trim();
|
|
54
|
+
if (!trimmed) {
|
|
55
|
+
const error = {
|
|
56
|
+
code: "INVALID_OUTPUT_SCHEMA",
|
|
57
|
+
message: "--output-schema cannot be empty",
|
|
58
|
+
details: "Provide a JSON schema file path or inline JSON object",
|
|
59
|
+
};
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
let source = trimmed;
|
|
63
|
+
if (!trimmed.startsWith("{")) {
|
|
64
|
+
try {
|
|
65
|
+
source = readFileSync(resolve(trimmed), "utf8");
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
69
|
+
const error = {
|
|
70
|
+
code: "INVALID_OUTPUT_SCHEMA",
|
|
71
|
+
message: `Failed to read output schema file: ${trimmed}`,
|
|
72
|
+
details: message,
|
|
73
|
+
};
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
let parsed;
|
|
78
|
+
try {
|
|
79
|
+
parsed = JSON.parse(source);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
83
|
+
const error = {
|
|
84
|
+
code: "INVALID_OUTPUT_SCHEMA",
|
|
85
|
+
message: "Failed to parse output schema JSON",
|
|
86
|
+
details: message,
|
|
87
|
+
};
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
91
|
+
const error = {
|
|
92
|
+
code: "INVALID_OUTPUT_SCHEMA",
|
|
93
|
+
message: "Output schema must be a JSON object",
|
|
94
|
+
};
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
return parsed;
|
|
98
|
+
}
|
|
99
|
+
class StructuredRunStatusError extends Error {
|
|
100
|
+
constructor(kind, message) {
|
|
101
|
+
super(message);
|
|
102
|
+
this.name = "StructuredRunStatusError";
|
|
103
|
+
this.kind = kind;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function fetchStructuredOutput(caller, prompt, outputSchema) {
|
|
107
|
+
try {
|
|
108
|
+
return await getStructuredAgentResponse({
|
|
109
|
+
caller,
|
|
110
|
+
prompt,
|
|
111
|
+
schema: outputSchema,
|
|
112
|
+
schemaName: "RunOutput",
|
|
113
|
+
maxRetries: 2,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
if (err instanceof StructuredRunStatusError) {
|
|
118
|
+
throw {
|
|
119
|
+
code: "OUTPUT_SCHEMA_FAILED",
|
|
120
|
+
message: err.message,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (err instanceof StructuredAgentResponseError) {
|
|
124
|
+
throw {
|
|
125
|
+
code: "OUTPUT_SCHEMA_FAILED",
|
|
126
|
+
message: "Agent response did not match the required output schema",
|
|
127
|
+
details: err.validationErrors.length > 0
|
|
128
|
+
? err.validationErrors.join("\n")
|
|
129
|
+
: err.lastResponse || "No response",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export async function resolveStructuredResponseMessage(options) {
|
|
136
|
+
const direct = options.lastMessage?.trim();
|
|
137
|
+
if (direct) {
|
|
138
|
+
return direct;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const timeline = await options.client.fetchAgentTimeline(options.agentId, {
|
|
142
|
+
direction: "tail",
|
|
143
|
+
limit: 200,
|
|
144
|
+
});
|
|
145
|
+
for (let index = timeline.entries.length - 1; index >= 0; index -= 1) {
|
|
146
|
+
const entry = timeline.entries[index];
|
|
147
|
+
if (!entry || entry.item.type !== "assistant_message") {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const text = entry.item.text.trim();
|
|
151
|
+
if (text.length > 0) {
|
|
152
|
+
return text;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Leave empty; caller will surface a consistent structured-output failure message.
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
function structuredRunSchema(output) {
|
|
162
|
+
return {
|
|
163
|
+
...agentRunSchema,
|
|
164
|
+
serialize: () => output,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function validateRunOptions(prompt, options, outputSchema) {
|
|
168
|
+
if (!prompt || prompt.trim().length === 0) {
|
|
169
|
+
throw {
|
|
170
|
+
code: "MISSING_PROMPT",
|
|
171
|
+
message: "A prompt is required",
|
|
172
|
+
details: "Usage: otto agent run [options] <prompt>",
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (options.base && !options.worktree) {
|
|
176
|
+
throw {
|
|
177
|
+
code: "INVALID_OPTIONS",
|
|
178
|
+
message: "--base can only be used with --worktree",
|
|
179
|
+
details: "Usage: otto agent run --worktree <name> --base <branch> <prompt>",
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (options.worktree && options.workspace) {
|
|
183
|
+
throw {
|
|
184
|
+
code: "INVALID_OPTIONS",
|
|
185
|
+
message: "--worktree and --workspace cannot be combined",
|
|
186
|
+
details: "--workspace runs in an existing workspace; --worktree mints a new one",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (options.worktree && !options.workspace && process.env.OTTO_WORKSPACE_ID) {
|
|
190
|
+
throw {
|
|
191
|
+
code: "INVALID_OPTIONS",
|
|
192
|
+
message: "--worktree cannot be combined with an ambient OTTO_WORKSPACE_ID",
|
|
193
|
+
details: "OTTO_WORKSPACE_ID selects an existing workspace; --worktree mints a new one. Unset OTTO_WORKSPACE_ID to use --worktree.",
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (outputSchema && options.detach) {
|
|
197
|
+
throw {
|
|
198
|
+
code: "INVALID_OPTIONS",
|
|
199
|
+
message: "--output-schema cannot be used with --detach",
|
|
200
|
+
details: "Structured output requires waiting for the agent to finish",
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function parseWaitTimeoutOption(waitTimeout) {
|
|
205
|
+
if (!waitTimeout)
|
|
206
|
+
return 0;
|
|
207
|
+
try {
|
|
208
|
+
const ms = parseDuration(waitTimeout);
|
|
209
|
+
if (ms <= 0) {
|
|
210
|
+
throw new Error("Timeout must be positive");
|
|
211
|
+
}
|
|
212
|
+
return ms;
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
216
|
+
throw {
|
|
217
|
+
code: "INVALID_TIMEOUT",
|
|
218
|
+
message: "Invalid wait timeout value",
|
|
219
|
+
details: message,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function loadRunImages(imagePaths) {
|
|
224
|
+
if (!imagePaths || imagePaths.length === 0)
|
|
225
|
+
return undefined;
|
|
226
|
+
return imagePaths.map((imagePath) => {
|
|
227
|
+
const resolvedPath = resolve(imagePath);
|
|
228
|
+
try {
|
|
229
|
+
const imageData = readFileSync(resolvedPath);
|
|
230
|
+
const mimeType = lookup(resolvedPath) || "application/octet-stream";
|
|
231
|
+
if (!mimeType.startsWith("image/")) {
|
|
232
|
+
throw new Error(`File is not an image: ${imagePath} (detected type: ${mimeType})`);
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
data: imageData.toString("base64"),
|
|
236
|
+
mimeType,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
241
|
+
throw new Error(`Failed to read image ${imagePath}: ${message}`, { cause: err });
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
function parseRunLabels(labelFlags) {
|
|
246
|
+
return parseKeyValueFlags(labelFlags, {
|
|
247
|
+
flagName: "--label",
|
|
248
|
+
code: "INVALID_LABEL",
|
|
249
|
+
noun: "label",
|
|
250
|
+
pluralNoun: "Labels",
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function parseRunEnv(envFlags) {
|
|
254
|
+
return parseKeyValueFlags(envFlags, {
|
|
255
|
+
flagName: "--env",
|
|
256
|
+
code: "INVALID_ENV",
|
|
257
|
+
noun: "environment variable",
|
|
258
|
+
pluralNoun: "Environment variables",
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
function parseKeyValueFlags(flags, options) {
|
|
262
|
+
const labels = {};
|
|
263
|
+
if (!flags)
|
|
264
|
+
return labels;
|
|
265
|
+
for (const labelStr of flags) {
|
|
266
|
+
const eqIndex = labelStr.indexOf("=");
|
|
267
|
+
if (eqIndex === -1) {
|
|
268
|
+
throw {
|
|
269
|
+
code: options.code,
|
|
270
|
+
message: `Invalid ${options.noun} format: ${labelStr}`,
|
|
271
|
+
details: `${options.pluralNoun} must be in key=value format`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const key = labelStr.slice(0, eqIndex);
|
|
275
|
+
labels[key] = labelStr.slice(eqIndex + 1);
|
|
276
|
+
}
|
|
277
|
+
return labels;
|
|
278
|
+
}
|
|
279
|
+
async function connectToDaemonOrThrow(hostOption, host) {
|
|
280
|
+
try {
|
|
281
|
+
return await connectToDaemon({ host: hostOption });
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
285
|
+
throw {
|
|
286
|
+
code: "DAEMON_NOT_RUNNING",
|
|
287
|
+
message: `Cannot connect to daemon at ${host}: ${message}`,
|
|
288
|
+
details: "Start the daemon with: otto daemon start",
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Workspace policy for `otto run`. Precedence:
|
|
293
|
+
// 1. --workspace <id> -> run in that existing workspace
|
|
294
|
+
// 2. $OTTO_WORKSPACE_ID -> exported by workspace terminals
|
|
295
|
+
// 3. --worktree <name> -> mint a new worktree-backed workspace
|
|
296
|
+
// 4. bare run -> mint a new local-backed workspace for cwd
|
|
297
|
+
// --worktree is rejected alongside both --workspace and an ambient
|
|
298
|
+
// $OTTO_WORKSPACE_ID (validateRunOptions), so worktree resolution here never
|
|
299
|
+
// races an existing-workspace selection.
|
|
300
|
+
async function resolveRunWorkspace(client, options, cwd) {
|
|
301
|
+
// An explicit --worktree mints its own workspace; --workspace and an ambient
|
|
302
|
+
// OTTO_WORKSPACE_ID are both rejected alongside --worktree upstream.
|
|
303
|
+
const explicit = options.worktree
|
|
304
|
+
? undefined
|
|
305
|
+
: options.workspace?.trim() || process.env.OTTO_WORKSPACE_ID?.trim();
|
|
306
|
+
if (explicit) {
|
|
307
|
+
console.error(`Using workspace ${explicit}`);
|
|
308
|
+
return { id: explicit, cwd };
|
|
309
|
+
}
|
|
310
|
+
// TODO: thread the run `prompt` as firstAgentContext so workspace-level
|
|
311
|
+
// title/branch generation picks up the task description (U8/U6 deferred).
|
|
312
|
+
const result = options.worktree
|
|
313
|
+
? await client.createWorkspace({
|
|
314
|
+
source: {
|
|
315
|
+
kind: "worktree",
|
|
316
|
+
cwd,
|
|
317
|
+
worktreeSlug: options.worktree,
|
|
318
|
+
baseBranch: options.base,
|
|
319
|
+
},
|
|
320
|
+
})
|
|
321
|
+
: await client.createWorkspace({ source: { kind: "directory", path: cwd } });
|
|
322
|
+
if (!result.workspace) {
|
|
323
|
+
throw {
|
|
324
|
+
code: "WORKSPACE_CREATE_FAILED",
|
|
325
|
+
message: result.error ?? "Failed to create workspace for this run",
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
const branch = result.workspace.gitRuntime?.currentBranch;
|
|
329
|
+
const label = branch ? `${result.workspace.name} (${branch})` : result.workspace.name;
|
|
330
|
+
console.error(`Created workspace ${result.workspace.id} - ${label}`);
|
|
331
|
+
console.error("Tip: pass --workspace <id> (or set OTTO_WORKSPACE_ID) to run in an existing workspace.");
|
|
332
|
+
return { id: result.workspace.id, cwd: result.workspace.workspaceDirectory ?? cwd };
|
|
333
|
+
}
|
|
334
|
+
export async function runRunCommand(prompt, options, _command) {
|
|
335
|
+
const host = getDaemonHost({ host: options.host });
|
|
336
|
+
const outputSchema = options.outputSchema ? loadOutputSchema(options.outputSchema) : undefined;
|
|
337
|
+
validateRunOptions(prompt, options, outputSchema);
|
|
338
|
+
const waitTimeoutMs = parseWaitTimeoutOption(options.waitTimeout);
|
|
339
|
+
const resolvedProviderModel = resolveProviderAndModel(options);
|
|
340
|
+
const resolvedTitle = options.title ?? options.name;
|
|
341
|
+
const client = await connectToDaemonOrThrow(options.host, host);
|
|
342
|
+
try {
|
|
343
|
+
// Resolve working directory
|
|
344
|
+
const cwd = options.cwd ?? process.cwd();
|
|
345
|
+
const thinkingOptionId = options.thinking?.trim();
|
|
346
|
+
if (options.thinking !== undefined && !thinkingOptionId) {
|
|
347
|
+
const error = {
|
|
348
|
+
code: "INVALID_THINKING_OPTION",
|
|
349
|
+
message: "--thinking cannot be empty",
|
|
350
|
+
details: 'Provide a thinking option ID. Use "otto provider models <provider> --thinking" to list valid IDs.',
|
|
351
|
+
};
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
const images = loadRunImages(options.image);
|
|
355
|
+
const labels = parseRunLabels(options.label);
|
|
356
|
+
const env = parseRunEnv(options.env);
|
|
357
|
+
const requestEnv = Object.keys(env).length > 0 ? env : undefined;
|
|
358
|
+
const workspace = await resolveRunWorkspace(client, options, cwd);
|
|
359
|
+
const workspaceId = workspace.id;
|
|
360
|
+
const runCwd = workspace.cwd;
|
|
361
|
+
if (outputSchema) {
|
|
362
|
+
let structuredAgent = null;
|
|
363
|
+
const callStructuredTurn = async (structuredPrompt) => {
|
|
364
|
+
if (!structuredAgent) {
|
|
365
|
+
structuredAgent = await client.createAgent({
|
|
366
|
+
provider: resolvedProviderModel.provider,
|
|
367
|
+
cwd: runCwd,
|
|
368
|
+
workspaceId,
|
|
369
|
+
title: resolvedTitle,
|
|
370
|
+
modeId: options.mode,
|
|
371
|
+
model: resolvedProviderModel.model,
|
|
372
|
+
thinkingOptionId,
|
|
373
|
+
initialPrompt: structuredPrompt,
|
|
374
|
+
outputSchema,
|
|
375
|
+
images,
|
|
376
|
+
env: requestEnv,
|
|
377
|
+
labels: Object.keys(labels).length > 0 ? labels : undefined,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
await client.sendMessage(structuredAgent.id, structuredPrompt);
|
|
382
|
+
}
|
|
383
|
+
const state = await client.waitForFinish(structuredAgent.id, waitTimeoutMs);
|
|
384
|
+
if (state.status === "timeout") {
|
|
385
|
+
throw new StructuredRunStatusError("timeout", "Timed out waiting for structured output");
|
|
386
|
+
}
|
|
387
|
+
if (state.status === "permission") {
|
|
388
|
+
throw new StructuredRunStatusError("permission", "Agent is waiting for permission before producing structured output");
|
|
389
|
+
}
|
|
390
|
+
if (state.status === "error") {
|
|
391
|
+
throw new StructuredRunStatusError("error", state.error ?? "Agent failed before producing structured output");
|
|
392
|
+
}
|
|
393
|
+
const lastMessage = await resolveStructuredResponseMessage({
|
|
394
|
+
client,
|
|
395
|
+
agentId: structuredAgent.id,
|
|
396
|
+
lastMessage: state.lastMessage,
|
|
397
|
+
});
|
|
398
|
+
if (!lastMessage) {
|
|
399
|
+
throw new StructuredRunStatusError("empty", "Agent finished without a structured output message");
|
|
400
|
+
}
|
|
401
|
+
return lastMessage;
|
|
402
|
+
};
|
|
403
|
+
const output = await fetchStructuredOutput(callStructuredTurn, prompt, outputSchema);
|
|
404
|
+
if (!structuredAgent) {
|
|
405
|
+
const error = {
|
|
406
|
+
code: "OUTPUT_SCHEMA_FAILED",
|
|
407
|
+
message: "Agent finished without a structured output message",
|
|
408
|
+
};
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
411
|
+
await client.close();
|
|
412
|
+
return {
|
|
413
|
+
type: "single",
|
|
414
|
+
data: toRunResult(structuredAgent, "completed"),
|
|
415
|
+
schema: structuredRunSchema(output),
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
// Create the agent
|
|
419
|
+
const agent = await client.createAgent({
|
|
420
|
+
provider: resolvedProviderModel.provider,
|
|
421
|
+
cwd: runCwd,
|
|
422
|
+
workspaceId,
|
|
423
|
+
title: resolvedTitle,
|
|
424
|
+
modeId: options.mode,
|
|
425
|
+
model: resolvedProviderModel.model,
|
|
426
|
+
thinkingOptionId,
|
|
427
|
+
initialPrompt: prompt,
|
|
428
|
+
images,
|
|
429
|
+
env: requestEnv,
|
|
430
|
+
labels: Object.keys(labels).length > 0 ? labels : undefined,
|
|
431
|
+
});
|
|
432
|
+
// Default run behavior is foreground: wait for completion unless --detach is set.
|
|
433
|
+
if (!options.detach) {
|
|
434
|
+
const state = await client.waitForFinish(agent.id, waitTimeoutMs);
|
|
435
|
+
await client.close();
|
|
436
|
+
const finalAgent = state.final ?? agent;
|
|
437
|
+
const status = state.status === "idle" ? "completed" : state.status;
|
|
438
|
+
return {
|
|
439
|
+
type: "single",
|
|
440
|
+
data: toRunResult(finalAgent, status),
|
|
441
|
+
schema: agentRunSchema,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
await client.close();
|
|
445
|
+
return {
|
|
446
|
+
type: "single",
|
|
447
|
+
data: toRunResult(agent),
|
|
448
|
+
schema: agentRunSchema,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
catch (err) {
|
|
452
|
+
await client.close().catch(() => { });
|
|
453
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
454
|
+
throw err;
|
|
455
|
+
}
|
|
456
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
457
|
+
const error = {
|
|
458
|
+
code: "AGENT_CREATE_FAILED",
|
|
459
|
+
message: `Failed to create agent: ${message}`,
|
|
460
|
+
};
|
|
461
|
+
throw error;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Command } from "commander";
|
|
2
|
+
import type { CommandOptions, SingleResult, OutputSchema } from "../../output/index.js";
|
|
3
|
+
/** Result type for agent send command */
|
|
4
|
+
export interface AgentSendResult {
|
|
5
|
+
agentId: string;
|
|
6
|
+
status: "sent" | "completed" | "timeout" | "permission" | "error";
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
/** Schema for agent send output */
|
|
10
|
+
export declare const agentSendSchema: OutputSchema<AgentSendResult>;
|
|
11
|
+
export interface AgentSendOptions extends CommandOptions {
|
|
12
|
+
wait?: boolean;
|
|
13
|
+
image?: string[];
|
|
14
|
+
prompt?: string;
|
|
15
|
+
promptFile?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function addSendOptions(cmd: Command): Command;
|
|
18
|
+
export declare function runSendCommand(agentIdArg: string, prompt: string | undefined, options: AgentSendOptions, _command: Command): Promise<SingleResult<AgentSendResult>>;
|
|
19
|
+
//# sourceMappingURL=send.d.ts.map
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { collectMultiple } from "../../utils/command-options.js";
|
|
2
|
+
import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { extname, resolve } from "node:path";
|
|
5
|
+
/** Schema for agent send output */
|
|
6
|
+
export const agentSendSchema = {
|
|
7
|
+
idField: "agentId",
|
|
8
|
+
columns: [
|
|
9
|
+
{ header: "AGENT ID", field: "agentId", width: 12 },
|
|
10
|
+
{ header: "STATUS", field: "status", width: 12 },
|
|
11
|
+
{ header: "MESSAGE", field: "message", width: 40 },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
14
|
+
export function addSendOptions(cmd) {
|
|
15
|
+
return cmd
|
|
16
|
+
.description("Send a message/task to an existing agent")
|
|
17
|
+
.argument("<id>", "Agent ID (or prefix)")
|
|
18
|
+
.argument("[prompt]", "The message to send")
|
|
19
|
+
.option("--prompt <text>", "Provide the message inline as a flag")
|
|
20
|
+
.option("--prompt-file <path>", "Read the message from a UTF-8 text file")
|
|
21
|
+
.option("--image <path>", "Attach image(s) to the message", collectMultiple, [])
|
|
22
|
+
.option("--no-wait", "Return immediately without waiting for completion");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Read image files and convert them to base64 data URIs
|
|
26
|
+
*/
|
|
27
|
+
async function readImageFiles(imagePaths) {
|
|
28
|
+
return Promise.all(imagePaths.map(async (path) => {
|
|
29
|
+
try {
|
|
30
|
+
const buffer = await readFile(path);
|
|
31
|
+
const ext = extname(path).toLowerCase();
|
|
32
|
+
let mimeType = "image/jpeg";
|
|
33
|
+
switch (ext) {
|
|
34
|
+
case ".png":
|
|
35
|
+
mimeType = "image/png";
|
|
36
|
+
break;
|
|
37
|
+
case ".jpg":
|
|
38
|
+
case ".jpeg":
|
|
39
|
+
mimeType = "image/jpeg";
|
|
40
|
+
break;
|
|
41
|
+
case ".gif":
|
|
42
|
+
mimeType = "image/gif";
|
|
43
|
+
break;
|
|
44
|
+
case ".webp":
|
|
45
|
+
mimeType = "image/webp";
|
|
46
|
+
break;
|
|
47
|
+
default:
|
|
48
|
+
mimeType = "image/jpeg";
|
|
49
|
+
}
|
|
50
|
+
return { data: buffer.toString("base64"), mimeType };
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
54
|
+
throw {
|
|
55
|
+
code: "IMAGE_READ_ERROR",
|
|
56
|
+
message: `Failed to read image file: ${path}`,
|
|
57
|
+
details: message,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
async function resolvePromptInput(options) {
|
|
63
|
+
const promptText = options.promptArgument?.trim();
|
|
64
|
+
const promptOptionText = options.promptOption?.trim();
|
|
65
|
+
const promptFilePath = options.promptFile?.trim();
|
|
66
|
+
const providedSourceCount = [promptText, promptOptionText, promptFilePath].filter(Boolean).length;
|
|
67
|
+
if (providedSourceCount > 1) {
|
|
68
|
+
const error = {
|
|
69
|
+
code: "CONFLICTING_PROMPT_INPUT",
|
|
70
|
+
message: "Provide exactly one of prompt argument, --prompt, or --prompt-file",
|
|
71
|
+
};
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
if (promptText) {
|
|
75
|
+
return options.promptArgument;
|
|
76
|
+
}
|
|
77
|
+
if (promptOptionText) {
|
|
78
|
+
return options.promptOption;
|
|
79
|
+
}
|
|
80
|
+
if (!promptFilePath) {
|
|
81
|
+
const error = {
|
|
82
|
+
code: "MISSING_PROMPT",
|
|
83
|
+
message: "A prompt is required",
|
|
84
|
+
details: "Usage: otto agent send [options] <id> [prompt] | --prompt <text> | --prompt-file <path>",
|
|
85
|
+
};
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
return await readFile(resolve(promptFilePath), "utf8");
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
93
|
+
const error = {
|
|
94
|
+
code: "PROMPT_FILE_READ_ERROR",
|
|
95
|
+
message: `Failed to read prompt file: ${promptFilePath}`,
|
|
96
|
+
details: message,
|
|
97
|
+
};
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function buildSendResult(agentIdArg, state) {
|
|
102
|
+
const agentId = state.final?.id ?? agentIdArg;
|
|
103
|
+
if (state.status === "timeout") {
|
|
104
|
+
return { agentId, status: "timeout", message: "Timed out waiting for agent to finish" };
|
|
105
|
+
}
|
|
106
|
+
if (state.status === "permission") {
|
|
107
|
+
return { agentId, status: "permission", message: "Agent is waiting for permission" };
|
|
108
|
+
}
|
|
109
|
+
if (state.status === "error") {
|
|
110
|
+
return {
|
|
111
|
+
agentId,
|
|
112
|
+
status: "error",
|
|
113
|
+
message: state.error ?? "Agent finished with error",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return { agentId, status: "completed", message: "Agent completed processing the message" };
|
|
117
|
+
}
|
|
118
|
+
export async function runSendCommand(agentIdArg, prompt, options, _command) {
|
|
119
|
+
const host = getDaemonHost({ host: options.host });
|
|
120
|
+
// Validate arguments
|
|
121
|
+
if (!agentIdArg || agentIdArg.trim().length === 0) {
|
|
122
|
+
const error = {
|
|
123
|
+
code: "MISSING_AGENT_ID",
|
|
124
|
+
message: "Agent ID is required",
|
|
125
|
+
details: "Usage: otto agent send [options] <id> [prompt]",
|
|
126
|
+
};
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
const promptInput = await resolvePromptInput({
|
|
130
|
+
promptArgument: prompt,
|
|
131
|
+
promptOption: options.prompt,
|
|
132
|
+
promptFile: options.promptFile,
|
|
133
|
+
});
|
|
134
|
+
let client;
|
|
135
|
+
try {
|
|
136
|
+
client = await connectToDaemon({ host: options.host });
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
140
|
+
const error = {
|
|
141
|
+
code: "DAEMON_NOT_RUNNING",
|
|
142
|
+
message: `Cannot connect to daemon at ${host}: ${message}`,
|
|
143
|
+
details: "Start the daemon with: otto daemon start",
|
|
144
|
+
};
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
// Read image files if provided
|
|
149
|
+
const images = options.image && options.image.length > 0 ? await readImageFiles(options.image) : undefined;
|
|
150
|
+
// Send the message
|
|
151
|
+
await client.sendAgentMessage(agentIdArg, promptInput, { images });
|
|
152
|
+
// If --no-wait, return immediately
|
|
153
|
+
if (options.wait === false) {
|
|
154
|
+
await client.close();
|
|
155
|
+
return {
|
|
156
|
+
type: "single",
|
|
157
|
+
data: {
|
|
158
|
+
agentId: agentIdArg,
|
|
159
|
+
status: "sent",
|
|
160
|
+
message: "Message sent, not waiting for completion",
|
|
161
|
+
},
|
|
162
|
+
schema: agentSendSchema,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const state = await client.waitForFinish(agentIdArg, 600000); // 10 minute timeout
|
|
166
|
+
await client.close();
|
|
167
|
+
return {
|
|
168
|
+
type: "single",
|
|
169
|
+
data: buildSendResult(agentIdArg, state),
|
|
170
|
+
schema: agentSendSchema,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
await client.close().catch(() => { });
|
|
175
|
+
// Re-throw CommandError as-is
|
|
176
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
180
|
+
const error = {
|
|
181
|
+
code: "SEND_FAILED",
|
|
182
|
+
message: `Failed to send message: ${message}`,
|
|
183
|
+
};
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=send.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import type { CommandOptions, SingleResult, OutputSchema } from "../../output/index.js";
|
|
3
|
+
/** Result type for agent stop command */
|
|
4
|
+
export interface StopResult {
|
|
5
|
+
stoppedCount: number;
|
|
6
|
+
agentIds: string[];
|
|
7
|
+
}
|
|
8
|
+
/** Schema for stop command output */
|
|
9
|
+
export declare const stopSchema: OutputSchema<StopResult>;
|
|
10
|
+
export declare function addStopOptions(cmd: Command): Command;
|
|
11
|
+
export interface AgentStopOptions extends CommandOptions {
|
|
12
|
+
all?: boolean;
|
|
13
|
+
cwd?: string;
|
|
14
|
+
}
|
|
15
|
+
export type AgentStopResult = SingleResult<StopResult>;
|
|
16
|
+
export declare function runStopCommand(id: string | undefined, options: AgentStopOptions, _command: Command): Promise<AgentStopResult>;
|
|
17
|
+
//# sourceMappingURL=stop.d.ts.map
|