@ordewell/cli 0.4.10 → 0.4.11
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 +334 -292
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -23,12 +23,15 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
mod
|
|
24
24
|
));
|
|
25
25
|
|
|
26
|
+
// src/main.ts
|
|
27
|
+
var import_core18 = require("@ordewell/core");
|
|
28
|
+
|
|
26
29
|
// src/utils/env.ts
|
|
27
30
|
var import_fs = require("fs");
|
|
28
31
|
var import_path = require("path");
|
|
29
|
-
var
|
|
32
|
+
var import_core = require("@ordewell/core");
|
|
30
33
|
function findEnvFile() {
|
|
31
|
-
return (0, import_path.join)((0,
|
|
34
|
+
return (0, import_path.join)((0, import_core.globalDataDir)(), ".env");
|
|
32
35
|
}
|
|
33
36
|
function loadEnvFile() {
|
|
34
37
|
const filePath = findEnvFile();
|
|
@@ -83,7 +86,7 @@ an id.
|
|
|
83
86
|
Planning:
|
|
84
87
|
ordewell Full-screen terminal UI (everything the VS Code extension does)
|
|
85
88
|
ordewell tui The same thing, named \u2014 for scripts and aliases
|
|
86
|
-
ordewell plan --goal "text" Plan a goal conversationally (honours
|
|
89
|
+
ordewell plan --goal "text" Plan a goal conversationally (honours verify)
|
|
87
90
|
ordewell run Execute the last generated plan (streams status)
|
|
88
91
|
ordewell approve Sign off a plan paused for review, and continue it
|
|
89
92
|
ordewell stop Stop execution of the last session (or --server)
|
|
@@ -121,12 +124,8 @@ Planner, models and runners:
|
|
|
121
124
|
ordewell models List every provider's catalog (works without a server)
|
|
122
125
|
|
|
123
126
|
Modes:
|
|
124
|
-
ordewell grill-me [on|off] Toggle Grill-Me challenge mode (or show status)
|
|
125
127
|
ordewell tdd [on|off] Toggle Test-Driven Development mode (or show status)
|
|
126
|
-
ordewell prd [on|off] Toggle PRD mode (planner writes a PRD before the plan)
|
|
127
|
-
ordewell review [on|off] Toggle review mode \u2014 adds a final opinion-review task, human sign-off (or show status)
|
|
128
128
|
ordewell verify [on|off] Toggle verification mode \u2014 adds a final evidence-based task that runs the full suite (or show status)
|
|
129
|
-
ordewell research-subagents [on|off] Toggle parallel read-only research subagents during planning (or show status)
|
|
130
129
|
|
|
131
130
|
Other:
|
|
132
131
|
ordewell web Start the API server (foreground; --daemon for background)
|
|
@@ -202,16 +201,21 @@ Environment:
|
|
|
202
201
|
// src/daemon.ts
|
|
203
202
|
var import_child_process = require("child_process");
|
|
204
203
|
var import_fs2 = require("fs");
|
|
205
|
-
var
|
|
204
|
+
var import_os = require("os");
|
|
206
205
|
var import_path2 = require("path");
|
|
207
206
|
var import_http = __toESM(require("http"));
|
|
208
|
-
var
|
|
207
|
+
var import_net = require("net");
|
|
208
|
+
var import_core2 = require("@ordewell/core");
|
|
209
209
|
var DEFAULT_PORT = 3742;
|
|
210
|
-
var CONFIG_DIR = (0,
|
|
210
|
+
var CONFIG_DIR = (0, import_core2.globalDataDir)();
|
|
211
211
|
var LEGACY_PID_FILE = (0, import_path2.join)(CONFIG_DIR, "server.pid");
|
|
212
|
+
var OLD_CONFIG_DIR = (0, import_path2.join)((0, import_os.homedir)(), ".config", "ordewell");
|
|
212
213
|
function pidFileFor(port) {
|
|
213
214
|
return port === DEFAULT_PORT ? LEGACY_PID_FILE : (0, import_path2.join)(CONFIG_DIR, `server-${port}.pid`);
|
|
214
215
|
}
|
|
216
|
+
function oldPidFileFor(port) {
|
|
217
|
+
return port === DEFAULT_PORT ? (0, import_path2.join)(OLD_CONFIG_DIR, "server.pid") : (0, import_path2.join)(OLD_CONFIG_DIR, `server-${port}.pid`);
|
|
218
|
+
}
|
|
215
219
|
function httpGet(url, timeoutMs = 2e3, headers = {}) {
|
|
216
220
|
return new Promise((resolve3, reject) => {
|
|
217
221
|
const req = import_http.default.get(url, { timeout: timeoutMs, headers }, (res) => {
|
|
@@ -243,12 +247,12 @@ function getWebServerPath() {
|
|
|
243
247
|
);
|
|
244
248
|
}
|
|
245
249
|
async function isDaemonRunning(port = DEFAULT_PORT) {
|
|
246
|
-
const token = (0,
|
|
250
|
+
const token = (0, import_core2.readDaemonToken)(port);
|
|
247
251
|
try {
|
|
248
252
|
const res = await httpGet(
|
|
249
253
|
`http://127.0.0.1:${port}/api/workspaces`,
|
|
250
254
|
2e3,
|
|
251
|
-
token ? { Authorization: (0,
|
|
255
|
+
token ? { Authorization: (0, import_core2.bearerHeaderValue)(token) } : {}
|
|
252
256
|
);
|
|
253
257
|
return res.status === 200 || res.status === 401;
|
|
254
258
|
} catch {
|
|
@@ -268,7 +272,12 @@ async function startDaemon(port = DEFAULT_PORT, opts) {
|
|
|
268
272
|
}
|
|
269
273
|
const logFd = (0, import_fs2.openSync)((0, import_path2.join)(CONFIG_DIR, "server.log"), "a");
|
|
270
274
|
const detached = opts?.detached ?? true;
|
|
271
|
-
const child = (0, import_child_process.spawn)(process.execPath, [
|
|
275
|
+
const child = (0, import_child_process.spawn)(process.execPath, [
|
|
276
|
+
serverPath,
|
|
277
|
+
"--port",
|
|
278
|
+
String(port),
|
|
279
|
+
...detached ? [] : ["--watch-parent", String(process.pid)]
|
|
280
|
+
], {
|
|
272
281
|
detached,
|
|
273
282
|
stdio: ["ignore", logFd, logFd],
|
|
274
283
|
env: { ...process.env }
|
|
@@ -288,7 +297,8 @@ async function startDaemon(port = DEFAULT_PORT, opts) {
|
|
|
288
297
|
process.exit(1);
|
|
289
298
|
}
|
|
290
299
|
async function stopDaemon(port = DEFAULT_PORT) {
|
|
291
|
-
|
|
300
|
+
let pidFile = pidFileFor(port);
|
|
301
|
+
if (!(0, import_fs2.existsSync)(pidFile) && (0, import_fs2.existsSync)(oldPidFileFor(port))) pidFile = oldPidFileFor(port);
|
|
292
302
|
if (!(0, import_fs2.existsSync)(pidFile)) {
|
|
293
303
|
console.error(`No server PID file found for port ${port}. Is the server running?`);
|
|
294
304
|
return false;
|
|
@@ -298,7 +308,7 @@ async function stopDaemon(port = DEFAULT_PORT) {
|
|
|
298
308
|
const pid = parseInt(pidStr, 10);
|
|
299
309
|
if (!pid || isNaN(pid)) {
|
|
300
310
|
(0, import_fs2.unlinkSync)(pidFile);
|
|
301
|
-
(0,
|
|
311
|
+
(0, import_core2.clearDaemonToken)(port);
|
|
302
312
|
return false;
|
|
303
313
|
}
|
|
304
314
|
process.kill(pid, "SIGTERM");
|
|
@@ -311,7 +321,7 @@ async function stopDaemon(port = DEFAULT_PORT) {
|
|
|
311
321
|
(0, import_fs2.unlinkSync)(pidFile);
|
|
312
322
|
} catch {
|
|
313
323
|
}
|
|
314
|
-
(0,
|
|
324
|
+
(0, import_core2.clearDaemonToken)(port);
|
|
315
325
|
console.log("Server was not running (stale PID removed).");
|
|
316
326
|
return true;
|
|
317
327
|
}
|
|
@@ -338,6 +348,18 @@ function isConnectionRefused(err) {
|
|
|
338
348
|
function describeConnectionRefused(port) {
|
|
339
349
|
return `No Ordewell server is listening on port ${port}. It may have been stopped, or it may have crashed \u2014 check ${(0, import_path2.join)(CONFIG_DIR, "server.log")}. Start one with \`ordewell web --daemon\`.`;
|
|
340
350
|
}
|
|
351
|
+
function findFreePort() {
|
|
352
|
+
return new Promise((resolvePromise, reject) => {
|
|
353
|
+
const srv = (0, import_net.createServer)();
|
|
354
|
+
srv.unref();
|
|
355
|
+
srv.on("error", reject);
|
|
356
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
357
|
+
const address = srv.address();
|
|
358
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
359
|
+
srv.close(() => resolvePromise(port));
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
}
|
|
341
363
|
function resolvePort(args = []) {
|
|
342
364
|
const idx = args.indexOf("--port");
|
|
343
365
|
if (idx !== -1) {
|
|
@@ -352,7 +374,7 @@ function resolvePort(args = []) {
|
|
|
352
374
|
// src/apiClient.ts
|
|
353
375
|
var import_http2 = __toESM(require("http"));
|
|
354
376
|
var import_ws = __toESM(require("ws"));
|
|
355
|
-
var
|
|
377
|
+
var import_core3 = require("@ordewell/core");
|
|
356
378
|
var DEFAULT_HTTP_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
357
379
|
var ApiClient = class {
|
|
358
380
|
port;
|
|
@@ -365,7 +387,7 @@ var ApiClient = class {
|
|
|
365
387
|
* A missing file is not an error here — the daemon's own 401 names the path.
|
|
366
388
|
*/
|
|
367
389
|
token() {
|
|
368
|
-
return (0,
|
|
390
|
+
return (0, import_core3.readDaemonToken)(this.port);
|
|
369
391
|
}
|
|
370
392
|
httpRequest(method, urlPath, body) {
|
|
371
393
|
return new Promise((resolve3, reject) => {
|
|
@@ -379,7 +401,7 @@ var ApiClient = class {
|
|
|
379
401
|
method,
|
|
380
402
|
headers: {
|
|
381
403
|
...body ? { "Content-Type": "application/json" } : {},
|
|
382
|
-
...token ? { Authorization: (0,
|
|
404
|
+
...token ? { Authorization: (0, import_core3.bearerHeaderValue)(token) } : {}
|
|
383
405
|
},
|
|
384
406
|
// Planner research on a real repository regularly exceeds two minutes.
|
|
385
407
|
// The benchmark harness uses the same 15-minute default explicitly;
|
|
@@ -408,7 +430,7 @@ var ApiClient = class {
|
|
|
408
430
|
req.end();
|
|
409
431
|
});
|
|
410
432
|
}
|
|
411
|
-
async generatePlan(goal, runners2, workspace, sessionId = (0,
|
|
433
|
+
async generatePlan(goal, runners2, workspace, sessionId = (0, import_core3.mintSessionId)()) {
|
|
412
434
|
const res = await this.httpRequest("POST", `/api/plans/${sessionId}/generate`, {
|
|
413
435
|
goal,
|
|
414
436
|
runners: runners2 || void 0,
|
|
@@ -421,7 +443,7 @@ var ApiClient = class {
|
|
|
421
443
|
}
|
|
422
444
|
/**
|
|
423
445
|
* Open the ADR-0002 planner dialogue. Unlike `generatePlan`, this path honours
|
|
424
|
-
* the
|
|
446
|
+
* the verify toggle, so the returned plan may be a question
|
|
425
447
|
* (empty `tasks`, last word in `conversationHistory`) rather than a committed plan.
|
|
426
448
|
*/
|
|
427
449
|
async startConversation(sessionId, goal, runners2, workspace) {
|
|
@@ -609,7 +631,7 @@ var ApiClient = class {
|
|
|
609
631
|
const token = this.token();
|
|
610
632
|
const socket = new import_ws.default(
|
|
611
633
|
`ws://127.0.0.1:${this.port}/ws/session/${sessionId}`,
|
|
612
|
-
token ? (0,
|
|
634
|
+
token ? (0, import_core3.tokenSubprotocols)(token) : void 0
|
|
613
635
|
);
|
|
614
636
|
socket.on("unexpected-response", (_req, res) => {
|
|
615
637
|
let body = "";
|
|
@@ -773,6 +795,7 @@ function positionals(args) {
|
|
|
773
795
|
}
|
|
774
796
|
|
|
775
797
|
// src/utils/tasks.ts
|
|
798
|
+
var import_core4 = require("@ordewell/core");
|
|
776
799
|
function allTasksOf(plan) {
|
|
777
800
|
const tasks = plan?.pendingTasks || plan?.tasks || [];
|
|
778
801
|
const executionLog = plan?.executionLog || [];
|
|
@@ -780,6 +803,10 @@ function allTasksOf(plan) {
|
|
|
780
803
|
}
|
|
781
804
|
function resolveTaskId(plan, identifier) {
|
|
782
805
|
const allTasks = allTasksOf(plan);
|
|
806
|
+
if (identifier.includes(".")) {
|
|
807
|
+
const subtask = (0, import_core4.resolveOrderLabel)(allTasks, identifier);
|
|
808
|
+
return subtask?.id;
|
|
809
|
+
}
|
|
783
810
|
const order = parseInt(identifier, 10);
|
|
784
811
|
if (!isNaN(order)) {
|
|
785
812
|
const task = allTasks.find((t) => t.order === order);
|
|
@@ -795,20 +822,24 @@ function resolveTaskId(plan, identifier) {
|
|
|
795
822
|
// src/utils/session.ts
|
|
796
823
|
var import_fs3 = require("fs");
|
|
797
824
|
var import_path3 = require("path");
|
|
798
|
-
var
|
|
799
|
-
var
|
|
825
|
+
var import_core5 = require("@ordewell/core");
|
|
826
|
+
var LAST_SESSION_FILENAME = "last-session.json";
|
|
827
|
+
function lastSessionFile(workspace) {
|
|
828
|
+
return (0, import_path3.join)((0, import_core5.getStateDir)(workspace), LAST_SESSION_FILENAME);
|
|
829
|
+
}
|
|
800
830
|
function saveLastSession(sessionId, goal, runners2, workspace) {
|
|
801
|
-
const dir = (0,
|
|
831
|
+
const dir = (0, import_core5.getStateDir)(workspace);
|
|
802
832
|
if (!(0, import_fs3.existsSync)(dir)) (0, import_fs3.mkdirSync)(dir, { recursive: true });
|
|
803
833
|
(0, import_fs3.writeFileSync)(
|
|
804
|
-
|
|
834
|
+
lastSessionFile(workspace),
|
|
805
835
|
JSON.stringify({ sessionId, goal, runners: runners2, workspace, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
806
836
|
);
|
|
807
837
|
}
|
|
808
|
-
function readLastSession() {
|
|
838
|
+
function readLastSession(workspace) {
|
|
809
839
|
try {
|
|
810
|
-
|
|
811
|
-
|
|
840
|
+
const file = lastSessionFile(workspace);
|
|
841
|
+
if ((0, import_fs3.existsSync)(file)) {
|
|
842
|
+
return JSON.parse((0, import_fs3.readFileSync)(file, "utf8"));
|
|
812
843
|
}
|
|
813
844
|
} catch {
|
|
814
845
|
}
|
|
@@ -914,7 +945,7 @@ function isTransient(event) {
|
|
|
914
945
|
}
|
|
915
946
|
|
|
916
947
|
// src/commands/plan.ts
|
|
917
|
-
var
|
|
948
|
+
var import_core6 = require("@ordewell/core");
|
|
918
949
|
function planTasks(plan) {
|
|
919
950
|
return plan?.tasks || [];
|
|
920
951
|
}
|
|
@@ -976,21 +1007,26 @@ function printPlan(plan, sessionId, runners2, models) {
|
|
|
976
1007
|
const tasks = planTasks(plan);
|
|
977
1008
|
const aiCount = tasks.filter((t) => t.type !== "user").length;
|
|
978
1009
|
const manCount = tasks.filter((t) => t.type === "user").length;
|
|
1010
|
+
const modelSuffix = (task) => {
|
|
1011
|
+
if (!task.assignedModel) return "";
|
|
1012
|
+
const found = models.find((m) => m.modelId === task.assignedModel?.modelId);
|
|
1013
|
+
const rp = found?.runnerProvider;
|
|
1014
|
+
const label = rp ? rp.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ") : "";
|
|
1015
|
+
return rp ? ` (${task.assignedModel.modelLabel} \xB7 ${label})` : ` (${task.assignedModel.modelLabel})`;
|
|
1016
|
+
};
|
|
979
1017
|
console.log(`
|
|
980
1018
|
Plan: ${tasks.length} tasks (${aiCount} AI, ${manCount} Manual) \u2014 ${(plan.runners || runners2).join(", ")}`);
|
|
981
1019
|
console.log(`Session: ${sessionId}
|
|
982
1020
|
`);
|
|
983
1021
|
for (const t of tasks) {
|
|
984
1022
|
const typeIcon = t.type === "user" ? "[MAN]" : "[ AI]";
|
|
985
|
-
let model = "";
|
|
986
|
-
if (t.assignedModel) {
|
|
987
|
-
const found = models.find((m) => m.modelId === t.assignedModel?.modelId);
|
|
988
|
-
const rp = found?.runnerProvider;
|
|
989
|
-
const label = rp ? rp.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ") : "";
|
|
990
|
-
model = rp ? ` (${t.assignedModel.modelLabel} \xB7 ${label})` : ` (${t.assignedModel.modelLabel})`;
|
|
991
|
-
}
|
|
992
1023
|
const deps = t.dependencies?.length ? ` \u2190 ${t.dependencies.join(", ")}` : "";
|
|
993
|
-
console.log(` ${
|
|
1024
|
+
console.log(` ${(0, import_core6.taskOrderLabel)(t).padStart(2)}. ${typeIcon} ${t.title}${modelSuffix(t)}${deps}`);
|
|
1025
|
+
for (const sub of t.subtasks || []) {
|
|
1026
|
+
const subTypeIcon = sub.type === "user" ? "[MAN]" : "[ AI]";
|
|
1027
|
+
const subDeps = sub.dependencies?.length ? ` \u2190 ${sub.dependencies.join(", ")}` : "";
|
|
1028
|
+
console.log(` ${(0, import_core6.taskOrderLabel)(sub, t)}. ${subTypeIcon} ${sub.title}${modelSuffix(sub)}${subDeps}`);
|
|
1029
|
+
}
|
|
994
1030
|
}
|
|
995
1031
|
if (manCount > 0) {
|
|
996
1032
|
console.log("\n [MAN] = manual step \u2014 run `ordewell tui` to work through it");
|
|
@@ -1036,7 +1072,7 @@ async function handlePlan(subArgs, deps = {}) {
|
|
|
1036
1072
|
runners2 = runnerFlags;
|
|
1037
1073
|
}
|
|
1038
1074
|
console.error(`Generating plan for: "${goal}"...`);
|
|
1039
|
-
const sessionId = (0,
|
|
1075
|
+
const sessionId = (0, import_core6.mintSessionId)();
|
|
1040
1076
|
const isTty = !!process.stderr.isTTY;
|
|
1041
1077
|
let lastLineLength = 0;
|
|
1042
1078
|
function renderStep(event) {
|
|
@@ -1148,9 +1184,10 @@ function iconFor(status) {
|
|
|
1148
1184
|
function resolveSessionId(subArgs) {
|
|
1149
1185
|
const explicit = flag(subArgs, "--session-id");
|
|
1150
1186
|
if (explicit) return explicit;
|
|
1151
|
-
const
|
|
1187
|
+
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
1188
|
+
const last = readLastSession(workspace);
|
|
1152
1189
|
if (!last) {
|
|
1153
|
-
console.error(
|
|
1190
|
+
console.error(`No session specified. Use --session-id <id> or run \`ordewell plan\` in ${workspace} first.`);
|
|
1154
1191
|
process.exit(1);
|
|
1155
1192
|
}
|
|
1156
1193
|
console.error(`Using last session: ${last.sessionId} ("${last.goal}")`);
|
|
@@ -1224,51 +1261,29 @@ async function handleRun(subArgs) {
|
|
|
1224
1261
|
|
|
1225
1262
|
// src/commands/status.ts
|
|
1226
1263
|
var import_fs4 = require("fs");
|
|
1264
|
+
var import_core7 = require("@ordewell/core");
|
|
1227
1265
|
async function handleStatus(subArgs, injectedApi) {
|
|
1228
1266
|
const sessionId = flag(subArgs, "--session-id");
|
|
1229
|
-
const
|
|
1230
|
-
let workspace = explicitWorkspace || process.cwd();
|
|
1267
|
+
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
1231
1268
|
const outputPath = flag(subArgs, "--output");
|
|
1232
1269
|
const asJson = hasFlag(subArgs, "--json") || Boolean(outputPath);
|
|
1233
1270
|
const port = injectedApi ? resolvePort(subArgs) : await ensureDaemon(resolvePort(subArgs));
|
|
1234
1271
|
const api = injectedApi || new ApiClient(port);
|
|
1235
1272
|
if (sessionId) {
|
|
1236
|
-
|
|
1237
|
-
try {
|
|
1238
|
-
session = await api.getSession(sessionId, workspace);
|
|
1239
|
-
} catch (err) {
|
|
1240
|
-
const last = readLastSession();
|
|
1241
|
-
if (!explicitWorkspace && last?.workspace && last.workspace !== workspace) {
|
|
1242
|
-
session = await api.getSession(sessionId, last.workspace);
|
|
1243
|
-
} else {
|
|
1244
|
-
throw err;
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1273
|
+
const session = await api.getSession(sessionId, workspace);
|
|
1247
1274
|
if (asJson) {
|
|
1248
1275
|
emitJson(session, outputPath);
|
|
1249
1276
|
return;
|
|
1250
1277
|
}
|
|
1251
1278
|
printOneSession(sessionId, session.meta, session.plan);
|
|
1252
1279
|
} else {
|
|
1253
|
-
|
|
1254
|
-
if (sessions.length === 0 && !explicitWorkspace) {
|
|
1255
|
-
const last2 = readLastSession();
|
|
1256
|
-
if (last2?.workspace && last2.workspace !== workspace) {
|
|
1257
|
-
const fallback = await api.getSessions(last2.workspace);
|
|
1258
|
-
if (fallback.length > 0) {
|
|
1259
|
-
workspace = last2.workspace;
|
|
1260
|
-
sessions = fallback;
|
|
1261
|
-
console.log(`(no sessions in ${process.cwd()} \u2014 showing ${workspace})
|
|
1262
|
-
`);
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1280
|
+
const sessions = await api.getSessions(workspace);
|
|
1266
1281
|
if (asJson) {
|
|
1267
1282
|
emitJson(sessions, outputPath);
|
|
1268
1283
|
return;
|
|
1269
1284
|
}
|
|
1270
1285
|
if (sessions.length === 0) {
|
|
1271
|
-
console.log(
|
|
1286
|
+
console.log(`No sessions found in ${workspace}.`);
|
|
1272
1287
|
return;
|
|
1273
1288
|
}
|
|
1274
1289
|
console.log(`${sessions.length} session(s):
|
|
@@ -1276,7 +1291,7 @@ async function handleStatus(subArgs, injectedApi) {
|
|
|
1276
1291
|
for (const s of sessions) {
|
|
1277
1292
|
console.log(` ${s.id.slice(-8)} ${s.status.padEnd(10)} ${s.taskCount} tasks ${(s.runners || []).join(",").padEnd(12)} ${s.goal.slice(0, 60)}`);
|
|
1278
1293
|
}
|
|
1279
|
-
const last = readLastSession();
|
|
1294
|
+
const last = readLastSession(workspace);
|
|
1280
1295
|
if (last) {
|
|
1281
1296
|
console.log(`
|
|
1282
1297
|
Last active: ${last.sessionId.slice(-8)} ("${last.goal.slice(0, 50)}")`);
|
|
@@ -1317,7 +1332,13 @@ function printOneSession(sessionId, meta, plan) {
|
|
|
1317
1332
|
const icon = iconFor(t.status);
|
|
1318
1333
|
const model = t.assignedModel ? ` (${t.assignedModel.modelLabel})` : "";
|
|
1319
1334
|
const shortId = (t.id ?? "").slice(-8);
|
|
1320
|
-
console.log(` ${
|
|
1335
|
+
console.log(` ${(0, import_core7.taskOrderLabel)(t).padStart(2)}. ${icon} [${shortId}] ${t.title}${model}`);
|
|
1336
|
+
for (const sub of t.subtasks || []) {
|
|
1337
|
+
const subIcon = iconFor(sub.status);
|
|
1338
|
+
const subModel = sub.assignedModel ? ` (${sub.assignedModel.modelLabel})` : "";
|
|
1339
|
+
const subShortId = (sub.id ?? "").slice(-8);
|
|
1340
|
+
console.log(` ${(0, import_core7.taskOrderLabel)(sub, t)}. ${subIcon} [${subShortId}] ${sub.title}${subModel}`);
|
|
1341
|
+
}
|
|
1321
1342
|
}
|
|
1322
1343
|
}
|
|
1323
1344
|
|
|
@@ -1330,7 +1351,8 @@ async function handleStop(subArgs) {
|
|
|
1330
1351
|
}
|
|
1331
1352
|
let sessionId = flag(subArgs, "--session-id");
|
|
1332
1353
|
if (!sessionId) {
|
|
1333
|
-
const
|
|
1354
|
+
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
1355
|
+
const last = readLastSession(workspace);
|
|
1334
1356
|
if (!last) {
|
|
1335
1357
|
console.error("No session specified. Use --session-id <id> or --server to stop the server.");
|
|
1336
1358
|
process.exit(1);
|
|
@@ -1367,10 +1389,10 @@ async function handleWeb(subArgs) {
|
|
|
1367
1389
|
}
|
|
1368
1390
|
|
|
1369
1391
|
// src/commands/models.ts
|
|
1370
|
-
var
|
|
1392
|
+
var import_core9 = require("@ordewell/core");
|
|
1371
1393
|
|
|
1372
1394
|
// src/commands/model.ts
|
|
1373
|
-
var
|
|
1395
|
+
var import_core8 = require("@ordewell/core");
|
|
1374
1396
|
|
|
1375
1397
|
// src/catalog.ts
|
|
1376
1398
|
function normalizeCatalog(result) {
|
|
@@ -1452,7 +1474,8 @@ function toTaskView(task) {
|
|
|
1452
1474
|
dependencies: task.dependencies ?? [],
|
|
1453
1475
|
assignedRunner: task.assignedRunner || void 0,
|
|
1454
1476
|
taskMode: task.taskMode || void 0,
|
|
1455
|
-
assignedModel: task.assignedModel ?? void 0
|
|
1477
|
+
assignedModel: task.assignedModel ?? void 0,
|
|
1478
|
+
subtasks: [...task.subtasks ?? []].sort((a, b) => a.order - b.order).map(toTaskView)
|
|
1456
1479
|
};
|
|
1457
1480
|
}
|
|
1458
1481
|
function taskViews(plan) {
|
|
@@ -1475,7 +1498,7 @@ async function handleModel(subArgs, injectedApi) {
|
|
|
1475
1498
|
if (!modelId) fail(USAGE);
|
|
1476
1499
|
const settings = await api.getSettings();
|
|
1477
1500
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
1478
|
-
const runner = (0,
|
|
1501
|
+
const runner = (0, import_core8.runnerForProvider)(provider);
|
|
1479
1502
|
if (runner) {
|
|
1480
1503
|
const known = (await fetchCatalog(api)).models.filter((m) => m.runners?.includes(runner));
|
|
1481
1504
|
if (known.length > 0 && !known.some((m) => m.id === modelId)) {
|
|
@@ -1494,7 +1517,7 @@ async function showModels(api) {
|
|
|
1494
1517
|
const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
|
|
1495
1518
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
1496
1519
|
const current = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
|
|
1497
|
-
const runner = (0,
|
|
1520
|
+
const runner = (0, import_core8.runnerForProvider)(provider);
|
|
1498
1521
|
const options = runner ? catalog.models.filter((m) => m.runners?.includes(runner)) : catalog.orchestratorModels;
|
|
1499
1522
|
console.log(`
|
|
1500
1523
|
Planner model: ${current || "(backend default)"}`);
|
|
@@ -1528,8 +1551,8 @@ async function handleModels(subArgs) {
|
|
|
1528
1551
|
const rest = subArgs.filter((_, i) => i !== setIdx && i !== setIdx + 1);
|
|
1529
1552
|
return handleModel(["set", subArgs[setIdx + 1], ...rest]);
|
|
1530
1553
|
}
|
|
1531
|
-
const config = new
|
|
1532
|
-
const providers = (0,
|
|
1554
|
+
const config = new import_core9.EnvConfig();
|
|
1555
|
+
const providers = (0, import_core9.configuredProviders)(config);
|
|
1533
1556
|
if (providers.length === 0) {
|
|
1534
1557
|
console.error("No providers configured. Set an API key for a provider, e.g.:");
|
|
1535
1558
|
console.error(" OPENROUTER_API_KEY=... (OpenRouter)");
|
|
@@ -1538,19 +1561,19 @@ async function handleModels(subArgs) {
|
|
|
1538
1561
|
console.error(" OPENAI_COMPATIBLE_BASE_URL=... (self-hosted / OpenAI-compatible)");
|
|
1539
1562
|
process.exit(1);
|
|
1540
1563
|
}
|
|
1541
|
-
const { apiKeys, baseUrls } = (0,
|
|
1564
|
+
const { apiKeys, baseUrls } = (0, import_core9.collectProviderCredentials)(config);
|
|
1542
1565
|
let models;
|
|
1543
1566
|
let errors;
|
|
1544
1567
|
try {
|
|
1545
|
-
const result = await (0,
|
|
1546
|
-
models = (0,
|
|
1568
|
+
const result = await (0, import_core9.fetchAllProviderModels)({ apiKeys, baseUrls });
|
|
1569
|
+
models = (0, import_core9.toOrchestratorOptions)(result.models, import_core9.ORCHESTRATOR_SHORTCUTS);
|
|
1547
1570
|
errors = result.errors;
|
|
1548
1571
|
} catch (err) {
|
|
1549
1572
|
console.error(`Failed to fetch models: ${err instanceof Error ? err.message : String(err)}`);
|
|
1550
1573
|
process.exit(1);
|
|
1551
1574
|
return;
|
|
1552
1575
|
}
|
|
1553
|
-
const providerLabel = (p) => (0,
|
|
1576
|
+
const providerLabel = (p) => (0, import_core9.getProviderMeta)(p)?.label ?? p;
|
|
1554
1577
|
console.log(
|
|
1555
1578
|
`
|
|
1556
1579
|
Available models (${models.length}) across ${providers.length} configured provider(s):`
|
|
@@ -1667,9 +1690,9 @@ function handleSetup(subArgs) {
|
|
|
1667
1690
|
}
|
|
1668
1691
|
|
|
1669
1692
|
// src/commands/plugins.ts
|
|
1670
|
-
var
|
|
1693
|
+
var import_core10 = require("@ordewell/core");
|
|
1671
1694
|
function handlePlugins(subArgs) {
|
|
1672
|
-
const registry = new
|
|
1695
|
+
const registry = new import_core10.RunnerRegistry();
|
|
1673
1696
|
registry.loadUserPlugins();
|
|
1674
1697
|
const sub = subArgs[0];
|
|
1675
1698
|
if (sub === "list" || !sub) {
|
|
@@ -1705,7 +1728,7 @@ ${plugins.length} plugin(s):
|
|
|
1705
1728
|
process.exit(1);
|
|
1706
1729
|
}
|
|
1707
1730
|
try {
|
|
1708
|
-
const resolved = (0,
|
|
1731
|
+
const resolved = (0, import_core10.classifyPluginSource)(source);
|
|
1709
1732
|
const manifest = resolved.kind === "git" ? registry.installFromGit(resolved.url) : registry.installFromPath(resolved.path);
|
|
1710
1733
|
console.log(`Installed plugin: ${manifest.name} v${manifest.version}`);
|
|
1711
1734
|
} catch (err) {
|
|
@@ -1746,23 +1769,6 @@ ${plugins.length} plugin(s):
|
|
|
1746
1769
|
process.exit(1);
|
|
1747
1770
|
}
|
|
1748
1771
|
|
|
1749
|
-
// src/commands/grillMe.ts
|
|
1750
|
-
async function handleGrillMe(subArgs) {
|
|
1751
|
-
const action = subArgs[0];
|
|
1752
|
-
const port = await ensureDaemon(resolvePort(subArgs));
|
|
1753
|
-
const api = new ApiClient(port);
|
|
1754
|
-
if (action === "on" || action === "off") {
|
|
1755
|
-
const result = await api.sendCommand("grill-me", { action });
|
|
1756
|
-
const state = result.settings?.grillMe;
|
|
1757
|
-
console.log(`Grill-Me: ${state?.enabled ? "ON" : "OFF"}`);
|
|
1758
|
-
} else {
|
|
1759
|
-
const settings = await api.getSettings();
|
|
1760
|
-
const grillMe = settings.grillMe;
|
|
1761
|
-
console.log(`Grill-Me: ${grillMe?.enabled ? "ON" : "OFF"}`);
|
|
1762
|
-
console.log("Usage: ordewell grill-me [on|off]");
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
1772
|
// src/commands/tdd.ts
|
|
1767
1773
|
async function handleTdd(subArgs) {
|
|
1768
1774
|
const action = subArgs[0];
|
|
@@ -1780,24 +1786,6 @@ async function handleTdd(subArgs) {
|
|
|
1780
1786
|
}
|
|
1781
1787
|
}
|
|
1782
1788
|
|
|
1783
|
-
// src/commands/prd.ts
|
|
1784
|
-
async function handlePrd(subArgs) {
|
|
1785
|
-
const action = subArgs[0];
|
|
1786
|
-
const port = await ensureDaemon(resolvePort(subArgs));
|
|
1787
|
-
const api = new ApiClient(port);
|
|
1788
|
-
if (action === "on" || action === "off") {
|
|
1789
|
-
const result = await api.sendCommand("prd", { action });
|
|
1790
|
-
const state = result.settings?.prd;
|
|
1791
|
-
console.log(`PRD mode: ${state?.enabled ? "ON" : "OFF"}`);
|
|
1792
|
-
} else {
|
|
1793
|
-
const settings = await api.getSettings();
|
|
1794
|
-
const prd = settings.prd;
|
|
1795
|
-
console.log(`PRD mode: ${prd?.enabled ? "ON" : "OFF"}`);
|
|
1796
|
-
console.log("Usage: ordewell prd [on|off]");
|
|
1797
|
-
console.log("When ON, the planner previews and writes a markdown PRD (saved to .scratch/<slug>/PRD.md) before the task plan.");
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
|
|
1801
1789
|
// src/commands/allowlist.ts
|
|
1802
1790
|
var USAGE2 = `Usage:
|
|
1803
1791
|
ordewell allowlist set <runner> <id1,id2,\u2026> Set allowlist for a runner
|
|
@@ -1884,23 +1872,6 @@ async function handleShow(args, api) {
|
|
|
1884
1872
|
}
|
|
1885
1873
|
}
|
|
1886
1874
|
|
|
1887
|
-
// src/commands/review.ts
|
|
1888
|
-
async function handleReview(subArgs, api) {
|
|
1889
|
-
const action = subArgs[0];
|
|
1890
|
-
const port = api ? void 0 : await ensureDaemon(resolvePort(subArgs));
|
|
1891
|
-
const client = api || new ApiClient(port);
|
|
1892
|
-
if (action === "on" || action === "off") {
|
|
1893
|
-
const result = await client.sendCommand("review", { action });
|
|
1894
|
-
const state = result.settings?.review;
|
|
1895
|
-
console.log(`Review mode: ${state?.enabled ? "ON" : "OFF"}`);
|
|
1896
|
-
} else {
|
|
1897
|
-
const settings = await client.getSettings();
|
|
1898
|
-
const review = settings.review;
|
|
1899
|
-
console.log(`Review mode: ${review?.enabled ? "ON" : "OFF"}`);
|
|
1900
|
-
console.log("Usage: ordewell review [on|off]");
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
1875
|
// src/commands/verify.ts
|
|
1905
1876
|
async function handleVerify(subArgs, api) {
|
|
1906
1877
|
const action = subArgs[0];
|
|
@@ -1918,23 +1889,6 @@ async function handleVerify(subArgs, api) {
|
|
|
1918
1889
|
}
|
|
1919
1890
|
}
|
|
1920
1891
|
|
|
1921
|
-
// src/commands/researchSubagents.ts
|
|
1922
|
-
async function handleResearchSubagents(subArgs, api) {
|
|
1923
|
-
const action = subArgs[0];
|
|
1924
|
-
const port = api ? void 0 : await ensureDaemon();
|
|
1925
|
-
const client = api || new ApiClient(port);
|
|
1926
|
-
if (action === "on" || action === "off") {
|
|
1927
|
-
const result = await client.sendCommand("research-subagents", { action });
|
|
1928
|
-
const state = result.settings?.researchSubagents;
|
|
1929
|
-
console.log(`Research subagents: ${state?.enabled ? "ON" : "OFF"}`);
|
|
1930
|
-
} else {
|
|
1931
|
-
const settings = await client.getSettings();
|
|
1932
|
-
const researchSubagents = settings.researchSubagents;
|
|
1933
|
-
console.log(`Research subagents: ${researchSubagents?.enabled ? "ON" : "OFF"}`);
|
|
1934
|
-
console.log("Usage: ordewell research-subagents [on|off]");
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
1892
|
// src/commands/task-control.ts
|
|
1939
1893
|
var PAST_TENSE = {
|
|
1940
1894
|
run: "Task started.",
|
|
@@ -1943,11 +1897,12 @@ var PAST_TENSE = {
|
|
|
1943
1897
|
cancel: "Task cancelled."
|
|
1944
1898
|
};
|
|
1945
1899
|
async function withResolvedTask(subArgs, usage, injectedApi, run) {
|
|
1946
|
-
const
|
|
1900
|
+
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
1947
1901
|
let sessionId = flag(subArgs, "--session-id");
|
|
1948
1902
|
if (!sessionId) {
|
|
1903
|
+
const last = readLastSession(workspace);
|
|
1949
1904
|
if (!last) {
|
|
1950
|
-
console.error(
|
|
1905
|
+
console.error(`No session specified. Use --session-id <id> or run \`ordewell plan\` in ${workspace} first.`);
|
|
1951
1906
|
process.exit(1);
|
|
1952
1907
|
}
|
|
1953
1908
|
sessionId = last.sessionId;
|
|
@@ -1958,22 +1913,12 @@ async function withResolvedTask(subArgs, usage, injectedApi, run) {
|
|
|
1958
1913
|
process.exit(1);
|
|
1959
1914
|
}
|
|
1960
1915
|
const api = injectedApi || new ApiClient(await ensureDaemon(resolvePort(subArgs)));
|
|
1961
|
-
const explicitWorkspace = flag(subArgs, "--workspace");
|
|
1962
1916
|
let plan;
|
|
1963
1917
|
try {
|
|
1964
|
-
plan = (await api.getSession(sessionId,
|
|
1918
|
+
plan = (await api.getSession(sessionId, workspace)).plan;
|
|
1965
1919
|
} catch (err) {
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
plan = (await api.getSession(sessionId, last.workspace)).plan;
|
|
1969
|
-
} catch {
|
|
1970
|
-
console.error(`Failed to load session: ${err.message}`);
|
|
1971
|
-
process.exit(1);
|
|
1972
|
-
}
|
|
1973
|
-
} else {
|
|
1974
|
-
console.error(`Failed to load session: ${err.message}`);
|
|
1975
|
-
process.exit(1);
|
|
1976
|
-
}
|
|
1920
|
+
console.error(`Failed to load session: ${err.message}`);
|
|
1921
|
+
process.exit(1);
|
|
1977
1922
|
}
|
|
1978
1923
|
const taskId = resolveTaskId(plan, identifier);
|
|
1979
1924
|
if (!taskId) {
|
|
@@ -1986,7 +1931,7 @@ function makeHandler(action, command = action) {
|
|
|
1986
1931
|
return async function handle(subArgs, injectedApi) {
|
|
1987
1932
|
await withResolvedTask(
|
|
1988
1933
|
subArgs,
|
|
1989
|
-
`Usage: ordewell ${command} <task-id-or-order> [--session-id <id>]`,
|
|
1934
|
+
`Usage: ordewell ${command} <task-id-or-order> [--session-id <id>] [--workspace /path]`,
|
|
1990
1935
|
injectedApi,
|
|
1991
1936
|
async (api, sessionId, taskId) => {
|
|
1992
1937
|
try {
|
|
@@ -2059,14 +2004,15 @@ async function handleSkip(subArgs, api) {
|
|
|
2059
2004
|
async function handleAddTask(subArgs, injectedApi) {
|
|
2060
2005
|
const title = flag(subArgs, "--title");
|
|
2061
2006
|
if (!title) {
|
|
2062
|
-
console.error('Usage: ordewell add-task --title "..." [--description "..."] [--prompt "..."] [--type ai|user] [--depends-on <task-id> ...] [--session-id <id>]');
|
|
2007
|
+
console.error('Usage: ordewell add-task --title "..." [--description "..."] [--prompt "..."] [--type ai|user] [--depends-on <task-id> ...] [--session-id <id>] [--workspace /path]');
|
|
2063
2008
|
process.exit(1);
|
|
2064
2009
|
}
|
|
2065
2010
|
let sessionId = flag(subArgs, "--session-id");
|
|
2066
2011
|
if (!sessionId) {
|
|
2067
|
-
const
|
|
2012
|
+
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
2013
|
+
const last = readLastSession(workspace);
|
|
2068
2014
|
if (!last) {
|
|
2069
|
-
console.error(
|
|
2015
|
+
console.error(`No session specified. Use --session-id <id> or run \`ordewell plan\` in ${workspace} first.`);
|
|
2070
2016
|
process.exit(1);
|
|
2071
2017
|
}
|
|
2072
2018
|
sessionId = last.sessionId;
|
|
@@ -2096,7 +2042,7 @@ async function handleAddTask(subArgs, injectedApi) {
|
|
|
2096
2042
|
async function handleRemoveTask(subArgs, api) {
|
|
2097
2043
|
await withResolvedTask(
|
|
2098
2044
|
subArgs,
|
|
2099
|
-
"Usage: ordewell remove-task <task-id-or-order> [--session-id <id>]",
|
|
2045
|
+
"Usage: ordewell remove-task <task-id-or-order> [--session-id <id>] [--workspace /path]",
|
|
2100
2046
|
api,
|
|
2101
2047
|
async (client, sessionId, taskId) => {
|
|
2102
2048
|
try {
|
|
@@ -2127,7 +2073,7 @@ async function handleSessions(subArgs, injectedApi) {
|
|
|
2127
2073
|
console.log(`No sessions found in ${workspace}.`);
|
|
2128
2074
|
return;
|
|
2129
2075
|
}
|
|
2130
|
-
const last = readLastSession();
|
|
2076
|
+
const last = readLastSession(workspace);
|
|
2131
2077
|
console.log(`${sessions.length} session(s) in ${workspace}:
|
|
2132
2078
|
`);
|
|
2133
2079
|
for (const s of sessions) {
|
|
@@ -2177,7 +2123,7 @@ async function handleSessions(subArgs, injectedApi) {
|
|
|
2177
2123
|
}
|
|
2178
2124
|
|
|
2179
2125
|
// src/commands/planner.ts
|
|
2180
|
-
var
|
|
2126
|
+
var import_core11 = require("@ordewell/core");
|
|
2181
2127
|
|
|
2182
2128
|
// src/plannerModelSwitch.ts
|
|
2183
2129
|
function describePlannerSwitch(settings, provider) {
|
|
@@ -2196,7 +2142,7 @@ function rememberedModelFor(settings, provider) {
|
|
|
2196
2142
|
}
|
|
2197
2143
|
|
|
2198
2144
|
// src/commands/planner.ts
|
|
2199
|
-
var KNOWN_PROVIDERS = Object.keys(
|
|
2145
|
+
var KNOWN_PROVIDERS = Object.keys(import_core11.ALL_PROVIDERS);
|
|
2200
2146
|
async function handlePlanner(subArgs, injectedApi) {
|
|
2201
2147
|
const api = await connect(subArgs, injectedApi);
|
|
2202
2148
|
const [wanted] = positionals(subArgs);
|
|
@@ -2211,13 +2157,13 @@ async function handlePlanner(subArgs, injectedApi) {
|
|
|
2211
2157
|
const settings = await persistEnv(api, { AI_PROVIDER: provider });
|
|
2212
2158
|
const recall2 = describePlannerSwitch(settings, provider);
|
|
2213
2159
|
writeEnv({ ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
|
|
2214
|
-
const meta =
|
|
2160
|
+
const meta = import_core11.ALL_PROVIDERS[provider];
|
|
2215
2161
|
const catalog = await fetchCatalog(api);
|
|
2216
2162
|
console.log(
|
|
2217
|
-
(0,
|
|
2163
|
+
(0, import_core11.isCliProvider)(provider) ? `Planning with ${meta.label} \u2014 no API key needed, it uses that agent's own subscription.` : `Planner set to ${meta.label}.`
|
|
2218
2164
|
);
|
|
2219
2165
|
console.log(plannerModelLine(recall2));
|
|
2220
|
-
if (!(0,
|
|
2166
|
+
if (!(0, import_core11.isCliProvider)(provider) && !catalog.providers.includes(provider)) {
|
|
2221
2167
|
console.log(` No ${meta.apiKeyEnvVar} configured yet \u2014 set one with \`ordewell key set ${provider} <key>\`.`);
|
|
2222
2168
|
}
|
|
2223
2169
|
}
|
|
@@ -2235,15 +2181,15 @@ async function showPlanners(api) {
|
|
|
2235
2181
|
const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
|
|
2236
2182
|
const current = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
2237
2183
|
console.log("\nCoding agents (no API key \u2014 they use their own subscription)");
|
|
2238
|
-
for (const id of
|
|
2239
|
-
const runner = (0,
|
|
2184
|
+
for (const id of import_core11.CLI_PROVIDERS) {
|
|
2185
|
+
const runner = (0, import_core11.runnerForProvider)(id);
|
|
2240
2186
|
const discovered = runner ? catalog.models.some((m) => m.runners?.includes(runner)) : false;
|
|
2241
|
-
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${
|
|
2187
|
+
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${import_core11.ALL_PROVIDERS[id].label}${discovered ? "" : " (not detected)"}`);
|
|
2242
2188
|
}
|
|
2243
2189
|
console.log("\nAPI providers");
|
|
2244
|
-
for (const id of
|
|
2190
|
+
for (const id of import_core11.PROVIDER_PRIORITY) {
|
|
2245
2191
|
const configured = catalog.providers.includes(id);
|
|
2246
|
-
const meta =
|
|
2192
|
+
const meta = import_core11.ALL_PROVIDERS[id];
|
|
2247
2193
|
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${meta.label}${configured ? "" : ` (no ${meta.apiKeyEnvVar})`}`);
|
|
2248
2194
|
}
|
|
2249
2195
|
console.log(`
|
|
@@ -2260,7 +2206,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
|
|
|
2260
2206
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
2261
2207
|
const modelId = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
|
|
2262
2208
|
const currentEffort = typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : "";
|
|
2263
|
-
const runner = (0,
|
|
2209
|
+
const runner = (0, import_core11.runnerForProvider)(provider);
|
|
2264
2210
|
if (!runner) {
|
|
2265
2211
|
fail(
|
|
2266
2212
|
"Thinking effort applies to a coding-agent planner.",
|
|
@@ -2300,7 +2246,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
|
|
|
2300
2246
|
}
|
|
2301
2247
|
|
|
2302
2248
|
// src/commands/key.ts
|
|
2303
|
-
var
|
|
2249
|
+
var import_core12 = require("@ordewell/core");
|
|
2304
2250
|
var USAGE4 = "Usage: ordewell key [list | set <provider> <key>]";
|
|
2305
2251
|
async function handleKey(subArgs, injectedApi) {
|
|
2306
2252
|
const [action, provider, ...keyParts] = positionals(subArgs);
|
|
@@ -2311,8 +2257,8 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
2311
2257
|
if (action !== "set") {
|
|
2312
2258
|
const catalog = await fetchCatalog(api);
|
|
2313
2259
|
console.log("\nAPI provider keys\n");
|
|
2314
|
-
for (const id2 of
|
|
2315
|
-
const meta2 =
|
|
2260
|
+
for (const id2 of import_core12.PROVIDER_PRIORITY) {
|
|
2261
|
+
const meta2 = import_core12.ALL_PROVIDERS[id2];
|
|
2316
2262
|
const configured = catalog.providers.includes(id2);
|
|
2317
2263
|
console.log(` ${configured ? "\u2713" : " "} ${id2.padEnd(14)} ${meta2.apiKeyEnvVar.padEnd(28)} ${meta2.label}`);
|
|
2318
2264
|
}
|
|
@@ -2322,7 +2268,7 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
2322
2268
|
}
|
|
2323
2269
|
if (!provider) fail(USAGE4);
|
|
2324
2270
|
const id = provider.toLowerCase();
|
|
2325
|
-
const meta =
|
|
2271
|
+
const meta = import_core12.ALL_PROVIDERS[id];
|
|
2326
2272
|
if (!meta) {
|
|
2327
2273
|
fail(`Unknown provider: ${provider}`, "Run `ordewell key` to see the list.");
|
|
2328
2274
|
}
|
|
@@ -2402,7 +2348,7 @@ async function handleApprove(subArgs, injectedApi) {
|
|
|
2402
2348
|
// src/tui/terminalLauncher.ts
|
|
2403
2349
|
var import_child_process3 = require("child_process");
|
|
2404
2350
|
var import_util = require("util");
|
|
2405
|
-
var
|
|
2351
|
+
var import_core13 = require("@ordewell/core");
|
|
2406
2352
|
var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
|
|
2407
2353
|
var defaultExecFile = async (command, args) => {
|
|
2408
2354
|
const { stdout, stderr } = await execFileAsync(command, args);
|
|
@@ -2422,16 +2368,16 @@ var defaultWhich = (bin) => {
|
|
|
2422
2368
|
async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
|
|
2423
2369
|
const platform = deps.platform ?? process.platform;
|
|
2424
2370
|
const env = deps.env ?? process.env;
|
|
2425
|
-
const hasTmuxImpl = deps.hasTmuxImpl ??
|
|
2371
|
+
const hasTmuxImpl = deps.hasTmuxImpl ?? import_core13.hasTmux;
|
|
2426
2372
|
const execFileImpl = deps.execFileImpl ?? defaultExecFile;
|
|
2427
2373
|
const spawnImpl = deps.spawnImpl ?? defaultSpawn;
|
|
2428
2374
|
const which = deps.which ?? defaultWhich;
|
|
2429
2375
|
if (!hasTmuxImpl()) {
|
|
2430
2376
|
return { ok: false, message: "tmux is not installed \u2014 install it to get a real terminal per task." };
|
|
2431
2377
|
}
|
|
2432
|
-
const session = (0,
|
|
2433
|
-
const socket = (0,
|
|
2434
|
-
const base = (0,
|
|
2378
|
+
const session = (0, import_core13.tmuxSessionName)(port);
|
|
2379
|
+
const socket = (0, import_core13.tmuxSocketName)(port);
|
|
2380
|
+
const base = (0, import_core13.tmuxWindowName)(taskId, planSessionId);
|
|
2435
2381
|
let windows;
|
|
2436
2382
|
try {
|
|
2437
2383
|
const { stdout } = await execFileImpl("tmux", ["-L", socket, "list-windows", "-t", session, "-F", "#{window_name}"]);
|
|
@@ -2725,7 +2671,7 @@ Dependencies \xB7 #${task.order} ${task.title}
|
|
|
2725
2671
|
}
|
|
2726
2672
|
|
|
2727
2673
|
// src/tui/index.ts
|
|
2728
|
-
var
|
|
2674
|
+
var import_core17 = require("@ordewell/core");
|
|
2729
2675
|
|
|
2730
2676
|
// src/tui/ansi.ts
|
|
2731
2677
|
var import_string_width = __toESM(require("string-width"));
|
|
@@ -3030,12 +2976,8 @@ var SLASH_COMMANDS = [
|
|
|
3030
2976
|
{ name: "auto", usage: "/auto [on|off]", description: "Autonomous permission mode for new plans", category: "models" },
|
|
3031
2977
|
{ name: "refresh", usage: "/refresh", description: "Re-discover runners and model catalogs", category: "models" },
|
|
3032
2978
|
// Skills
|
|
3033
|
-
{ name: "grill-me", usage: "/grill-me [on|off]", description: "Grill-Me challenge mode \u2014 the planner interrogates the goal", category: "skills" },
|
|
3034
2979
|
{ name: "tdd", usage: "/tdd [on|off]", description: "Test-Driven Development mode", category: "skills" },
|
|
3035
|
-
{ name: "prd", usage: "/prd [on|off]", description: "PRD mode \u2014 write a PRD before the plan", category: "skills" },
|
|
3036
|
-
{ name: "review", usage: "/review [on|off]", description: "Review mode \u2014 final opinion-review task with human sign-off", category: "skills" },
|
|
3037
2980
|
{ name: "verify", usage: "/verify [on|off]", description: "Verification mode \u2014 final evidence-based task that runs the suite", category: "skills" },
|
|
3038
|
-
{ name: "research-subagents", usage: "/research-subagents [on|off]", description: "Parallel read-only research subagents during planning", category: "skills" },
|
|
3039
2981
|
// Sessions
|
|
3040
2982
|
{ name: "sessions", usage: "/sessions", description: "List saved sessions", category: "session" },
|
|
3041
2983
|
{ name: "new", usage: "/new", description: "Start a new session", category: "session" },
|
|
@@ -3048,6 +2990,23 @@ var SLASH_COMMANDS = [
|
|
|
3048
2990
|
{ name: "quit", usage: "/quit", description: "Exit the TUI", category: "system" }
|
|
3049
2991
|
];
|
|
3050
2992
|
var BY_NAME = new Map(SLASH_COMMANDS.map((c) => [c.name, c]));
|
|
2993
|
+
var skillCommands = [];
|
|
2994
|
+
var skillByName = /* @__PURE__ */ new Map();
|
|
2995
|
+
function registerSkillCommands(skills) {
|
|
2996
|
+
const map = /* @__PURE__ */ new Map();
|
|
2997
|
+
for (const skill of skills) {
|
|
2998
|
+
if (BY_NAME.has(skill.name)) continue;
|
|
2999
|
+
map.set(skill.name, {
|
|
3000
|
+
name: skill.name,
|
|
3001
|
+
usage: `/${skill.name}`,
|
|
3002
|
+
description: skill.description,
|
|
3003
|
+
category: "skills",
|
|
3004
|
+
source: "skill"
|
|
3005
|
+
});
|
|
3006
|
+
}
|
|
3007
|
+
skillByName = map;
|
|
3008
|
+
skillCommands = [...map.values()];
|
|
3009
|
+
}
|
|
3051
3010
|
function parseSlash(text) {
|
|
3052
3011
|
const trimmed = text.trim();
|
|
3053
3012
|
if (!trimmed.startsWith("/")) return null;
|
|
@@ -3056,14 +3015,15 @@ function parseSlash(text) {
|
|
|
3056
3015
|
return { name: parts[0].toLowerCase(), args: parts.slice(1) };
|
|
3057
3016
|
}
|
|
3058
3017
|
function findCommand(name) {
|
|
3059
|
-
|
|
3018
|
+
const key = name.replace(/^\//, "").toLowerCase();
|
|
3019
|
+
return BY_NAME.get(key) ?? skillByName.get(key);
|
|
3060
3020
|
}
|
|
3061
3021
|
function completions(text) {
|
|
3062
3022
|
if (!text.startsWith("/")) return [];
|
|
3063
3023
|
const typed = text.slice(1);
|
|
3064
3024
|
if (/\s/.test(typed)) return [];
|
|
3065
3025
|
const prefix = typed.toLowerCase();
|
|
3066
|
-
return SLASH_COMMANDS.filter((c) => c.name.startsWith(prefix));
|
|
3026
|
+
return [...SLASH_COMMANDS, ...skillCommands].filter((c) => c.name.startsWith(prefix));
|
|
3067
3027
|
}
|
|
3068
3028
|
|
|
3069
3029
|
// src/tui/markdown.ts
|
|
@@ -3208,9 +3168,9 @@ function chatEditorRoom(cols, active) {
|
|
|
3208
3168
|
}
|
|
3209
3169
|
|
|
3210
3170
|
// src/tui/state.ts
|
|
3211
|
-
var SKILL_IDS = ["
|
|
3171
|
+
var SKILL_IDS = ["tdd", "verify"];
|
|
3212
3172
|
function noSkills() {
|
|
3213
|
-
return {
|
|
3173
|
+
return { tdd: false, verify: false };
|
|
3214
3174
|
}
|
|
3215
3175
|
function visibleItems(picker2) {
|
|
3216
3176
|
const filter = picker2.filter.trim().toLowerCase();
|
|
@@ -3221,6 +3181,21 @@ function visibleItems(picker2) {
|
|
|
3221
3181
|
}
|
|
3222
3182
|
var isTaskRunning = (task) => task.status === "in_progress" || task.status === "running";
|
|
3223
3183
|
var anyTaskRunning = (state) => state.tasks.some(isTaskRunning);
|
|
3184
|
+
function planRows(state) {
|
|
3185
|
+
const rows = [];
|
|
3186
|
+
for (const task of state.tasks) {
|
|
3187
|
+
rows.push({ task, parent: null });
|
|
3188
|
+
const subtasks = task.subtasks ?? [];
|
|
3189
|
+
const open = state.expandedTaskId === task.id || subtasks.some((s) => s.id === state.expandedTaskId);
|
|
3190
|
+
if (open) {
|
|
3191
|
+
for (const subtask of subtasks) rows.push({ task: subtask, parent: task });
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
return rows;
|
|
3195
|
+
}
|
|
3196
|
+
function selectedPlanRow(state) {
|
|
3197
|
+
return planRows(state)[state.selectedTask] ?? null;
|
|
3198
|
+
}
|
|
3224
3199
|
function initialState(overrides = {}) {
|
|
3225
3200
|
return {
|
|
3226
3201
|
editor: emptyEditor(),
|
|
@@ -3266,7 +3241,7 @@ function initialState(overrides = {}) {
|
|
|
3266
3241
|
}
|
|
3267
3242
|
|
|
3268
3243
|
// src/tui/layout.ts
|
|
3269
|
-
var
|
|
3244
|
+
var import_core14 = require("@ordewell/core");
|
|
3270
3245
|
var CHROME_ROWS = 4;
|
|
3271
3246
|
function chatInputWrap(state) {
|
|
3272
3247
|
const active = state.focus === "chat" && !state.overlay;
|
|
@@ -3274,7 +3249,7 @@ function chatInputWrap(state) {
|
|
|
3274
3249
|
return wrapLines(state.editor.text, chatEditorRoom(state.cols, true));
|
|
3275
3250
|
}
|
|
3276
3251
|
function footerHints(state) {
|
|
3277
|
-
const markHint = state.
|
|
3252
|
+
const markHint = selectedPlanRow(state)?.task.status === "completed" ? "m undone" : "m done";
|
|
3278
3253
|
const planning = state.status === "planning" || state.status === "researching";
|
|
3279
3254
|
const escHint = planning ? "esc stop planning" : null;
|
|
3280
3255
|
if (state.focus === "plan") {
|
|
@@ -3402,9 +3377,9 @@ function chatScrollMax(state) {
|
|
|
3402
3377
|
}
|
|
3403
3378
|
function plannerSetup(state) {
|
|
3404
3379
|
const provider = state.plannerProvider;
|
|
3405
|
-
const registration =
|
|
3380
|
+
const registration = import_core14.ALL_PROVIDERS[provider];
|
|
3406
3381
|
if (!registration) return { label: "not set", ready: false };
|
|
3407
|
-
const runner = (0,
|
|
3382
|
+
const runner = (0, import_core14.runnerForProvider)(provider);
|
|
3408
3383
|
const ready = runner ? state.runners.some((r) => r.id === runner) : state.configuredProviders.includes(provider);
|
|
3409
3384
|
return { label: registration.label, ready };
|
|
3410
3385
|
}
|
|
@@ -3511,16 +3486,17 @@ var STATUS_ICON = {
|
|
|
3511
3486
|
approved: "\xB7",
|
|
3512
3487
|
pending: "\xB7"
|
|
3513
3488
|
};
|
|
3489
|
+
var IDLE_ICON = "~";
|
|
3514
3490
|
function planLayout(state, rows, cols) {
|
|
3515
3491
|
const done = state.tasks.filter((t) => t.status === "completed").length;
|
|
3516
3492
|
const lines = [style.bold(`Plan ${done}/${state.tasks.length}`), ""];
|
|
3517
3493
|
let selectedLine = 2;
|
|
3518
3494
|
let editorLine;
|
|
3519
|
-
state.
|
|
3495
|
+
planRows(state).forEach((row, i) => {
|
|
3520
3496
|
const taskStart = lines.length;
|
|
3521
3497
|
if (i === state.selectedTask) selectedLine = taskStart;
|
|
3522
|
-
const renderedTask = taskLines(state,
|
|
3523
|
-
if (task.id === state.expandedTaskId && renderedTask.editorLine !== void 0) {
|
|
3498
|
+
const renderedTask = taskLines(state, row, i, cols);
|
|
3499
|
+
if (row.task.id === state.expandedTaskId && renderedTask.editorLine !== void 0) {
|
|
3524
3500
|
editorLine = taskStart + renderedTask.editorLine;
|
|
3525
3501
|
}
|
|
3526
3502
|
lines.push(...renderedTask.lines);
|
|
@@ -3539,16 +3515,20 @@ function planScrollExtent(state) {
|
|
|
3539
3515
|
const { followOffset, maxScroll } = planLayout(state, bodyRows(state), cols);
|
|
3540
3516
|
return { followOffset, maxScroll };
|
|
3541
3517
|
}
|
|
3542
|
-
function taskLines(state,
|
|
3518
|
+
function taskLines(state, row, index, cols) {
|
|
3519
|
+
const { task, parent } = row;
|
|
3543
3520
|
const selected = state.focus === "plan" && index === state.selectedTask;
|
|
3544
3521
|
const expanded = state.expandedTaskId === task.id;
|
|
3545
3522
|
const running = isTaskRunning(task);
|
|
3546
|
-
const
|
|
3547
|
-
const
|
|
3523
|
+
const idle = running && task.status === "in_progress" && task.idleSince != null;
|
|
3524
|
+
const paint = idle ? style.cyan : STATUS_MARK[task.status] ?? ((t) => t);
|
|
3525
|
+
const rawIcon = idle ? IDLE_ICON : running ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[task.status] ?? "\xB7";
|
|
3548
3526
|
const icon = paint(rawIcon);
|
|
3549
3527
|
const kind = running ? style.yellow("RUN") : task.type === "user" ? style.yellow("MAN") : style.grey(" AI");
|
|
3550
3528
|
const caret = selected ? style.cyan("\u276F") : " ";
|
|
3551
|
-
const
|
|
3529
|
+
const orderLabel = parent ? (0, import_core14.taskOrderLabel)(task, parent) : String(task.order).padStart(2);
|
|
3530
|
+
const indent = parent ? " " : "";
|
|
3531
|
+
const head = `${indent}${caret} ${icon} ${orderLabel} ${kind} `;
|
|
3552
3532
|
const titleRoom = Math.max(1, cols - width(head));
|
|
3553
3533
|
const wrappedTitle = wrap(task.title, titleRoom);
|
|
3554
3534
|
const shownTitle = expanded ? wrappedTitle : wrappedTitle.slice(0, 2);
|
|
@@ -3566,50 +3546,51 @@ function taskLines(state, task, index, cols) {
|
|
|
3566
3546
|
const modeInfo = task.type === "ai" ? modesForTask(state.modesByRunner, task).find((m) => m.id === task.taskMode) : void 0;
|
|
3567
3547
|
const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${modeInfo?.autonomous ? style.yellow(" \u26A1") : ""}` : "";
|
|
3568
3548
|
const runner = task.assignedRunner ?? "";
|
|
3549
|
+
const bodyPad = parent ? " " : " ";
|
|
3569
3550
|
const meta = [running ? "working" : "", runner, model].filter(Boolean).join(" \xB7 ");
|
|
3570
|
-
if (meta) lines.push(style.grey(truncate(
|
|
3571
|
-
if (effort || mode) lines.push(style.grey(truncate(
|
|
3551
|
+
if (meta) lines.push(style.grey(truncate(`${bodyPad}${meta}`, cols)));
|
|
3552
|
+
if (effort || mode) lines.push(style.grey(truncate(`${bodyPad}${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
|
|
3572
3553
|
let editorLine;
|
|
3573
3554
|
if (expanded) {
|
|
3574
|
-
lines.push(style.grey(
|
|
3555
|
+
lines.push(style.grey(`${bodyPad}${task.status.replace(/_/g, " ")}`));
|
|
3575
3556
|
if (modeInfo?.autonomous) {
|
|
3576
|
-
lines.push(...taskText("Autonomy", "Runs without permission prompts. Toggle with /auto.", cols));
|
|
3557
|
+
lines.push(...taskText("Autonomy", "Runs without permission prompts. Toggle with /auto.", cols, bodyPad));
|
|
3577
3558
|
}
|
|
3578
3559
|
const editableSeed = task.prompt ?? task.description ?? task.title;
|
|
3579
3560
|
if (task.description && task.description !== task.title && task.description !== editableSeed) {
|
|
3580
|
-
lines.push(...taskText("Description", task.description, cols));
|
|
3561
|
+
lines.push(...taskText("Description", task.description, cols, bodyPad));
|
|
3581
3562
|
}
|
|
3582
3563
|
if (state.taskEditor) {
|
|
3583
|
-
const room =
|
|
3564
|
+
const room = Math.max(1, cols - width(bodyPad));
|
|
3584
3565
|
const wrapped = wrapLines(state.taskEditor.text, room);
|
|
3585
3566
|
const cp = cursorInLines(wrapped, state.taskEditor.cursor, state.taskEditor.text.length);
|
|
3586
3567
|
editorLine = lines.length + 1 + cp.line;
|
|
3587
|
-
lines.push(...taskPromptLines(wrapped, cp));
|
|
3568
|
+
lines.push(...taskPromptLines(wrapped, cp, bodyPad));
|
|
3588
3569
|
}
|
|
3589
3570
|
if (task.dependencies.length > 0) {
|
|
3590
3571
|
const orderById = new Map(state.tasks.map((candidate) => [candidate.id, candidate.order]));
|
|
3591
3572
|
const dependencies = task.dependencies.map((id) => orderById.has(id) ? `#${orderById.get(id)}` : id).join(", ");
|
|
3592
|
-
lines.push(...taskText("Depends on", dependencies, cols));
|
|
3573
|
+
lines.push(...taskText("Depends on", dependencies, cols, bodyPad));
|
|
3593
3574
|
}
|
|
3594
|
-
lines.push(style.grey(
|
|
3595
|
-
lines.push(style.grey(truncate(
|
|
3575
|
+
lines.push(style.grey(`${bodyPad}enter save \xB7 esc cancel`));
|
|
3576
|
+
lines.push(style.grey(truncate(`${bodyPad}then R runner \xB7 o model \xB7 e effort \xB7 M mode \xB7 D deps`, cols)));
|
|
3596
3577
|
}
|
|
3597
3578
|
return { lines, editorLine };
|
|
3598
3579
|
}
|
|
3599
|
-
function taskText(label, text, cols) {
|
|
3600
|
-
const room =
|
|
3580
|
+
function taskText(label, text, cols, pad2 = " ") {
|
|
3581
|
+
const room = Math.max(1, cols - width(pad2));
|
|
3601
3582
|
return [
|
|
3602
|
-
style.grey(
|
|
3603
|
-
...wrap(text, room).map((line) =>
|
|
3583
|
+
style.grey(`${pad2}${label}`),
|
|
3584
|
+
...wrap(text, room).map((line) => `${pad2}${line}`)
|
|
3604
3585
|
];
|
|
3605
3586
|
}
|
|
3606
|
-
function taskPromptLines(wrapped, cursor) {
|
|
3587
|
+
function taskPromptLines(wrapped, cursor, pad2 = " ") {
|
|
3607
3588
|
return [
|
|
3608
|
-
style.grey(
|
|
3589
|
+
style.grey(`${pad2}Prompt`),
|
|
3609
3590
|
...wrapped.map(({ line }, i) => {
|
|
3610
|
-
if (i !== cursor.line) return
|
|
3591
|
+
if (i !== cursor.line) return `${pad2}${line}`;
|
|
3611
3592
|
const col = cursor.col;
|
|
3612
|
-
return
|
|
3593
|
+
return `${pad2}${line.slice(0, col)}${style.inverse(line.slice(col, col + 1) || " ")}${line.slice(col + 1)}`;
|
|
3613
3594
|
})
|
|
3614
3595
|
];
|
|
3615
3596
|
}
|
|
@@ -3762,15 +3743,29 @@ function renderStatus(state, cols) {
|
|
|
3762
3743
|
const tail = thinking.length > room ? `\u2026${thinking.slice(-(room - 1)).trimStart()}` : thinking;
|
|
3763
3744
|
return truncate(`${head}${style.grey(` \xB7 ${tail}`)}`, cols);
|
|
3764
3745
|
}
|
|
3746
|
+
var LEADING_TOKEN = /^\/\S*/;
|
|
3747
|
+
function skillTokenLength(text) {
|
|
3748
|
+
const match = LEADING_TOKEN.exec(text);
|
|
3749
|
+
if (!match) return 0;
|
|
3750
|
+
return findCommand(match[0])?.source === "skill" ? match[0].length : 0;
|
|
3751
|
+
}
|
|
3752
|
+
function paintRange(text, base, tokenEnd) {
|
|
3753
|
+
const localEnd = Math.max(0, Math.min(text.length, tokenEnd - base));
|
|
3754
|
+
if (localEnd <= 0) return text;
|
|
3755
|
+
if (localEnd >= text.length) return style.cyan(text);
|
|
3756
|
+
return style.cyan(text.slice(0, localEnd)) + text.slice(localEnd);
|
|
3757
|
+
}
|
|
3765
3758
|
function renderInput(state, cols) {
|
|
3766
3759
|
const prompt = state.focus === "plan" ? style.grey("\u276F ") : style.cyan("\u276F ");
|
|
3767
3760
|
const lines = chatInputWrap(state);
|
|
3768
3761
|
const room = chatEditorRoom(cols, lines !== null);
|
|
3769
3762
|
const { text, cursor } = state.editor;
|
|
3770
3763
|
const continuation = " ".repeat(width(prompt));
|
|
3764
|
+
const tokenEnd = skillTokenLength(text);
|
|
3771
3765
|
if (!lines) {
|
|
3772
3766
|
const visible = text.replace(/\n/g, "\xB6").replace(/\t/g, " ");
|
|
3773
|
-
|
|
3767
|
+
const painted = tokenEnd > 0 ? paintRange(visible, 0, tokenEnd) : visible;
|
|
3768
|
+
return [prompt + painted + suggest(state, room - width(visible))];
|
|
3774
3769
|
}
|
|
3775
3770
|
const { line: lineIndex, col } = cursorInLines(lines, cursor, text.length);
|
|
3776
3771
|
const rawLines = lines.map((l) => l.line);
|
|
@@ -3778,7 +3773,8 @@ function renderInput(state, cols) {
|
|
|
3778
3773
|
const prefix = i === 0 ? prompt : continuation;
|
|
3779
3774
|
const line = raw.replace(/\t/g, " ");
|
|
3780
3775
|
const isCursorLine = i === lineIndex;
|
|
3781
|
-
const
|
|
3776
|
+
const lineTokenEnd = i === 0 ? tokenEnd : 0;
|
|
3777
|
+
const rendered = isCursorLine ? paintRange(line.slice(0, col), 0, lineTokenEnd) + style.inverse(line.slice(col, col + 1) || " ") + paintRange(line.slice(col + 1), col + 1, lineTokenEnd) : paintRange(line, 0, lineTokenEnd);
|
|
3782
3778
|
const used = width(line) + (isCursorLine && col >= line.length ? 1 : 0);
|
|
3783
3779
|
const hint = i === rawLines.length - 1 ? suggest(state, room - used) : "";
|
|
3784
3780
|
return prefix + rendered + hint;
|
|
@@ -3908,15 +3904,21 @@ function frame(title, body, rows, cols) {
|
|
|
3908
3904
|
}
|
|
3909
3905
|
|
|
3910
3906
|
// src/tui/reducer.ts
|
|
3911
|
-
var
|
|
3907
|
+
var import_core15 = require("@ordewell/core");
|
|
3912
3908
|
var import_plan_utils3 = require("@ordewell/core/plan-utils");
|
|
3913
|
-
var KNOWN_PROVIDERS2 = Object.keys(
|
|
3909
|
+
var KNOWN_PROVIDERS2 = Object.keys(import_core15.ALL_PROVIDERS);
|
|
3914
3910
|
var DEFAULT_EFFORT = "__runner_default__";
|
|
3915
3911
|
function step(state, effects = []) {
|
|
3916
3912
|
return { state, effects };
|
|
3917
3913
|
}
|
|
3918
|
-
function say(state, role, content, research) {
|
|
3919
|
-
const message = {
|
|
3914
|
+
function say(state, role, content, research, extra) {
|
|
3915
|
+
const message = {
|
|
3916
|
+
role,
|
|
3917
|
+
content: sanitize(content),
|
|
3918
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3919
|
+
...research ? { research } : {},
|
|
3920
|
+
...extra
|
|
3921
|
+
};
|
|
3920
3922
|
return { ...state, messages: [...state.messages, message], scroll: 0 };
|
|
3921
3923
|
}
|
|
3922
3924
|
function restoredMessages(history) {
|
|
@@ -3986,24 +3988,42 @@ function reduce(state, action) {
|
|
|
3986
3988
|
case "planUpdated": {
|
|
3987
3989
|
if (stale(state, action.sessionId)) return step(state);
|
|
3988
3990
|
const tasks = normalizeTasks(action.plan);
|
|
3989
|
-
|
|
3991
|
+
const expandedTaskId = tasks.some((task) => task.id === state.expandedTaskId) ? state.expandedTaskId : null;
|
|
3992
|
+
const next = {
|
|
3990
3993
|
...state,
|
|
3991
3994
|
tasks,
|
|
3992
3995
|
status: state.status === "planning" || state.status === "researching" ? "idle" : state.status,
|
|
3993
3996
|
busyLabel: "",
|
|
3994
3997
|
thinkingLine: "",
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
});
|
|
3998
|
+
expandedTaskId,
|
|
3999
|
+
taskEditor: expandedTaskId !== null ? state.taskEditor : null
|
|
4000
|
+
};
|
|
4001
|
+
return step({ ...next, selectedTask: Math.min(state.selectedTask, Math.max(0, planRows(next).length - 1)) });
|
|
3999
4002
|
}
|
|
4000
4003
|
case "plannerMessage": {
|
|
4001
4004
|
if (stale(state, action.sessionId)) return step(state);
|
|
4002
4005
|
const settled = { ...state, status: "idle", busyLabel: "", thinkingLine: "" };
|
|
4003
4006
|
const content = sanitize(action.content);
|
|
4007
|
+
const last = state.messages.at(-1);
|
|
4008
|
+
if (last?.role === "assistant" && last.streaming) {
|
|
4009
|
+
const messages = [...state.messages];
|
|
4010
|
+
messages[messages.length - 1] = { ...last, content, streaming: void 0 };
|
|
4011
|
+
return step({ ...settled, messages });
|
|
4012
|
+
}
|
|
4004
4013
|
if (alreadySpoken(state.messages, content)) return step(settled);
|
|
4005
4014
|
return step(say(settled, "assistant", content));
|
|
4006
4015
|
}
|
|
4016
|
+
case "plannerToken": {
|
|
4017
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
4018
|
+
const text = sanitize(action.text);
|
|
4019
|
+
const last = state.messages.at(-1);
|
|
4020
|
+
if (last?.role === "assistant" && last.streaming) {
|
|
4021
|
+
const messages = [...state.messages];
|
|
4022
|
+
messages[messages.length - 1] = { ...last, content: last.content + text };
|
|
4023
|
+
return step({ ...state, messages });
|
|
4024
|
+
}
|
|
4025
|
+
return step(say(state, "assistant", text, void 0, { streaming: true }));
|
|
4026
|
+
}
|
|
4007
4027
|
case "researchStep": {
|
|
4008
4028
|
if (stale(state, action.sessionId)) return step(state);
|
|
4009
4029
|
const summary = sanitize(action.summary);
|
|
@@ -4040,10 +4060,12 @@ function reduce(state, action) {
|
|
|
4040
4060
|
const updates = action.updates;
|
|
4041
4061
|
let changed = false;
|
|
4042
4062
|
const tasks = state.tasks.map((t) => {
|
|
4043
|
-
const
|
|
4044
|
-
if (
|
|
4063
|
+
const update = updates[t.id];
|
|
4064
|
+
if (update === void 0) return t;
|
|
4065
|
+
const idleSince = update.idleSince ?? null;
|
|
4066
|
+
if (update.status !== t.status || idleSince !== (t.idleSince ?? null)) {
|
|
4045
4067
|
changed = true;
|
|
4046
|
-
return { ...t, status };
|
|
4068
|
+
return { ...t, status: update.status, idleSince };
|
|
4047
4069
|
}
|
|
4048
4070
|
return t;
|
|
4049
4071
|
});
|
|
@@ -4125,9 +4147,12 @@ function normalizeTasks(plan) {
|
|
|
4125
4147
|
}
|
|
4126
4148
|
}
|
|
4127
4149
|
const raw = byId.size > 0 ? [...byId.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) : source?.tasks ?? [];
|
|
4128
|
-
return raw.map((t, i) => (
|
|
4129
|
-
|
|
4130
|
-
|
|
4150
|
+
return raw.map((t, i) => toTaskView2(t, i));
|
|
4151
|
+
}
|
|
4152
|
+
function toTaskView2(t, index) {
|
|
4153
|
+
return {
|
|
4154
|
+
id: String(t.id ?? `task-${index + 1}`),
|
|
4155
|
+
order: typeof t.order === "number" ? t.order : index + 1,
|
|
4131
4156
|
title: planLabel(t.title, "Untitled task"),
|
|
4132
4157
|
description: planText(t.description ?? t.title),
|
|
4133
4158
|
prompt: typeof t.prompt === "string" ? sanitize(t.prompt) : void 0,
|
|
@@ -4141,18 +4166,15 @@ function normalizeTasks(plan) {
|
|
|
4141
4166
|
modelLabel: planLabel(t.assignedModel.modelLabel ?? t.assignedModel.modelId),
|
|
4142
4167
|
thinkingEffort: typeof t.assignedModel.thinkingEffort === "string" ? planLabel(t.assignedModel.thinkingEffort) : typeof t.thinkingEffort === "string" ? planLabel(t.thinkingEffort) : void 0,
|
|
4143
4168
|
availableVariants: Array.isArray(t.assignedModel.availableVariants) ? t.assignedModel.availableVariants.map((variant) => planLabel(variant)) : void 0
|
|
4144
|
-
} : void 0
|
|
4145
|
-
|
|
4169
|
+
} : void 0,
|
|
4170
|
+
subtasks: Array.isArray(t.subtasks) ? [...t.subtasks].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)).map((s, i) => toTaskView2(s, i)) : void 0
|
|
4171
|
+
};
|
|
4146
4172
|
}
|
|
4147
4173
|
var enabledFlag = (value) => typeof value?.enabled === "boolean" ? value.enabled : void 0;
|
|
4148
4174
|
function applySettings(state, settings) {
|
|
4149
4175
|
const sources = {
|
|
4150
|
-
"grill-me": settings.grillMe,
|
|
4151
4176
|
tdd: settings.tdd,
|
|
4152
|
-
|
|
4153
|
-
review: settings.review,
|
|
4154
|
-
verify: settings.verification,
|
|
4155
|
-
"research-subagents": settings.researchSubagents
|
|
4177
|
+
verify: settings.verification
|
|
4156
4178
|
};
|
|
4157
4179
|
const skills = { ...state.skills };
|
|
4158
4180
|
for (const id of SKILL_IDS) {
|
|
@@ -4181,11 +4203,11 @@ function modelDetail(m) {
|
|
|
4181
4203
|
function providerErrorHint(state) {
|
|
4182
4204
|
const failed = Object.keys(state.providerErrors ?? {});
|
|
4183
4205
|
if (failed.length === 0) return void 0;
|
|
4184
|
-
const names = failed.map((p) =>
|
|
4206
|
+
const names = failed.map((p) => import_core15.ALL_PROVIDERS[p]?.label ?? p);
|
|
4185
4207
|
return `\u26A0 Unavailable (key or fetch failed): ${names.join(", ")} \u2014 showing working providers only.`;
|
|
4186
4208
|
}
|
|
4187
4209
|
function plannerModelItems(state) {
|
|
4188
|
-
const runner = (0,
|
|
4210
|
+
const runner = (0, import_core15.runnerForProvider)(state.plannerProvider);
|
|
4189
4211
|
if (!runner) return state.orchestratorModels.map((m) => ({ id: m.id, label: m.label, detail: modelDetail(m) }));
|
|
4190
4212
|
const items = state.models.filter((m) => m.runners?.includes(runner)).map((m) => ({
|
|
4191
4213
|
id: m.id,
|
|
@@ -4196,7 +4218,7 @@ function plannerModelItems(state) {
|
|
|
4196
4218
|
return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
|
|
4197
4219
|
}
|
|
4198
4220
|
function plannerEffortItems(state) {
|
|
4199
|
-
const runner = (0,
|
|
4221
|
+
const runner = (0, import_core15.runnerForProvider)(state.plannerProvider);
|
|
4200
4222
|
if (!runner) {
|
|
4201
4223
|
return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
|
|
4202
4224
|
}
|
|
@@ -4409,20 +4431,20 @@ function handlePlanKey(state, key) {
|
|
|
4409
4431
|
if (editingTask && state.taskEditor) return handleTaskEditKey(state, editingTask, state.taskEditor, key);
|
|
4410
4432
|
if (key.name === "escape") return step({ ...state, focus: "chat" });
|
|
4411
4433
|
if (key.name === "up") {
|
|
4412
|
-
return step({ ...state, selectedTask: Math.max(0, state.selectedTask - 1),
|
|
4434
|
+
return step({ ...state, selectedTask: Math.max(0, state.selectedTask - 1), planScroll: null });
|
|
4413
4435
|
}
|
|
4414
4436
|
if (key.name === "down") {
|
|
4415
4437
|
return step({
|
|
4416
4438
|
...state,
|
|
4417
|
-
selectedTask: Math.min(state.
|
|
4418
|
-
expandedTaskId: null,
|
|
4439
|
+
selectedTask: Math.min(planRows(state).length - 1, state.selectedTask + 1),
|
|
4419
4440
|
planScroll: null
|
|
4420
4441
|
});
|
|
4421
4442
|
}
|
|
4422
4443
|
const scroll = scrollDelta(key, state);
|
|
4423
4444
|
if (scroll !== null) return scrollPlan(state, scroll);
|
|
4424
|
-
const
|
|
4425
|
-
if (!
|
|
4445
|
+
const row = selectedPlanRow(state);
|
|
4446
|
+
if (!row) return step(state);
|
|
4447
|
+
const task = row.task;
|
|
4426
4448
|
if (key.name === "enter" || key.name === "right") {
|
|
4427
4449
|
const text = task.prompt ?? task.description ?? task.title;
|
|
4428
4450
|
return step({
|
|
@@ -4672,7 +4694,7 @@ function submit(state) {
|
|
|
4672
4694
|
if (!text) return step(state);
|
|
4673
4695
|
const cleared = { ...state, editor: commit(state.editor) };
|
|
4674
4696
|
const command = parseSlash(text);
|
|
4675
|
-
if (command) return runCommand(cleared, command);
|
|
4697
|
+
if (command && findCommand(command.name)?.source !== "skill") return runCommand(cleared, command);
|
|
4676
4698
|
const spoken = say(cleared, "user", text);
|
|
4677
4699
|
const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
|
|
4678
4700
|
return step({ ...spoken, status: "planning" }, [effect]);
|
|
@@ -4834,7 +4856,7 @@ function newSession(state) {
|
|
|
4834
4856
|
}
|
|
4835
4857
|
function setModel(state, args) {
|
|
4836
4858
|
if (args[0] === "set" && args[1]) {
|
|
4837
|
-
const runner = (0,
|
|
4859
|
+
const runner = (0, import_core15.runnerForProvider)(state.plannerProvider);
|
|
4838
4860
|
const known = runner ? modelsForRunner(state.models, runner) : [];
|
|
4839
4861
|
if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
|
|
4840
4862
|
return fail2(state, `${args[1]} was not discovered for ${runner}.`);
|
|
@@ -4856,21 +4878,21 @@ function setModel(state, args) {
|
|
|
4856
4878
|
}
|
|
4857
4879
|
function plannerItems(state) {
|
|
4858
4880
|
const installed = new Set(state.runners.map((r) => r.id));
|
|
4859
|
-
const agents =
|
|
4860
|
-
const runner = (0,
|
|
4881
|
+
const agents = import_core15.CLI_PROVIDERS.map((id) => {
|
|
4882
|
+
const runner = (0, import_core15.runnerForProvider)(id);
|
|
4861
4883
|
const ready = installed.has(runner);
|
|
4862
4884
|
return {
|
|
4863
4885
|
id,
|
|
4864
|
-
label:
|
|
4886
|
+
label: import_core15.ALL_PROVIDERS[id].label,
|
|
4865
4887
|
detail: ready ? "coding agent \xB7 no API key needed" : "CLI not installed or not on PATH",
|
|
4866
4888
|
selected: state.plannerProvider === id,
|
|
4867
4889
|
disabled: !ready
|
|
4868
4890
|
};
|
|
4869
4891
|
});
|
|
4870
|
-
const vendors =
|
|
4892
|
+
const vendors = import_core15.PROVIDER_PRIORITY.filter((id) => state.configuredProviders.includes(id)).map((id) => ({
|
|
4871
4893
|
id,
|
|
4872
|
-
label:
|
|
4873
|
-
detail:
|
|
4894
|
+
label: import_core15.ALL_PROVIDERS[id].label,
|
|
4895
|
+
detail: import_core15.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
4874
4896
|
selected: state.plannerProvider === id
|
|
4875
4897
|
}));
|
|
4876
4898
|
return vendors.length > 0 ? [...agents, ...vendors] : [...agents, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
|
|
@@ -4928,16 +4950,16 @@ function setKey(state, args) {
|
|
|
4928
4950
|
}
|
|
4929
4951
|
return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
|
|
4930
4952
|
}
|
|
4931
|
-
const items =
|
|
4953
|
+
const items = import_core15.PROVIDER_PRIORITY.map((id) => ({
|
|
4932
4954
|
id,
|
|
4933
|
-
label:
|
|
4934
|
-
detail:
|
|
4955
|
+
label: import_core15.ALL_PROVIDERS[id].label,
|
|
4956
|
+
detail: import_core15.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
4935
4957
|
selected: state.configuredProviders.includes(id)
|
|
4936
4958
|
}));
|
|
4937
4959
|
return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
|
|
4938
4960
|
}
|
|
4939
4961
|
function keyPrompt(provider) {
|
|
4940
|
-
const meta =
|
|
4962
|
+
const meta = import_core15.ALL_PROVIDERS[provider];
|
|
4941
4963
|
return {
|
|
4942
4964
|
kind: "prompt",
|
|
4943
4965
|
title: `${meta.label} API key`,
|
|
@@ -5295,7 +5317,7 @@ function createApp(deps) {
|
|
|
5295
5317
|
|
|
5296
5318
|
// src/tui/effects.ts
|
|
5297
5319
|
var import_child_process4 = require("child_process");
|
|
5298
|
-
var
|
|
5320
|
+
var import_core16 = require("@ordewell/core");
|
|
5299
5321
|
var import_plan_utils4 = require("@ordewell/core/plan-utils");
|
|
5300
5322
|
var ConversationQueue = class {
|
|
5301
5323
|
tail = Promise.resolve();
|
|
@@ -5462,7 +5484,7 @@ async function perform(effect, deps) {
|
|
|
5462
5484
|
dispatch({ type: "notice", message: `Orchestrator model set to ${effect.modelId}.` });
|
|
5463
5485
|
return;
|
|
5464
5486
|
case "setPlanner": {
|
|
5465
|
-
const meta =
|
|
5487
|
+
const meta = import_core16.ALL_PROVIDERS[effect.provider];
|
|
5466
5488
|
if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
|
|
5467
5489
|
const env = { AI_PROVIDER: effect.provider };
|
|
5468
5490
|
const settings = await api.updateSettings({ env });
|
|
@@ -5474,7 +5496,7 @@ async function perform(effect, deps) {
|
|
|
5474
5496
|
});
|
|
5475
5497
|
dispatch({
|
|
5476
5498
|
type: "notice",
|
|
5477
|
-
message: `${(0,
|
|
5499
|
+
message: `${(0, import_core16.isCliProvider)(effect.provider) ? `Planning with ${meta.label} \u2014 no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
|
|
5478
5500
|
});
|
|
5479
5501
|
await loadModels(deps);
|
|
5480
5502
|
return;
|
|
@@ -5486,7 +5508,7 @@ async function perform(effect, deps) {
|
|
|
5486
5508
|
dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
|
|
5487
5509
|
return;
|
|
5488
5510
|
case "setApiKey": {
|
|
5489
|
-
const meta =
|
|
5511
|
+
const meta = import_core16.ALL_PROVIDERS[effect.provider];
|
|
5490
5512
|
if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
|
|
5491
5513
|
await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
|
|
5492
5514
|
deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
|
|
@@ -5574,7 +5596,7 @@ function osc52(text) {
|
|
|
5574
5596
|
function copySelection(deps, text) {
|
|
5575
5597
|
if (!text) return;
|
|
5576
5598
|
const lines = text.split("\n").length;
|
|
5577
|
-
const command = (0,
|
|
5599
|
+
const command = (0, import_core16.clipboardCopyCommand)(deps.hasBin);
|
|
5578
5600
|
if (command) {
|
|
5579
5601
|
try {
|
|
5580
5602
|
(deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
|
|
@@ -5607,7 +5629,28 @@ async function withExecutionStream(deps, sessionId, request) {
|
|
|
5607
5629
|
}
|
|
5608
5630
|
async function converse(deps, sessionId, call) {
|
|
5609
5631
|
let streamed = null;
|
|
5632
|
+
const TOKEN_DEBOUNCE_MS = 75;
|
|
5633
|
+
let tokenBuffer = "";
|
|
5634
|
+
let tokenTimer = null;
|
|
5635
|
+
const flushTokens = () => {
|
|
5636
|
+
if (tokenTimer !== null) {
|
|
5637
|
+
clearTimeout(tokenTimer);
|
|
5638
|
+
tokenTimer = null;
|
|
5639
|
+
}
|
|
5640
|
+
if (!tokenBuffer) return;
|
|
5641
|
+
const text = tokenBuffer;
|
|
5642
|
+
tokenBuffer = "";
|
|
5643
|
+
deps.dispatch({ type: "plannerToken", text, sessionId });
|
|
5644
|
+
};
|
|
5610
5645
|
const stream = deps.api.streamPlanning(sessionId, (event) => {
|
|
5646
|
+
if (event?.type === "plan_token" && event.token) {
|
|
5647
|
+
tokenBuffer += event.token;
|
|
5648
|
+
if (tokenTimer === null) {
|
|
5649
|
+
tokenTimer = setTimeout(flushTokens, TOKEN_DEBOUNCE_MS);
|
|
5650
|
+
}
|
|
5651
|
+
return;
|
|
5652
|
+
}
|
|
5653
|
+
flushTokens();
|
|
5611
5654
|
if (event?.type === "approval_request") {
|
|
5612
5655
|
deps.dispatch({
|
|
5613
5656
|
type: "approvalRequested",
|
|
@@ -5664,6 +5707,7 @@ async function converse(deps, sessionId, call) {
|
|
|
5664
5707
|
deps.dispatch({ type: "plannerMessage", content: question, sessionId });
|
|
5665
5708
|
}
|
|
5666
5709
|
} finally {
|
|
5710
|
+
if (tokenTimer !== null) clearTimeout(tokenTimer);
|
|
5667
5711
|
stream.close();
|
|
5668
5712
|
}
|
|
5669
5713
|
}
|
|
@@ -5683,7 +5727,7 @@ function onExecutionEvent(dispatch, event, sessionId) {
|
|
|
5683
5727
|
case "status_update": {
|
|
5684
5728
|
const updates = {};
|
|
5685
5729
|
for (const task of event.tasks ?? []) {
|
|
5686
|
-
updates[String(task.id)] = String(task.status);
|
|
5730
|
+
updates[String(task.id)] = { status: String(task.status), idleSince: task.idleSince ?? null };
|
|
5687
5731
|
}
|
|
5688
5732
|
dispatch({ type: "tasksStatus", updates, sessionId });
|
|
5689
5733
|
return;
|
|
@@ -6047,7 +6091,7 @@ async function handleTui(subArgs) {
|
|
|
6047
6091
|
}
|
|
6048
6092
|
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
6049
6093
|
try {
|
|
6050
|
-
(0,
|
|
6094
|
+
(0, import_core17.assertWorkspaceExists)(workspace);
|
|
6051
6095
|
} catch {
|
|
6052
6096
|
console.error(
|
|
6053
6097
|
`Workspace "${workspace}" does not exist.
|
|
@@ -6056,7 +6100,10 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
6056
6100
|
);
|
|
6057
6101
|
process.exit(1);
|
|
6058
6102
|
}
|
|
6059
|
-
|
|
6103
|
+
registerSkillCommands((0, import_core17.createSkillsService)(workspace).listSkills());
|
|
6104
|
+
const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
|
|
6105
|
+
const targetPort = explicitPort ? resolvePort(subArgs) : await findFreePort();
|
|
6106
|
+
const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(targetPort, { detached: false });
|
|
6060
6107
|
let owned = ownedAtLaunch;
|
|
6061
6108
|
const api = new ApiClient(port);
|
|
6062
6109
|
const conversationQueue = new ConversationQueue();
|
|
@@ -6072,7 +6119,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
6072
6119
|
conversationQueue,
|
|
6073
6120
|
port,
|
|
6074
6121
|
dispatch: (action) => app.dispatch(action),
|
|
6075
|
-
newSessionId: () => (0,
|
|
6122
|
+
newSessionId: () => (0, import_core17.mintSessionId)(),
|
|
6076
6123
|
setEnvVar: (key, value) => {
|
|
6077
6124
|
writeEnvVar(findEnvFile(), key, value);
|
|
6078
6125
|
process.env[key] = value;
|
|
@@ -6141,14 +6188,8 @@ var COMMANDS = {
|
|
|
6141
6188
|
refresh: handleRefresh,
|
|
6142
6189
|
setup: handleSetup,
|
|
6143
6190
|
plugins: handlePlugins,
|
|
6144
|
-
"grill-me": handleGrillMe,
|
|
6145
6191
|
"tdd": handleTdd,
|
|
6146
|
-
"prd": handlePrd,
|
|
6147
|
-
"review": handleReview,
|
|
6148
|
-
// 'verify' was once a legacy alias for review; it now toggles the
|
|
6149
|
-
// evidence-based verification mode.
|
|
6150
6192
|
"verify": handleVerify,
|
|
6151
|
-
"research-subagents": handleResearchSubagents,
|
|
6152
6193
|
"mark-complete": handleMarkComplete,
|
|
6153
6194
|
complete: handleMarkComplete,
|
|
6154
6195
|
uncomplete: handleUncomplete,
|
|
@@ -6184,6 +6225,7 @@ function cliVersion() {
|
|
|
6184
6225
|
|
|
6185
6226
|
// src/main.ts
|
|
6186
6227
|
async function main() {
|
|
6228
|
+
(0, import_core18.migrateOldConfigDir)();
|
|
6187
6229
|
loadEnvFile();
|
|
6188
6230
|
const argv = process.argv.slice(2);
|
|
6189
6231
|
const command = argv[0] ?? (process.stdin.isTTY ? "tui" : "--help");
|