@otto-code/cli 0.6.7 → 0.7.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/dist/commands/agent/delete.d.ts +20 -0
- package/dist/commands/agent/delete.js +42 -8
- package/dist/commands/agent/run.js +5 -1
- package/dist/commands/agent/send.d.ts +1 -0
- package/dist/commands/agent/send.js +20 -2
- package/dist/utils/provider-model.d.ts +13 -0
- package/dist/utils/provider-model.js +33 -0
- package/package.json +4 -4
|
@@ -9,7 +9,27 @@ export declare const deleteSchema: OutputSchema<DeleteResult>;
|
|
|
9
9
|
export interface AgentDeleteOptions extends CommandOptions {
|
|
10
10
|
all?: boolean;
|
|
11
11
|
cwd?: string;
|
|
12
|
+
archived?: boolean;
|
|
13
|
+
includeArchived?: boolean;
|
|
12
14
|
}
|
|
13
15
|
export type AgentDeleteResult = SingleResult<DeleteResult>;
|
|
16
|
+
/** Which side of the archive line a bulk delete is allowed to touch. */
|
|
17
|
+
export type AgentDeleteScope = "active" | "archived" | "both";
|
|
18
|
+
/**
|
|
19
|
+
* Bulk delete used to filter `!a.archivedAt` unconditionally, so the one command
|
|
20
|
+
* that could clear the archive deliberately skipped exactly the rows a user most
|
|
21
|
+
* wants gone. The flags open it up without moving anyone's muscle memory: bare
|
|
22
|
+
* `--all` / `--cwd` still mean active-only.
|
|
23
|
+
*
|
|
24
|
+
* Both flags at once is contradictory ("only archived" and "also archived"), so
|
|
25
|
+
* it is refused rather than guessed — this command is irreversible.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveAgentDeleteScope(options: {
|
|
28
|
+
archived?: boolean;
|
|
29
|
+
includeArchived?: boolean;
|
|
30
|
+
}): AgentDeleteScope;
|
|
31
|
+
export declare function matchesAgentDeleteScope(agent: {
|
|
32
|
+
archivedAt?: Date | string | null;
|
|
33
|
+
}, scope: AgentDeleteScope): boolean;
|
|
14
34
|
export declare function runDeleteCommand(id: string | undefined, options: AgentDeleteOptions, _command: Command): Promise<AgentDeleteResult>;
|
|
15
35
|
//# sourceMappingURL=delete.d.ts.map
|
|
@@ -2,15 +2,50 @@ import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
|
|
|
2
2
|
import { isSameOrDescendantPath } from "../../utils/paths.js";
|
|
3
3
|
export function addDeleteOptions(cmd) {
|
|
4
4
|
return cmd
|
|
5
|
-
.description("Delete an agent (interrupt if running, then hard-delete)")
|
|
5
|
+
.description("Delete an agent (interrupt if running, then hard-delete). Removes Otto's record only - the agent provider's own transcript is left on disk.")
|
|
6
6
|
.argument("[id]", "Agent ID (or prefix) - optional if --all or --cwd specified")
|
|
7
7
|
.option("--all", "Delete all agents")
|
|
8
|
-
.option("--cwd <path>", "Delete all agents in directory")
|
|
8
|
+
.option("--cwd <path>", "Delete all agents in directory")
|
|
9
|
+
.option("--archived", "Only archived agents (use with --all or --cwd)")
|
|
10
|
+
.option("--include-archived", "Include archived agents (use with --all or --cwd)");
|
|
9
11
|
}
|
|
10
12
|
export const deleteSchema = {
|
|
11
13
|
idField: (item) => item.agentIds.join("\n"),
|
|
12
14
|
columns: [{ header: "DELETED", field: "deletedCount" }],
|
|
13
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Bulk delete used to filter `!a.archivedAt` unconditionally, so the one command
|
|
18
|
+
* that could clear the archive deliberately skipped exactly the rows a user most
|
|
19
|
+
* wants gone. The flags open it up without moving anyone's muscle memory: bare
|
|
20
|
+
* `--all` / `--cwd` still mean active-only.
|
|
21
|
+
*
|
|
22
|
+
* Both flags at once is contradictory ("only archived" and "also archived"), so
|
|
23
|
+
* it is refused rather than guessed — this command is irreversible.
|
|
24
|
+
*/
|
|
25
|
+
export function resolveAgentDeleteScope(options) {
|
|
26
|
+
if (options.archived && options.includeArchived) {
|
|
27
|
+
const error = {
|
|
28
|
+
code: "CONFLICTING_OPTIONS",
|
|
29
|
+
message: "--archived and --include-archived cannot be combined",
|
|
30
|
+
details: "--archived deletes only archived agents; --include-archived deletes both",
|
|
31
|
+
};
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
if (options.archived) {
|
|
35
|
+
return "archived";
|
|
36
|
+
}
|
|
37
|
+
if (options.includeArchived) {
|
|
38
|
+
return "both";
|
|
39
|
+
}
|
|
40
|
+
return "active";
|
|
41
|
+
}
|
|
42
|
+
export function matchesAgentDeleteScope(agent, scope) {
|
|
43
|
+
if (scope === "both") {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
const isArchived = agent.archivedAt != null && agent.archivedAt !== "";
|
|
47
|
+
return scope === "archived" ? isArchived : !isArchived;
|
|
48
|
+
}
|
|
14
49
|
export async function runDeleteCommand(id, options, _command) {
|
|
15
50
|
const host = getDaemonHost({ host: options.host });
|
|
16
51
|
if (!id && !options.all && !options.cwd) {
|
|
@@ -21,6 +56,9 @@ export async function runDeleteCommand(id, options, _command) {
|
|
|
21
56
|
};
|
|
22
57
|
throw error;
|
|
23
58
|
}
|
|
59
|
+
// Resolved before connecting: a contradictory flag pair should fail fast, not
|
|
60
|
+
// after opening a socket.
|
|
61
|
+
const scope = resolveAgentDeleteScope(options);
|
|
24
62
|
let client;
|
|
25
63
|
try {
|
|
26
64
|
client = await connectToDaemon({ host: options.host });
|
|
@@ -39,14 +77,10 @@ export async function runDeleteCommand(id, options, _command) {
|
|
|
39
77
|
let agents = fetchPayload.entries.map((entry) => entry.agent);
|
|
40
78
|
const deletedIds = [];
|
|
41
79
|
if (options.all) {
|
|
42
|
-
agents = agents.filter((a) =>
|
|
80
|
+
agents = agents.filter((a) => matchesAgentDeleteScope(a, scope));
|
|
43
81
|
}
|
|
44
82
|
else if (options.cwd) {
|
|
45
|
-
agents = agents.filter((a) =>
|
|
46
|
-
if (a.archivedAt)
|
|
47
|
-
return false;
|
|
48
|
-
return isSameOrDescendantPath(options.cwd, a.cwd);
|
|
49
|
-
});
|
|
83
|
+
agents = agents.filter((a) => matchesAgentDeleteScope(a, scope) && isSameOrDescendantPath(options.cwd, a.cwd));
|
|
50
84
|
}
|
|
51
85
|
else if (id) {
|
|
52
86
|
const fetchResult = await client.fetchAgent({ agentId: id });
|
|
@@ -6,7 +6,7 @@ import { resolve } from "node:path";
|
|
|
6
6
|
import { lookup } from "mime-types";
|
|
7
7
|
import { parseDuration } from "../../utils/duration.js";
|
|
8
8
|
import { collectMultiple } from "../../utils/command-options.js";
|
|
9
|
-
import { resolveProviderAndModel } from "../../utils/provider-model.js";
|
|
9
|
+
import { assertNoConflictingModelInputs, resolveProviderAndModel, } from "../../utils/provider-model.js";
|
|
10
10
|
export { resolveProviderAndModel } from "../../utils/provider-model.js";
|
|
11
11
|
export function addRunOptions(cmd) {
|
|
12
12
|
return cmd
|
|
@@ -210,6 +210,10 @@ function structuredRunSchema(output) {
|
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
212
|
function validateRunOptions(prompt, options, outputSchema) {
|
|
213
|
+
// Before anything opens a socket: a contradictory command line should say so, not report that
|
|
214
|
+
// the daemon is unreachable. Full provider/model resolution still happens after connecting,
|
|
215
|
+
// because it needs the host.
|
|
216
|
+
assertNoConflictingModelInputs(options.provider, options.model);
|
|
213
217
|
if (!prompt || prompt.trim().length === 0) {
|
|
214
218
|
throw {
|
|
215
219
|
code: "MISSING_PROMPT",
|
|
@@ -13,6 +13,7 @@ export interface AgentSendOptions extends CommandOptions {
|
|
|
13
13
|
image?: string[];
|
|
14
14
|
prompt?: string;
|
|
15
15
|
promptFile?: string;
|
|
16
|
+
queue?: boolean;
|
|
16
17
|
}
|
|
17
18
|
export declare function addSendOptions(cmd: Command): Command;
|
|
18
19
|
export declare function runSendCommand(agentIdArg: string, prompt: string | undefined, options: AgentSendOptions, _command: Command): Promise<SingleResult<AgentSendResult>>;
|
|
@@ -19,7 +19,8 @@ export function addSendOptions(cmd) {
|
|
|
19
19
|
.option("--prompt <text>", "Provide the message inline as a flag")
|
|
20
20
|
.option("--prompt-file <path>", "Read the message from a UTF-8 text file")
|
|
21
21
|
.option("--image <path>", "Attach image(s) to the message", collectMultiple, [])
|
|
22
|
-
.option("--no-wait", "Return immediately without waiting for completion")
|
|
22
|
+
.option("--no-wait", "Return immediately without waiting for completion")
|
|
23
|
+
.option("--queue", "If the agent is busy, run this after its current turn instead of interrupting it");
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
26
|
* Read image files and convert them to base64 data URIs
|
|
@@ -148,7 +149,24 @@ export async function runSendCommand(agentIdArg, prompt, options, _command) {
|
|
|
148
149
|
// Read image files if provided
|
|
149
150
|
const images = options.image && options.image.length > 0 ? await readImageFiles(options.image) : undefined;
|
|
150
151
|
// Send the message
|
|
151
|
-
await client.sendAgentMessage(agentIdArg, promptInput, {
|
|
152
|
+
const dispatch = await client.sendAgentMessage(agentIdArg, promptInput, {
|
|
153
|
+
images,
|
|
154
|
+
...(options.queue ? { delivery: "queue" } : {}),
|
|
155
|
+
});
|
|
156
|
+
// A queued message has no run to wait for yet — it starts when the current
|
|
157
|
+
// turn ends, so report it and return rather than blocking on --wait.
|
|
158
|
+
if (dispatch.queued) {
|
|
159
|
+
await client.close();
|
|
160
|
+
return {
|
|
161
|
+
type: "single",
|
|
162
|
+
data: {
|
|
163
|
+
agentId: agentIdArg,
|
|
164
|
+
status: "sent",
|
|
165
|
+
message: "Agent is busy; message queued to run as its next turn",
|
|
166
|
+
},
|
|
167
|
+
schema: agentSendSchema,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
152
170
|
// If --no-wait, return immediately
|
|
153
171
|
if (options.wait === false) {
|
|
154
172
|
await client.close();
|
|
@@ -7,5 +7,18 @@ export interface ResolvedProviderModel {
|
|
|
7
7
|
provider: string;
|
|
8
8
|
model: string | undefined;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* The one provider/model check that needs no daemon: `--provider a/b --model c` is contradictory
|
|
12
|
+
* on its face.
|
|
13
|
+
*
|
|
14
|
+
* Callers run this **before** connecting. Full resolution happens after a connection because it
|
|
15
|
+
* consults the host (personalities, defaults), so leaving this check there meant a plainly
|
|
16
|
+
* malformed command line reported "Cannot connect to daemon" instead of the real problem — and
|
|
17
|
+
* only on machines without a daemon running, which is why it read as a CI-only failure.
|
|
18
|
+
*
|
|
19
|
+
* Silent when either input is absent or they agree; `resolveProviderAndModel` still repeats the
|
|
20
|
+
* check for callers that reach it directly.
|
|
21
|
+
*/
|
|
22
|
+
export declare function assertNoConflictingModelInputs(provider?: string, model?: string): void;
|
|
10
23
|
export declare function resolveProviderAndModel(options: ResolveProviderAndModelOptions): ResolvedProviderModel;
|
|
11
24
|
//# sourceMappingURL=provider-model.d.ts.map
|
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one provider/model check that needs no daemon: `--provider a/b --model c` is contradictory
|
|
3
|
+
* on its face.
|
|
4
|
+
*
|
|
5
|
+
* Callers run this **before** connecting. Full resolution happens after a connection because it
|
|
6
|
+
* consults the host (personalities, defaults), so leaving this check there meant a plainly
|
|
7
|
+
* malformed command line reported "Cannot connect to daemon" instead of the real problem — and
|
|
8
|
+
* only on machines without a daemon running, which is why it read as a CI-only failure.
|
|
9
|
+
*
|
|
10
|
+
* Silent when either input is absent or they agree; `resolveProviderAndModel` still repeats the
|
|
11
|
+
* check for callers that reach it directly.
|
|
12
|
+
*/
|
|
13
|
+
export function assertNoConflictingModelInputs(provider, model) {
|
|
14
|
+
const providerInput = provider?.trim();
|
|
15
|
+
const modelInput = model?.trim();
|
|
16
|
+
if (!providerInput || !modelInput) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const slashIndex = providerInput.indexOf("/");
|
|
20
|
+
if (slashIndex === -1) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const modelFromProvider = providerInput.slice(slashIndex + 1).trim();
|
|
24
|
+
if (!modelFromProvider || modelFromProvider === modelInput) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const error = {
|
|
28
|
+
code: "CONFLICTING_MODEL_OPTIONS",
|
|
29
|
+
message: "Conflicting model values provided",
|
|
30
|
+
details: `--provider specifies model ${modelFromProvider}, but --model specifies ${modelInput}`,
|
|
31
|
+
};
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
1
34
|
export function resolveProviderAndModel(options) {
|
|
2
35
|
const providerInput = options.provider?.trim() || options.defaultProvider;
|
|
3
36
|
const modelInput = options.model?.trim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@otto-code/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Otto CLI - control your AI coding agents from the command line",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"bin": {
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@clack/prompts": "^1.0.0",
|
|
31
|
-
"@otto-code/client": "0.
|
|
32
|
-
"@otto-code/protocol": "0.
|
|
33
|
-
"@otto-code/server": "0.
|
|
31
|
+
"@otto-code/client": "0.7.0",
|
|
32
|
+
"@otto-code/protocol": "0.7.0",
|
|
33
|
+
"@otto-code/server": "0.7.0",
|
|
34
34
|
"chalk": "^5.3.0",
|
|
35
35
|
"commander": "^12.0.0",
|
|
36
36
|
"mime-types": "^2.1.35",
|