@ordewell/cli 0.5.1 → 0.5.2
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/main.js +154 -58
- package/package.json +4 -4
package/dist/main.js
CHANGED
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/main.ts
|
|
27
|
-
var
|
|
27
|
+
var import_core23 = require("@ordewell/core");
|
|
28
28
|
|
|
29
29
|
// src/utils/env.ts
|
|
30
30
|
var import_fs = require("fs");
|
|
@@ -95,6 +95,7 @@ Planning:
|
|
|
95
95
|
ordewell fork Continue in a copy of the conversation and its tasks
|
|
96
96
|
ordewell compact Condense the conversation into a summary (tasks are kept)
|
|
97
97
|
ordewell rewind [<n>] Fork the conversation from before message <n> (no <n>: list them)
|
|
98
|
+
ordewell handoff [action] Review, merge, discard or clean up a finished isolated run
|
|
98
99
|
|
|
99
100
|
Tasks (<id> is an order number or a task ID):
|
|
100
101
|
ordewell add-task --title "..." Add a task to the current plan
|
|
@@ -123,6 +124,7 @@ Planner, models and runners:
|
|
|
123
124
|
ordewell runners [<id> on|off] Enable or disable runners (claude-code, opencode, codex)
|
|
124
125
|
ordewell allowlist set|clear|show Limit which models a runner may use
|
|
125
126
|
ordewell auto [on|off] Autonomous permission mode for new sessions
|
|
127
|
+
ordewell parallel [<n>] How many AI tasks run at once (default 3; applies to a live run)
|
|
126
128
|
ordewell refresh Re-discover runners and model catalogs
|
|
127
129
|
ordewell models List every provider's catalog (works without a server)
|
|
128
130
|
|
|
@@ -196,8 +198,9 @@ Environment:
|
|
|
196
198
|
OPENROUTER_BASE_URL Default: https://openrouter.ai/api/v1
|
|
197
199
|
ORDEWELL_AUTONOMOUS_MODE Approval posture for new sessions (see "ordewell auto")
|
|
198
200
|
ORDEWELL_PORT Daemon port CLI commands target (default: 3742)
|
|
201
|
+
ORDEWELL_DIRENV false: agents skip the project's direnv .envrc
|
|
199
202
|
ORDEWELL_RESEARCH_ENABLED true (default) or false
|
|
200
|
-
ORDEWELL_MAX_PARALLEL Default: 3
|
|
203
|
+
ORDEWELL_MAX_PARALLEL Default: 3 \u2014 set with "ordewell parallel"
|
|
201
204
|
`);
|
|
202
205
|
}
|
|
203
206
|
|
|
@@ -941,6 +944,25 @@ function readLastSession(workspace) {
|
|
|
941
944
|
}
|
|
942
945
|
return null;
|
|
943
946
|
}
|
|
947
|
+
var SESSION_PREFIX = "session-";
|
|
948
|
+
function expandSessionId(id, workspace) {
|
|
949
|
+
if (id.startsWith(SESSION_PREFIX)) return id;
|
|
950
|
+
let files;
|
|
951
|
+
try {
|
|
952
|
+
files = (0, import_fs3.readdirSync)((0, import_path3.join)((0, import_core5.getStateDir)(workspace), "sessions")).filter((f) => f.endsWith(".json"));
|
|
953
|
+
} catch {
|
|
954
|
+
return id;
|
|
955
|
+
}
|
|
956
|
+
const matches = /* @__PURE__ */ new Set();
|
|
957
|
+
for (const file of files) {
|
|
958
|
+
try {
|
|
959
|
+
const full = JSON.parse((0, import_fs3.readFileSync)((0, import_path3.join)((0, import_core5.getStateDir)(workspace), "sessions", file), "utf8"))?.meta?.id;
|
|
960
|
+
if (typeof full === "string" && (full.endsWith(id) || full.slice(SESSION_PREFIX.length).startsWith(id))) matches.add(full);
|
|
961
|
+
} catch {
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return matches.size === 1 ? [...matches][0] : id;
|
|
965
|
+
}
|
|
944
966
|
|
|
945
967
|
// src/approvals.ts
|
|
946
968
|
function describeApproval(event) {
|
|
@@ -2961,30 +2983,47 @@ async function handlePlannerEffort(subArgs, injectedApi) {
|
|
|
2961
2983
|
console.log(`Planner effort set to ${level}.`);
|
|
2962
2984
|
}
|
|
2963
2985
|
|
|
2964
|
-
// src/commands/
|
|
2986
|
+
// src/commands/parallel.ts
|
|
2965
2987
|
var import_core14 = require("@ordewell/core");
|
|
2966
|
-
var USAGE5 = "Usage: ordewell
|
|
2988
|
+
var USAGE5 = "Usage: ordewell parallel [<number of tasks, 1 or more>]";
|
|
2989
|
+
async function handleParallel(subArgs, injectedApi) {
|
|
2990
|
+
const [wanted] = positionals(subArgs);
|
|
2991
|
+
const api = await connect(subArgs, injectedApi);
|
|
2992
|
+
if (!wanted || wanted === "show") {
|
|
2993
|
+
const settings = await api.getSettings();
|
|
2994
|
+
console.log(`Up to ${String(settings.maxParallel ?? "?")} AI tasks run at once. Change with: ordewell parallel <n>`);
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
const limit = (0, import_core14.parseMaxParallel)(wanted);
|
|
2998
|
+
if (limit === null) fail(`"${wanted}" is not a number of tasks (1 or more).`, USAGE5);
|
|
2999
|
+
await persistEnv(api, { ORDEWELL_MAX_PARALLEL: String(limit) });
|
|
3000
|
+
console.log(`Up to ${limit} AI task${limit === 1 ? "" : "s"} now run at once.`);
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
// src/commands/key.ts
|
|
3004
|
+
var import_core15 = require("@ordewell/core");
|
|
3005
|
+
var USAGE6 = "Usage: ordewell key [list | set <provider> <key>]";
|
|
2967
3006
|
async function handleKey(subArgs, injectedApi) {
|
|
2968
3007
|
const [action, provider, ...keyParts] = positionals(subArgs);
|
|
2969
3008
|
if (action && action !== "list" && action !== "set") {
|
|
2970
|
-
fail(`Unknown key action: "${action}"`,
|
|
3009
|
+
fail(`Unknown key action: "${action}"`, USAGE6);
|
|
2971
3010
|
}
|
|
2972
3011
|
const api = await connect(subArgs, injectedApi);
|
|
2973
3012
|
if (action !== "set") {
|
|
2974
3013
|
const catalog = await fetchCatalog(api);
|
|
2975
3014
|
console.log("\nAPI provider keys\n");
|
|
2976
|
-
for (const id2 of
|
|
2977
|
-
const meta2 =
|
|
3015
|
+
for (const id2 of import_core15.PROVIDER_PRIORITY) {
|
|
3016
|
+
const meta2 = import_core15.ALL_PROVIDERS[id2];
|
|
2978
3017
|
const configured = catalog.providers.includes(id2);
|
|
2979
3018
|
console.log(` ${configured ? "\u2713" : " "} ${id2.padEnd(14)} ${meta2.apiKeyEnvVar.padEnd(28)} ${meta2.label}`);
|
|
2980
3019
|
}
|
|
2981
3020
|
console.log(`
|
|
2982
|
-
\u2713 = a working key was found. ${
|
|
3021
|
+
\u2713 = a working key was found. ${USAGE6}`);
|
|
2983
3022
|
return;
|
|
2984
3023
|
}
|
|
2985
|
-
if (!provider) fail(
|
|
3024
|
+
if (!provider) fail(USAGE6);
|
|
2986
3025
|
const id = provider.toLowerCase();
|
|
2987
|
-
const meta =
|
|
3026
|
+
const meta = import_core15.ALL_PROVIDERS[id];
|
|
2988
3027
|
if (!meta) {
|
|
2989
3028
|
fail(`Unknown provider: ${provider}`, "Run `ordewell key` to see the list.");
|
|
2990
3029
|
}
|
|
@@ -2995,7 +3034,7 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
2995
3034
|
}
|
|
2996
3035
|
|
|
2997
3036
|
// src/commands/runners.ts
|
|
2998
|
-
var
|
|
3037
|
+
var USAGE7 = "Usage: ordewell runners [<runner-id> on|off]";
|
|
2999
3038
|
async function handleRunners(subArgs, injectedApi) {
|
|
3000
3039
|
const [runner, toggle] = positionals(subArgs);
|
|
3001
3040
|
const api = await connect(subArgs, injectedApi);
|
|
@@ -3006,7 +3045,7 @@ async function handleRunners(subArgs, injectedApi) {
|
|
|
3006
3045
|
console.log(` ${r.enabled ? "\u2713" : " "} ${r.id.padEnd(14)} ${r.name}${r.enabled ? "" : " (disabled)"}`);
|
|
3007
3046
|
}
|
|
3008
3047
|
console.log(`
|
|
3009
|
-
\u2713 = offered to the planner. ${
|
|
3048
|
+
\u2713 = offered to the planner. ${USAGE7}`);
|
|
3010
3049
|
return;
|
|
3011
3050
|
}
|
|
3012
3051
|
const state = await api.getRunners();
|
|
@@ -3015,7 +3054,7 @@ async function handleRunners(subArgs, injectedApi) {
|
|
|
3015
3054
|
fail(`Unknown runner: ${runner}`, `Enabled or not, this daemon knows: ${state.runners.map((r) => r.id).join(", ")}`);
|
|
3016
3055
|
}
|
|
3017
3056
|
const enabled = toggle === void 0 ? !known.enabled : toggle === "on" ? true : toggle === "off" ? false : null;
|
|
3018
|
-
if (enabled === null) fail(
|
|
3057
|
+
if (enabled === null) fail(USAGE7);
|
|
3019
3058
|
await api.setRunnerEnabled(runner, enabled);
|
|
3020
3059
|
console.log(`${runner} ${enabled ? "enabled" : "disabled"}.`);
|
|
3021
3060
|
}
|
|
@@ -3065,7 +3104,7 @@ async function handleApprove(subArgs, injectedApi) {
|
|
|
3065
3104
|
// src/tui/terminalLauncher.ts
|
|
3066
3105
|
var import_child_process3 = require("child_process");
|
|
3067
3106
|
var import_util = require("util");
|
|
3068
|
-
var
|
|
3107
|
+
var import_core16 = require("@ordewell/core");
|
|
3069
3108
|
var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
|
|
3070
3109
|
var defaultExecFile = async (command, args) => {
|
|
3071
3110
|
const { stdout, stderr } = await execFileAsync(command, args);
|
|
@@ -3085,16 +3124,16 @@ var defaultWhich = (bin) => {
|
|
|
3085
3124
|
async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
|
|
3086
3125
|
const platform = deps.platform ?? process.platform;
|
|
3087
3126
|
const env = deps.env ?? process.env;
|
|
3088
|
-
const hasTmuxImpl = deps.hasTmuxImpl ??
|
|
3127
|
+
const hasTmuxImpl = deps.hasTmuxImpl ?? import_core16.hasTmux;
|
|
3089
3128
|
const execFileImpl = deps.execFileImpl ?? defaultExecFile;
|
|
3090
3129
|
const spawnImpl = deps.spawnImpl ?? defaultSpawn;
|
|
3091
3130
|
const which = deps.which ?? defaultWhich;
|
|
3092
3131
|
if (!hasTmuxImpl()) {
|
|
3093
3132
|
return { ok: false, message: "tmux is not installed \u2014 install it to get a real terminal per task." };
|
|
3094
3133
|
}
|
|
3095
|
-
const session = (0,
|
|
3096
|
-
const socket = (0,
|
|
3097
|
-
const base = (0,
|
|
3134
|
+
const session = (0, import_core16.tmuxSessionName)(port);
|
|
3135
|
+
const socket = (0, import_core16.tmuxSocketName)(port);
|
|
3136
|
+
const base = (0, import_core16.tmuxWindowName)(taskId, planSessionId);
|
|
3098
3137
|
let windows;
|
|
3099
3138
|
try {
|
|
3100
3139
|
const { stdout } = await execFileImpl("tmux", ["-L", socket, "list-windows", "-t", session, "-F", "#{window_name}"]);
|
|
@@ -3388,7 +3427,7 @@ Dependencies \xB7 #${task.order} ${task.title}
|
|
|
3388
3427
|
}
|
|
3389
3428
|
|
|
3390
3429
|
// src/tui/index.ts
|
|
3391
|
-
var
|
|
3430
|
+
var import_core22 = require("@ordewell/core");
|
|
3392
3431
|
|
|
3393
3432
|
// src/tui/editor.ts
|
|
3394
3433
|
function emptyEditor() {
|
|
@@ -3550,6 +3589,7 @@ var SLASH_COMMANDS = [
|
|
|
3550
3589
|
{ name: "allowlist", usage: "/allowlist [set <runner> <ids> | clear <runner>]", description: "Limit which models a runner may use", category: "models" },
|
|
3551
3590
|
{ name: "runners", usage: "/runners [<id> on|off]", description: "Enable or disable runners (claude-code, opencode, codex)", category: "models" },
|
|
3552
3591
|
{ name: "auto", usage: "/auto [on|off]", description: "Autonomous permission mode for new plans", category: "models" },
|
|
3592
|
+
{ name: "parallel", usage: "/parallel [<n>]", description: "How many AI tasks run at once (applies to a live run)", category: "models" },
|
|
3553
3593
|
{ name: "refresh", usage: "/refresh", description: "Re-discover runners and model catalogs", category: "models" },
|
|
3554
3594
|
// Skills
|
|
3555
3595
|
{ name: "tdd", usage: "/tdd [on|off]", description: "Test-Driven Development mode", category: "skills" },
|
|
@@ -3774,6 +3814,7 @@ function chatEditorRoom(cols, active) {
|
|
|
3774
3814
|
}
|
|
3775
3815
|
|
|
3776
3816
|
// src/tui/state.ts
|
|
3817
|
+
var import_core17 = require("@ordewell/core");
|
|
3777
3818
|
var SKILL_IDS = ["tdd", "verify"];
|
|
3778
3819
|
function noSkills() {
|
|
3779
3820
|
return { tdd: false, verify: false };
|
|
@@ -3838,6 +3879,7 @@ function initialState(overrides = {}) {
|
|
|
3838
3879
|
orchestratorModel: "",
|
|
3839
3880
|
plannerProvider: "",
|
|
3840
3881
|
plannerEffort: "",
|
|
3882
|
+
maxParallel: import_core17.DEFAULT_MAX_PARALLEL,
|
|
3841
3883
|
configuredProviders: [],
|
|
3842
3884
|
allowlist: {},
|
|
3843
3885
|
autonomous: true,
|
|
@@ -3857,7 +3899,7 @@ function initialState(overrides = {}) {
|
|
|
3857
3899
|
}
|
|
3858
3900
|
|
|
3859
3901
|
// src/tui/layout.ts
|
|
3860
|
-
var
|
|
3902
|
+
var import_core18 = require("@ordewell/core");
|
|
3861
3903
|
var CHROME_ROWS = 4;
|
|
3862
3904
|
function chatInputWrap(state) {
|
|
3863
3905
|
const active = state.focus === "chat" && !state.overlay;
|
|
@@ -3993,9 +4035,9 @@ function chatScrollMax(state) {
|
|
|
3993
4035
|
}
|
|
3994
4036
|
function plannerSetup(state) {
|
|
3995
4037
|
const provider = state.plannerProvider;
|
|
3996
|
-
const registration =
|
|
4038
|
+
const registration = import_core18.ALL_PROVIDERS[provider];
|
|
3997
4039
|
if (!registration) return { label: "not set", ready: false };
|
|
3998
|
-
const runner = (0,
|
|
4040
|
+
const runner = (0, import_core18.runnerForProvider)(provider);
|
|
3999
4041
|
const ready = runner ? state.runners.some((r) => r.id === runner) : state.configuredProviders.includes(provider);
|
|
4000
4042
|
return { label: registration.label, ready };
|
|
4001
4043
|
}
|
|
@@ -4178,7 +4220,7 @@ function taskLines(state, row, index, cols) {
|
|
|
4178
4220
|
const icon = paint(rawIcon);
|
|
4179
4221
|
const kind = running ? style.yellow("RUN") : task.type === "user" ? style.yellow("MAN") : style.grey(" AI");
|
|
4180
4222
|
const caret = selected ? style.cyan("\u276F") : " ";
|
|
4181
|
-
const orderLabel = parent ? (0,
|
|
4223
|
+
const orderLabel = parent ? (0, import_core18.taskOrderLabel)(task, parent) : String(task.order).padStart(2);
|
|
4182
4224
|
const indent = parent ? " " : "";
|
|
4183
4225
|
const head = `${indent}${caret} ${icon} ${orderLabel} ${kind} `;
|
|
4184
4226
|
const titleRoom = Math.max(1, cols - width(head));
|
|
@@ -4199,21 +4241,22 @@ function taskLines(state, row, index, cols) {
|
|
|
4199
4241
|
const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${modeInfo?.autonomous ? style.yellow(" \u26A1") : ""}` : "";
|
|
4200
4242
|
const runner = task.assignedRunner ?? "";
|
|
4201
4243
|
const bodyPad = parent ? " " : " ";
|
|
4202
|
-
const
|
|
4244
|
+
const activity = idle ? "quiet \u2014 t opens its terminal" : running ? "working" : "";
|
|
4245
|
+
const meta = [activity, runner, model].filter(Boolean).join(" \xB7 ");
|
|
4203
4246
|
if (meta) lines.push(style.grey(truncate(`${bodyPad}${meta}`, cols)));
|
|
4204
4247
|
if (effort || mode) lines.push(style.grey(truncate(`${bodyPad}${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
|
|
4205
4248
|
if (task.isolation?.state === "conflict") {
|
|
4206
4249
|
const where = task.isolation.conflictRepo && task.isolation.conflictRepo !== "." ? ` in ${task.isolation.conflictRepo}` : "";
|
|
4207
|
-
const files = task.isolation.conflictFiles?.length ? ` (${(0,
|
|
4250
|
+
const files = task.isolation.conflictFiles?.length ? ` (${(0, import_core18.capConflictFiles)(task.isolation.conflictFiles)})` : "";
|
|
4208
4251
|
lines.push(style.red(truncate(`${bodyPad}\u26A0 merge conflict${where}${files} \u2014 its work is kept on its own branch`, cols)));
|
|
4209
4252
|
}
|
|
4210
4253
|
if (task.isolation?.state === "repairing") {
|
|
4211
|
-
const files = task.isolation.conflictFiles?.length ? ` in ${(0,
|
|
4254
|
+
const files = task.isolation.conflictFiles?.length ? ` in ${(0, import_core18.capConflictFiles)(task.isolation.conflictFiles)}` : "";
|
|
4212
4255
|
const attempt = task.isolation.repair ? ` (attempt ${task.isolation.repair.attempt}/${task.isolation.repair.limit})` : "";
|
|
4213
4256
|
lines.push(style.yellow(truncate(`${bodyPad}\u21BB repairing conflict${files}${attempt}`, cols)));
|
|
4214
4257
|
}
|
|
4215
4258
|
if (task.isolation?.state === "integrated" && task.isolation.repairedFiles?.length) {
|
|
4216
|
-
lines.push(style.grey(truncate(`${bodyPad}\u21BB landed after repairing conflict in ${(0,
|
|
4259
|
+
lines.push(style.grey(truncate(`${bodyPad}\u21BB landed after repairing conflict in ${(0, import_core18.capConflictFiles)(task.isolation.repairedFiles)}`, cols)));
|
|
4217
4260
|
}
|
|
4218
4261
|
let editorLine;
|
|
4219
4262
|
if (expanded) {
|
|
@@ -4298,7 +4341,7 @@ function helpScrollMax(state) {
|
|
|
4298
4341
|
}
|
|
4299
4342
|
|
|
4300
4343
|
// src/tui/render.ts
|
|
4301
|
-
var
|
|
4344
|
+
var import_core19 = require("@ordewell/core");
|
|
4302
4345
|
function render(state) {
|
|
4303
4346
|
const { rows, cols } = state;
|
|
4304
4347
|
const rowsForBody = bodyRows(state);
|
|
@@ -4562,7 +4605,7 @@ function renderHandoff(state, overlay, rows, cols) {
|
|
|
4562
4605
|
return frame(`Diff \u2014 ${handoffBranch(handoff)}`, [...shown, "", style.grey(`${more}enter or esc goes back`)], rows, cols);
|
|
4563
4606
|
}
|
|
4564
4607
|
const landed = handoff.landed.length === 0 ? [style.grey("Nothing landed on it.")] : handoff.landed.map((task) => {
|
|
4565
|
-
const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0,
|
|
4608
|
+
const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0, import_core19.capConflictFiles)(task.repairedFiles)})`) : "";
|
|
4566
4609
|
return ` ${style.green("\u2713")} #${task.order} ${task.title}${repaired}`;
|
|
4567
4610
|
});
|
|
4568
4611
|
const actions = handoffActions(handoff).map((action, index) => {
|
|
@@ -4652,9 +4695,9 @@ function frame(title, body, rows, cols) {
|
|
|
4652
4695
|
}
|
|
4653
4696
|
|
|
4654
4697
|
// src/tui/reducer.ts
|
|
4655
|
-
var
|
|
4698
|
+
var import_core20 = require("@ordewell/core");
|
|
4656
4699
|
var import_plan_utils3 = require("@ordewell/core/plan-utils");
|
|
4657
|
-
var KNOWN_PROVIDERS2 = Object.keys(
|
|
4700
|
+
var KNOWN_PROVIDERS2 = Object.keys(import_core20.ALL_PROVIDERS);
|
|
4658
4701
|
var DEFAULT_EFFORT = "__runner_default__";
|
|
4659
4702
|
function step(state, effects = []) {
|
|
4660
4703
|
return { state, effects };
|
|
@@ -4673,6 +4716,17 @@ function alreadySpoken(messages, content) {
|
|
|
4673
4716
|
const redrawn = messages.at(-1)?.role === "system" && messages.at(-1)?.content === content;
|
|
4674
4717
|
return spoken || redrawn;
|
|
4675
4718
|
}
|
|
4719
|
+
function runLabel(tasks) {
|
|
4720
|
+
const running = tasks.filter(isTaskRunning);
|
|
4721
|
+
if (running.length > 0) {
|
|
4722
|
+
const [first] = running;
|
|
4723
|
+
const name = first.assignedRunner ? `${first.title} \xB7 ${first.assignedRunner}` : first.title;
|
|
4724
|
+
return running.length > 1 ? `${name} (+${running.length - 1} more)` : name;
|
|
4725
|
+
}
|
|
4726
|
+
const waiting = tasks.filter((t) => t.status === "awaiting_user").length;
|
|
4727
|
+
if (waiting === 0) return "";
|
|
4728
|
+
return waiting === 1 ? "1 task waits for you" : `${waiting} tasks wait for you`;
|
|
4729
|
+
}
|
|
4676
4730
|
function researchLabel(messages, summary) {
|
|
4677
4731
|
const others = messages.filter(isPendingResearch).length - 1;
|
|
4678
4732
|
return others > 0 ? `${summary} (+${others} more)` : summary;
|
|
@@ -4776,6 +4830,16 @@ function reduce(state, action) {
|
|
|
4776
4830
|
const status = state.status === "planning" ? "researching" : state.status;
|
|
4777
4831
|
return step({ ...spoken, status, busyLabel: researchLabel(spoken.messages, summary) });
|
|
4778
4832
|
}
|
|
4833
|
+
// A settled transcript line, not a research step: nothing ever settles a
|
|
4834
|
+
// task's start, so as a step it stayed "⋯" forever and was counted into
|
|
4835
|
+
// the planner's next "(+N more)".
|
|
4836
|
+
case "taskStarted": {
|
|
4837
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
4838
|
+
const tasks = state.tasks.map((t) => t.id === action.taskId && !isTaskRunning(t) ? { ...t, status: "in_progress" } : t);
|
|
4839
|
+
const who = action.runner ? ` \xB7 ${action.runner}` : "";
|
|
4840
|
+
const spoken = say(state, "system", sanitize(`Started "${action.title}"${who}`));
|
|
4841
|
+
return step({ ...spoken, tasks, status: "executing", busyLabel: runLabel(tasks) });
|
|
4842
|
+
}
|
|
4779
4843
|
case "researchStepDone": {
|
|
4780
4844
|
if (stale(state, action.sessionId)) return step(state);
|
|
4781
4845
|
const messages = settleResearchStep(state.messages, action);
|
|
@@ -4816,7 +4880,7 @@ function reduce(state, action) {
|
|
|
4816
4880
|
return t;
|
|
4817
4881
|
});
|
|
4818
4882
|
if (!changed) return step(state);
|
|
4819
|
-
return step({ ...state, status: "executing", tasks });
|
|
4883
|
+
return step({ ...state, status: "executing", tasks, busyLabel: runLabel(tasks) });
|
|
4820
4884
|
}
|
|
4821
4885
|
case "queueReady": {
|
|
4822
4886
|
if (stale(state, action.sessionId)) return step(state);
|
|
@@ -4986,6 +5050,7 @@ function applySettings(state, settings) {
|
|
|
4986
5050
|
orchestratorModel: typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : state.orchestratorModel,
|
|
4987
5051
|
plannerProvider: typeof settings.aiProvider === "string" ? settings.aiProvider : state.plannerProvider,
|
|
4988
5052
|
plannerEffort: typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : state.plannerEffort,
|
|
5053
|
+
maxParallel: typeof settings.maxParallel === "number" ? settings.maxParallel : state.maxParallel,
|
|
4989
5054
|
allowlist: settings.modelAllowlist && typeof settings.modelAllowlist === "object" ? settings.modelAllowlist : state.allowlist
|
|
4990
5055
|
};
|
|
4991
5056
|
}
|
|
@@ -5002,11 +5067,11 @@ function modelDetail(m) {
|
|
|
5002
5067
|
function providerErrorHint(state) {
|
|
5003
5068
|
const failed = Object.keys(state.providerErrors ?? {});
|
|
5004
5069
|
if (failed.length === 0) return void 0;
|
|
5005
|
-
const names = failed.map((p) =>
|
|
5070
|
+
const names = failed.map((p) => import_core20.ALL_PROVIDERS[p]?.label ?? p);
|
|
5006
5071
|
return `\u26A0 Unavailable (key or fetch failed): ${names.join(", ")} \u2014 showing working providers only.`;
|
|
5007
5072
|
}
|
|
5008
5073
|
function plannerModelItems(state) {
|
|
5009
|
-
const runner = (0,
|
|
5074
|
+
const runner = (0, import_core20.runnerForProvider)(state.plannerProvider);
|
|
5010
5075
|
if (!runner) return state.orchestratorModels.map((m) => ({ id: m.id, label: m.label, detail: modelDetail(m) }));
|
|
5011
5076
|
const items = state.models.filter((m) => m.runners?.includes(runner)).map((m) => ({
|
|
5012
5077
|
id: m.id,
|
|
@@ -5020,7 +5085,7 @@ function plannerModelItems(state) {
|
|
|
5020
5085
|
return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
|
|
5021
5086
|
}
|
|
5022
5087
|
function plannerEffortItems(state) {
|
|
5023
|
-
const runner = (0,
|
|
5088
|
+
const runner = (0, import_core20.runnerForProvider)(state.plannerProvider);
|
|
5024
5089
|
if (!runner) {
|
|
5025
5090
|
return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
|
|
5026
5091
|
}
|
|
@@ -5601,6 +5666,8 @@ function runCommand(state, { name, args }) {
|
|
|
5601
5666
|
return runners(state, args);
|
|
5602
5667
|
case "auto":
|
|
5603
5668
|
return setAutonomous(state, args[0]);
|
|
5669
|
+
case "parallel":
|
|
5670
|
+
return setMaxParallel(state, args[0]);
|
|
5604
5671
|
case "mouse":
|
|
5605
5672
|
return setMouseCapture(state, args[0]);
|
|
5606
5673
|
case "sessions":
|
|
@@ -5769,7 +5836,7 @@ function newSession(state) {
|
|
|
5769
5836
|
}
|
|
5770
5837
|
function setModel(state, args) {
|
|
5771
5838
|
if (args[0] === "set" && args[1]) {
|
|
5772
|
-
const runner = (0,
|
|
5839
|
+
const runner = (0, import_core20.runnerForProvider)(state.plannerProvider);
|
|
5773
5840
|
const known = runner ? modelsForRunner(state.models, runner) : [];
|
|
5774
5841
|
if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
|
|
5775
5842
|
return fail3(state, `${args[1]} was not discovered for ${runner}.`);
|
|
@@ -5791,21 +5858,21 @@ function setModel(state, args) {
|
|
|
5791
5858
|
}
|
|
5792
5859
|
function plannerItems(state) {
|
|
5793
5860
|
const installed = new Set(state.runners.map((r) => r.id));
|
|
5794
|
-
const agents =
|
|
5795
|
-
const runner = (0,
|
|
5861
|
+
const agents = import_core20.CLI_PROVIDERS.map((id) => {
|
|
5862
|
+
const runner = (0, import_core20.runnerForProvider)(id);
|
|
5796
5863
|
const ready = installed.has(runner);
|
|
5797
5864
|
return {
|
|
5798
5865
|
id,
|
|
5799
|
-
label:
|
|
5866
|
+
label: import_core20.ALL_PROVIDERS[id].label,
|
|
5800
5867
|
detail: ready ? "coding agent \xB7 no API key needed" : "CLI not installed or not on PATH",
|
|
5801
5868
|
selected: state.plannerProvider === id,
|
|
5802
5869
|
disabled: !ready
|
|
5803
5870
|
};
|
|
5804
5871
|
});
|
|
5805
|
-
const vendors =
|
|
5872
|
+
const vendors = import_core20.PROVIDER_PRIORITY.filter((id) => state.configuredProviders.includes(id)).map((id) => ({
|
|
5806
5873
|
id,
|
|
5807
|
-
label:
|
|
5808
|
-
detail:
|
|
5874
|
+
label: import_core20.ALL_PROVIDERS[id].label,
|
|
5875
|
+
detail: import_core20.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
5809
5876
|
selected: state.plannerProvider === id
|
|
5810
5877
|
}));
|
|
5811
5878
|
return vendors.length > 0 ? [...agents, ...vendors] : [...agents, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
|
|
@@ -5863,16 +5930,16 @@ function setKey(state, args) {
|
|
|
5863
5930
|
}
|
|
5864
5931
|
return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
|
|
5865
5932
|
}
|
|
5866
|
-
const items =
|
|
5933
|
+
const items = import_core20.PROVIDER_PRIORITY.map((id) => ({
|
|
5867
5934
|
id,
|
|
5868
|
-
label:
|
|
5869
|
-
detail:
|
|
5935
|
+
label: import_core20.ALL_PROVIDERS[id].label,
|
|
5936
|
+
detail: import_core20.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
5870
5937
|
selected: state.configuredProviders.includes(id)
|
|
5871
5938
|
}));
|
|
5872
5939
|
return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
|
|
5873
5940
|
}
|
|
5874
5941
|
function keyPrompt(provider) {
|
|
5875
|
-
const meta =
|
|
5942
|
+
const meta = import_core20.ALL_PROVIDERS[provider];
|
|
5876
5943
|
return {
|
|
5877
5944
|
kind: "prompt",
|
|
5878
5945
|
title: `${meta.label} API key`,
|
|
@@ -5947,6 +6014,12 @@ function runners(state, args) {
|
|
|
5947
6014
|
})
|
|
5948
6015
|
} });
|
|
5949
6016
|
}
|
|
6017
|
+
function setMaxParallel(state, arg) {
|
|
6018
|
+
if (!arg) return step(say(state, "system", `Up to ${state.maxParallel} AI task${state.maxParallel === 1 ? "" : "s"} run at once \u2014 /parallel <n> changes it.`));
|
|
6019
|
+
const limit = (0, import_core20.parseMaxParallel)(arg);
|
|
6020
|
+
if (limit === null) return fail3(state, "Usage: /parallel [<number of tasks, 1 or more>]");
|
|
6021
|
+
return step({ ...state, maxParallel: limit }, [{ type: "setMaxParallel", limit }]);
|
|
6022
|
+
}
|
|
5950
6023
|
function setAutonomous(state, arg) {
|
|
5951
6024
|
const enabled = resolveToggle(arg, state.autonomous);
|
|
5952
6025
|
if (enabled === null) return fail3(state, "Usage: /auto [on|off]");
|
|
@@ -6202,7 +6275,9 @@ function createApp(deps) {
|
|
|
6202
6275
|
function dispatch(action) {
|
|
6203
6276
|
if (exited) return;
|
|
6204
6277
|
const result = reduce(state, action);
|
|
6278
|
+
const previousSession = state.sessionId;
|
|
6205
6279
|
state = result.state;
|
|
6280
|
+
if (state.sessionId && state.sessionId !== previousSession) deps.onSessionChange?.(state.sessionId, state);
|
|
6206
6281
|
if (state.exiting) {
|
|
6207
6282
|
exited = true;
|
|
6208
6283
|
clearInterval(spinnerInterval);
|
|
@@ -6234,7 +6309,7 @@ function createApp(deps) {
|
|
|
6234
6309
|
|
|
6235
6310
|
// src/tui/effects.ts
|
|
6236
6311
|
var import_child_process4 = require("child_process");
|
|
6237
|
-
var
|
|
6312
|
+
var import_core21 = require("@ordewell/core");
|
|
6238
6313
|
var import_plan_utils4 = require("@ordewell/core/plan-utils");
|
|
6239
6314
|
var ConversationQueue = class {
|
|
6240
6315
|
tail = Promise.resolve();
|
|
@@ -6405,7 +6480,7 @@ async function perform(effect, deps) {
|
|
|
6405
6480
|
dispatch({ type: "notice", message: `Orchestrator model set to ${effect.modelId}.` });
|
|
6406
6481
|
return;
|
|
6407
6482
|
case "setPlanner": {
|
|
6408
|
-
const meta =
|
|
6483
|
+
const meta = import_core21.ALL_PROVIDERS[effect.provider];
|
|
6409
6484
|
if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
|
|
6410
6485
|
const env = { AI_PROVIDER: effect.provider };
|
|
6411
6486
|
const settings = await api.updateSettings({ env });
|
|
@@ -6417,7 +6492,7 @@ async function perform(effect, deps) {
|
|
|
6417
6492
|
});
|
|
6418
6493
|
dispatch({
|
|
6419
6494
|
type: "notice",
|
|
6420
|
-
message: `${(0,
|
|
6495
|
+
message: `${(0, import_core21.isCliProvider)(effect.provider) ? `Planning with ${meta.label} \u2014 no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
|
|
6421
6496
|
});
|
|
6422
6497
|
await loadModels(deps);
|
|
6423
6498
|
return;
|
|
@@ -6429,7 +6504,7 @@ async function perform(effect, deps) {
|
|
|
6429
6504
|
dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
|
|
6430
6505
|
return;
|
|
6431
6506
|
case "setApiKey": {
|
|
6432
|
-
const meta =
|
|
6507
|
+
const meta = import_core21.ALL_PROVIDERS[effect.provider];
|
|
6433
6508
|
if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
|
|
6434
6509
|
await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
|
|
6435
6510
|
deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
|
|
@@ -6460,6 +6535,11 @@ async function perform(effect, deps) {
|
|
|
6460
6535
|
dispatch({ type: "notice", message: effect.message });
|
|
6461
6536
|
return;
|
|
6462
6537
|
}
|
|
6538
|
+
case "setMaxParallel":
|
|
6539
|
+
await api.updateSettings({ env: { ORDEWELL_MAX_PARALLEL: String(effect.limit) } });
|
|
6540
|
+
deps.setEnvVar("ORDEWELL_MAX_PARALLEL", String(effect.limit));
|
|
6541
|
+
dispatch({ type: "notice", message: `Up to ${effect.limit} AI task${effect.limit === 1 ? "" : "s"} now run at once.` });
|
|
6542
|
+
return;
|
|
6463
6543
|
case "setAutonomous":
|
|
6464
6544
|
deps.setEnvVar("ORDEWELL_AUTONOMOUS_MODE", String(effect.enabled));
|
|
6465
6545
|
dispatch({ type: "notice", message: `Autonomous mode ${effect.enabled ? "on" : "off"}.` });
|
|
@@ -6590,7 +6670,7 @@ function osc52(text) {
|
|
|
6590
6670
|
function copySelection(deps, text) {
|
|
6591
6671
|
if (!text) return;
|
|
6592
6672
|
const lines = text.split("\n").length;
|
|
6593
|
-
const command = (0,
|
|
6673
|
+
const command = (0, import_core21.clipboardCopyCommand)(deps.hasBin);
|
|
6594
6674
|
if (command) {
|
|
6595
6675
|
try {
|
|
6596
6676
|
(deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
|
|
@@ -6727,12 +6807,12 @@ function onExecutionEvent(dispatch, event, sessionId) {
|
|
|
6727
6807
|
return;
|
|
6728
6808
|
}
|
|
6729
6809
|
case "task_started":
|
|
6730
|
-
dispatch({ type: "
|
|
6810
|
+
dispatch({ type: "taskStarted", taskId: String(event.taskId), title: String(event.title ?? event.taskId), runner: event.runner, sessionId });
|
|
6731
6811
|
return;
|
|
6732
6812
|
// The extension shows these in its checkpoint panel; here they are
|
|
6733
6813
|
// transcript lines, which is the TUI's equivalent surface.
|
|
6734
6814
|
case "checkpoint":
|
|
6735
|
-
dispatch({ type: "notice", message: `\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0,
|
|
6815
|
+
dispatch({ type: "notice", message: `\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0, import_core21.truncateCheckpointSummary)(event.summary)}` });
|
|
6736
6816
|
return;
|
|
6737
6817
|
case "approval_decided":
|
|
6738
6818
|
dispatch({ type: "notice", message: describeApprovalDecision2(event) });
|
|
@@ -7116,7 +7196,7 @@ async function handleTui(subArgs) {
|
|
|
7116
7196
|
}
|
|
7117
7197
|
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
7118
7198
|
try {
|
|
7119
|
-
(0,
|
|
7199
|
+
(0, import_core22.assertWorkspaceExists)(workspace);
|
|
7120
7200
|
} catch {
|
|
7121
7201
|
console.error(
|
|
7122
7202
|
`Workspace "${workspace}" does not exist.
|
|
@@ -7125,7 +7205,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
7125
7205
|
);
|
|
7126
7206
|
process.exit(1);
|
|
7127
7207
|
}
|
|
7128
|
-
registerSkillCommands((0,
|
|
7208
|
+
registerSkillCommands((0, import_core22.createSkillsService)(workspace).listSkills());
|
|
7129
7209
|
const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
|
|
7130
7210
|
const targetPort = explicitPort ? resolvePort(subArgs) : await findFreePort();
|
|
7131
7211
|
const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(targetPort, { detached: false });
|
|
@@ -7144,7 +7224,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
7144
7224
|
conversationQueue,
|
|
7145
7225
|
port,
|
|
7146
7226
|
dispatch: (action) => app.dispatch(action),
|
|
7147
|
-
newSessionId: () => (0,
|
|
7227
|
+
newSessionId: () => (0, import_core22.mintSessionId)(),
|
|
7148
7228
|
setEnvVar: (key, value) => {
|
|
7149
7229
|
writeEnvVar(findEnvFile(), key, value);
|
|
7150
7230
|
process.env[key] = value;
|
|
@@ -7161,7 +7241,16 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
7161
7241
|
},
|
|
7162
7242
|
exit: () => shutdown(0)
|
|
7163
7243
|
}),
|
|
7164
|
-
onExit: () => shutdown(0)
|
|
7244
|
+
onExit: () => shutdown(0),
|
|
7245
|
+
// The same pointer `ordewell plan` leaves, so `ordewell handoff`,
|
|
7246
|
+
// `terminal <n>` and the other one-shot commands act on the session this
|
|
7247
|
+
// TUI is showing instead of saying there is none.
|
|
7248
|
+
onSessionChange: (sessionId, state) => {
|
|
7249
|
+
try {
|
|
7250
|
+
saveLastSession(sessionId, state.goal, state.runners.filter((r) => r.enabled).map((r) => r.id), workspace);
|
|
7251
|
+
} catch {
|
|
7252
|
+
}
|
|
7253
|
+
}
|
|
7165
7254
|
});
|
|
7166
7255
|
terminal = openTerminal({
|
|
7167
7256
|
mouse: mouseCapture,
|
|
@@ -7207,6 +7296,7 @@ var COMMANDS = {
|
|
|
7207
7296
|
model: handleModel,
|
|
7208
7297
|
planner: handlePlanner,
|
|
7209
7298
|
"planner-effort": handlePlannerEffort,
|
|
7299
|
+
parallel: handleParallel,
|
|
7210
7300
|
key: handleKey,
|
|
7211
7301
|
runners: handleRunners,
|
|
7212
7302
|
auto: handleAuto,
|
|
@@ -7253,10 +7343,12 @@ function cliVersion() {
|
|
|
7253
7343
|
}
|
|
7254
7344
|
|
|
7255
7345
|
// src/main.ts
|
|
7346
|
+
var TUI_FLAGS = /* @__PURE__ */ new Set(["--workspace", "--port"]);
|
|
7256
7347
|
async function main() {
|
|
7257
|
-
(0,
|
|
7348
|
+
(0, import_core23.migrateOldConfigDir)();
|
|
7258
7349
|
loadEnvFile();
|
|
7259
7350
|
const argv = process.argv.slice(2);
|
|
7351
|
+
if (TUI_FLAGS.has(argv[0] ?? "")) argv.unshift("tui");
|
|
7260
7352
|
const command = argv[0] ?? (process.stdin.isTTY ? "tui" : "--help");
|
|
7261
7353
|
if (command === "--help" || command === "-h") {
|
|
7262
7354
|
printHelp();
|
|
@@ -7266,6 +7358,10 @@ async function main() {
|
|
|
7266
7358
|
console.log(cliVersion());
|
|
7267
7359
|
return;
|
|
7268
7360
|
}
|
|
7361
|
+
const sessionAt = argv.indexOf("--session-id");
|
|
7362
|
+
if (sessionAt > 0 && argv[sessionAt + 1]) {
|
|
7363
|
+
argv[sessionAt + 1] = expandSessionId(argv[sessionAt + 1], flag(argv, "--workspace") || process.cwd());
|
|
7364
|
+
}
|
|
7269
7365
|
const handler = COMMANDS[command];
|
|
7270
7366
|
if (!handler) {
|
|
7271
7367
|
console.error(`Unknown command: ${command}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ordewell/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Ordewell CLI and TUI — plan, run and verify coding-agent tasks from the terminal.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Alessandro Costanzo Ciano",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"main": "./dist/main.js",
|
|
34
34
|
"bin": {
|
|
35
|
-
"ordewell": "
|
|
35
|
+
"ordewell": "dist/main.js"
|
|
36
36
|
},
|
|
37
37
|
"files": [
|
|
38
38
|
"dist",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"typecheck": "tsc --noEmit"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@ordewell/core": "^0.5.
|
|
52
|
-
"@ordewell/web": "^0.5.
|
|
51
|
+
"@ordewell/core": "^0.5.2",
|
|
52
|
+
"@ordewell/web": "^0.5.2",
|
|
53
53
|
"string-width": "^7.2.0",
|
|
54
54
|
"ws": "^8.18.0"
|
|
55
55
|
},
|