@wirecat/cli-core 0.568.0 → 0.569.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/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.569.0 — 05.11.2026
|
|
11
|
+
|
|
12
|
+
### Changed — may break callers
|
|
13
|
+
|
|
14
|
+
- **A claude agent schedule runs on Sonnet and starts lean.** `schedule agent` takes `--model <model>` (an alias such
|
|
15
|
+
as `sonnet` or `opus`, a model id, or `""` for the harness's own default). Without it, a claude schedule runs on
|
|
16
|
+
`sonnet` and a codex schedule on codex's own default; a host's config file may change either
|
|
17
|
+
(`schedule_model_claude`, `schedule_model_codex` in `SCHEDULE_SETTINGS`): flag, then config file, then default.
|
|
18
|
+
A new claude schedule also starts with `--tools Bash --strict-mcp-config --disable-slash-commands
|
|
19
|
+
--setting-sources ""`: none of the owner's MCP servers, skills, settings or CLAUDE.md, while `--permission-mode
|
|
20
|
+
dontAsk` and `--allowedTools` still hold. A "say ok" run measured $0.152 and 37,925 input tokens before, $0.019 and
|
|
21
|
+
4,740 after. `schedule_lean_start: false` keeps the owner's setup. Both are read when the schedule is added; an
|
|
22
|
+
existing schedule keeps its argv until it is added again. `ScheduleLimits` gains `models` and `leanStart`.
|
|
23
|
+
|
|
10
24
|
## 0.568.0 — 05.11.2026
|
|
11
25
|
|
|
12
26
|
### Changed — may break callers
|
|
@@ -4,6 +4,8 @@ import { type MailWaiting } from "../../mail/marks.js";
|
|
|
4
4
|
import { type AppIdentity } from "../../ports/app.js";
|
|
5
5
|
import { type AnswerOutput } from "../envelope.js";
|
|
6
6
|
import type { DeclaredSetting } from "../setting-defaults.js";
|
|
7
|
+
declare const HARNESSES: readonly ["claude", "codex"];
|
|
8
|
+
type Harness = (typeof HARNESSES)[number];
|
|
7
9
|
export interface ScheduleHost {
|
|
8
10
|
app: AppIdentity;
|
|
9
11
|
context(command: Command, resolving?: {
|
|
@@ -17,7 +19,7 @@ export interface ScheduleHost {
|
|
|
17
19
|
* then runs only once enough of it waits, or the oldest has waited long enough.
|
|
18
20
|
*/
|
|
19
21
|
newMail?: (command: Command, holder: string) => Promise<MailWaiting>;
|
|
20
|
-
/** The config file's
|
|
22
|
+
/** The config file's settings for agent schedules; a schedule's own flags win, the defaults fill the rest. */
|
|
21
23
|
limits?: (command: Command) => ScheduleLimits;
|
|
22
24
|
/**
|
|
23
25
|
* Other CLIs the scheduled agent may run besides this one, each under its own `<PREFIX>_READ_ONLY`: memo's mail
|
|
@@ -25,14 +27,20 @@ export interface ScheduleHost {
|
|
|
25
27
|
*/
|
|
26
28
|
agentRuns?: readonly AgentCli[];
|
|
27
29
|
}
|
|
28
|
-
export type ScheduleLimits = Partial<Pick<Schedule, "mailWaitMinutes" | "mailBatch" | "maxCostPerDay"
|
|
29
|
-
|
|
30
|
+
export type ScheduleLimits = Partial<Pick<Schedule, "mailWaitMinutes" | "mailBatch" | "maxCostPerDay"> & {
|
|
31
|
+
models: Partial<Record<Harness, string>>;
|
|
32
|
+
leanStart: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
/** The agent-schedule settings a host with `newMail` writes into its config file's top level (WC-CMD-30). */
|
|
30
35
|
export declare const SCHEDULE_SETTINGS: readonly DeclaredSetting[];
|
|
31
36
|
/** `SCHEDULE_SETTINGS` as the config file gives them; a missing or unusable value is left to the default. */
|
|
32
37
|
export declare const scheduleLimitsOf: (config: {
|
|
33
38
|
schedule_mail_wait?: unknown;
|
|
34
39
|
schedule_mail_batch?: unknown;
|
|
35
40
|
schedule_max_cost_per_day?: unknown;
|
|
41
|
+
schedule_model_claude?: unknown;
|
|
42
|
+
schedule_model_codex?: unknown;
|
|
43
|
+
schedule_lean_start?: unknown;
|
|
36
44
|
}) => ScheduleLimits;
|
|
37
45
|
/**
|
|
38
46
|
* Runs the CLI's own commands, or an agent, later or on a timer — through systemd (Linux), launchd
|
|
@@ -40,3 +48,4 @@ export declare const scheduleLimitsOf: (config: {
|
|
|
40
48
|
* the rest, so what runs and with which variables is the same on all three.
|
|
41
49
|
*/
|
|
42
50
|
export declare const scheduleCommandFor: ({ app, context: contextOf, newMail, limits: configured, agentRuns, }: ScheduleHost) => Command;
|
|
51
|
+
export {};
|
|
@@ -23,6 +23,15 @@ const AGENT_MINIMUM_MINUTES = 60;
|
|
|
23
23
|
const GATED_AGENT_MINIMUM_MINUTES = 5;
|
|
24
24
|
const COMMAND_MINIMUM_MINUTES = 1;
|
|
25
25
|
const HARNESSES = ["claude", "codex"];
|
|
26
|
+
/** A mid-tier model by default; codex has no alias of that tier, so it takes its own default. Empty: the harness's own. */
|
|
27
|
+
const AGENT_MODELS = { claude: "sonnet", codex: "" };
|
|
28
|
+
/** An alias or a model id; a leading dash would read as the harness's next flag. */
|
|
29
|
+
const isModel = (model) => /^([A-Za-z0-9][\w.:/@[\]-]*)?$/.test(model);
|
|
30
|
+
/**
|
|
31
|
+
* Claude starts without the owner's MCP servers, skills, settings and CLAUDE.md: a run reads its instructions
|
|
32
|
+
* through the CLI, and the rest was most of its starting context. Auth, `--permission-mode` and `--allowedTools` hold.
|
|
33
|
+
*/
|
|
34
|
+
const LEAN_CLAUDE = ["--tools", "Bash", "--strict-mcp-config", "--disable-slash-commands", "--setting-sources", ""];
|
|
26
35
|
const whenOf = ({ every, at, atTime }, minimum) => {
|
|
27
36
|
if ([every ?? at, atTime].filter(Boolean).length !== 1) {
|
|
28
37
|
throw new CliError("validation_error", "say when: --every <30m|2h|1d>, --at HH:MM (daily), or --at-time <30m|2d|ISO time>");
|
|
@@ -76,7 +85,7 @@ const withWhen = (command) => command
|
|
|
76
85
|
const promptExample = (app) => app.messenger === false
|
|
77
86
|
? `summarize what came in during the last day: ${app.command} personal search messages 'date>1d' --newest --limit 20`
|
|
78
87
|
: `run the daily follow-up review: ${app.command} skill show follow-ups`;
|
|
79
|
-
/** The agent-schedule
|
|
88
|
+
/** The agent-schedule settings a host with `newMail` writes into its config file's top level (WC-CMD-30). */
|
|
80
89
|
export const SCHEDULE_SETTINGS = [
|
|
81
90
|
{
|
|
82
91
|
key: "schedule_mail_wait",
|
|
@@ -93,11 +102,32 @@ export const SCHEDULE_SETTINGS = [
|
|
|
93
102
|
default: SCHEDULE_DEFAULTS.maxCostPerDay,
|
|
94
103
|
description: "US dollars an agent schedule's runs may cost in a day before it waits for the next; 0 for no cap",
|
|
95
104
|
},
|
|
105
|
+
{
|
|
106
|
+
key: "schedule_model_claude",
|
|
107
|
+
default: AGENT_MODELS.claude,
|
|
108
|
+
description: "the model a new claude agent schedule runs on, unless --model names one; empty for claude's own default",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
key: "schedule_model_codex",
|
|
112
|
+
default: AGENT_MODELS.codex,
|
|
113
|
+
description: "the model a new codex agent schedule runs on, unless --model names one; empty for codex's own default",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
key: "schedule_lean_start",
|
|
117
|
+
default: true,
|
|
118
|
+
description: "a new claude agent schedule starts without your MCP servers, skills, settings and CLAUDE.md; false loads them",
|
|
119
|
+
},
|
|
96
120
|
];
|
|
97
121
|
/** `SCHEDULE_SETTINGS` as the config file gives them; a missing or unusable value is left to the default. */
|
|
98
122
|
export const scheduleLimitsOf = (config) => {
|
|
99
123
|
const { schedule_mail_wait: wait, schedule_mail_batch: batch, schedule_max_cost_per_day: cap } = config;
|
|
124
|
+
const models = Object.fromEntries(HARNESSES.flatMap((harness) => {
|
|
125
|
+
const model = config[`schedule_model_${harness}`];
|
|
126
|
+
return typeof model === "string" && isModel(model.trim()) ? [[harness, model.trim()]] : [];
|
|
127
|
+
}));
|
|
100
128
|
return {
|
|
129
|
+
...(Object.keys(models).length ? { models } : {}),
|
|
130
|
+
...(typeof config.schedule_lean_start === "boolean" ? { leanStart: config.schedule_lean_start } : {}),
|
|
101
131
|
...(typeof wait === "string"
|
|
102
132
|
? { mailWaitMinutes: Math.ceil(parseDuration(wait, "schedule_mail_wait") / 60_000) }
|
|
103
133
|
: {}),
|
|
@@ -106,6 +136,7 @@ export const scheduleLimitsOf = (config) => {
|
|
|
106
136
|
};
|
|
107
137
|
};
|
|
108
138
|
const given = (limits) => Object.fromEntries(Object.entries(limits).filter(([, value]) => value !== undefined));
|
|
139
|
+
const shown = (argv) => argv.map((arg) => (arg === "" ? '""' : arg)).join(" ");
|
|
109
140
|
const usd = (value) => `$${value.toFixed(2)}`;
|
|
110
141
|
/**
|
|
111
142
|
* Runs the CLI's own commands, or an agent, later or on a timer — through systemd (Linux), launchd
|
|
@@ -143,7 +174,7 @@ export const scheduleCommandFor = ({ app, context: contextOf, newMail, limits: c
|
|
|
143
174
|
await timer().install(schedule, [...system.entry, "schedule", "exec", file]);
|
|
144
175
|
say(scheduleAnswer(schedule, { file, timer: timer().where(schedule.name), replaced }), [
|
|
145
176
|
`${replaced ? "Replaced" : "Added"} ${schedule.name}: ${describeWhen(schedule.when)}.`,
|
|
146
|
-
`It runs: ${schedule.kind === "agent" ? schedule.argv
|
|
177
|
+
`It runs: ${schedule.kind === "agent" ? shown(schedule.argv) : `${app.command} ${shown(schedule.argv)}`}`,
|
|
147
178
|
`Output: ${join(dirname(file), "runs")} · now: ${lineOf(self, `schedule run ${schedule.name}`)} · stop: ${lineOf(self, `schedule remove ${schedule.name}`)}`,
|
|
148
179
|
]);
|
|
149
180
|
};
|
|
@@ -216,12 +247,14 @@ export const scheduleCommandFor = ({ app, context: contextOf, newMail, limits: c
|
|
|
216
247
|
examples: [
|
|
217
248
|
`${app.command} personal schedule agent review claude "review what came in overnight and draft replies" --at 08:30`,
|
|
218
249
|
`${app.command} personal schedule agent triage codex "list what needs an answer today" --every 2h --max-cost 1`,
|
|
250
|
+
`${app.command} personal schedule agent weekly claude "review the week's open threads" --at 09:00 --model opus`,
|
|
219
251
|
],
|
|
220
252
|
}))
|
|
221
253
|
.description(`start an agent (claude or codex) with a prompt on a timer; it runs with ${agentClis.map((one) => `${one.envPrefix}_READ_ONLY=1`).join(" and ")}, so it reads and drafts and cannot send — its answer is kept and you are notified`)
|
|
222
254
|
.argument("<name>", "lowercase letters, digits and dashes")
|
|
223
255
|
.addArgument(new Argument("<harness>", "the agent to start").choices(HARNESSES))
|
|
224
256
|
.argument("<prompt>", `what to do, e.g. "${promptExample(app)}"`)
|
|
257
|
+
.option("--model <model>", `the model it runs on: an alias such as sonnet, or a model id; "" for the harness's default (default sonnet for claude, codex's own for codex, or the config file's)`)
|
|
225
258
|
.addOption(new Option("--max-cost <usd>", "claude only: stop a run that costs more than this, in US dollars").argParser(Number))
|
|
226
259
|
.addOption(new Option("--max-cost-per-day <usd>", `claude only: start no run for the rest of the day once its runs cost this much, in US dollars; 0 for no cap (default ${SCHEDULE_DEFAULTS.maxCostPerDay}, or the config file's)`).argParser(Number))).action(async function (name, harness, prompt) {
|
|
227
260
|
const { context, system, environment, save } = prepare(this);
|
|
@@ -236,6 +269,11 @@ export const scheduleCommandFor = ({ app, context: contextOf, newMail, limits: c
|
|
|
236
269
|
if (mailBatch !== undefined && !(Number.isSafeInteger(mailBatch) && mailBatch >= 1))
|
|
237
270
|
throw new CliError("validation_error", "--mail-batch takes a count of emails, 1 or more");
|
|
238
271
|
const mailWaitMinutes = options.mailWait === undefined ? undefined : Math.ceil(parseDuration(options.mailWait, "--mail-wait") / 60_000);
|
|
272
|
+
if (options.model !== undefined && !isModel(options.model))
|
|
273
|
+
throw new CliError("validation_error", `--model takes an alias such as sonnet or a model id, not "${options.model}"`);
|
|
274
|
+
const settings = configured?.(this) ?? {};
|
|
275
|
+
const model = options.model ?? settings.models?.[harness] ?? AGENT_MODELS[harness];
|
|
276
|
+
const modelFlag = model ? ["--model", model] : [];
|
|
239
277
|
const program = findProgram(harness, context.env, system.platform);
|
|
240
278
|
if (!program)
|
|
241
279
|
throw new CliError("not_found", `${harness} is not on PATH`);
|
|
@@ -254,6 +292,8 @@ export const scheduleCommandFor = ({ app, context: contextOf, newMail, limits: c
|
|
|
254
292
|
"--allowedTools",
|
|
255
293
|
...agentClis.map((one) => `Bash(${one.command}:*)`),
|
|
256
294
|
"--no-session-persistence",
|
|
295
|
+
...modelFlag,
|
|
296
|
+
...((settings.leanStart ?? true) ? LEAN_CLAUDE : []),
|
|
257
297
|
...(options.maxCost ? ["--max-budget-usd", String(options.maxCost)] : []),
|
|
258
298
|
]
|
|
259
299
|
: [
|
|
@@ -266,6 +306,7 @@ export const scheduleCommandFor = ({ app, context: contextOf, newMail, limits: c
|
|
|
266
306
|
// The CLIs reach their provider and write the store and their own state, and nothing else.
|
|
267
307
|
"-c",
|
|
268
308
|
"sandbox_workspace_write.network_access=true",
|
|
309
|
+
...modelFlag,
|
|
269
310
|
...[dirname(storePath(context.env)), ...folders].flatMap((dir) => ["--add-dir", dir]),
|
|
270
311
|
prompt,
|
|
271
312
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.569.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|
package/parity.json
CHANGED
|
@@ -3972,6 +3972,12 @@
|
|
|
3972
3972
|
"planned": {
|
|
3973
3973
|
"wa": "wa backlog: mount the shared schedule command"
|
|
3974
3974
|
}
|
|
3975
|
+
},
|
|
3976
|
+
"--model": {
|
|
3977
|
+
"in": ["max", "tg"],
|
|
3978
|
+
"planned": {
|
|
3979
|
+
"wa": "wa backlog: mount the shared schedule command"
|
|
3980
|
+
}
|
|
3975
3981
|
}
|
|
3976
3982
|
}
|
|
3977
3983
|
},
|