@ordewell/cli 0.7.2 → 0.8.1

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.
Files changed (3) hide show
  1. package/README.md +6 -4
  2. package/dist/main.js +265 -414
  3. package/package.json +3 -3
package/dist/main.js CHANGED
@@ -100,7 +100,7 @@ an id.
100
100
  Planning:
101
101
  ordewell Full-screen terminal UI (everything the VS Code extension does)
102
102
  ordewell tui The same thing, named \u2014 for scripts and aliases
103
- ordewell plan --goal "text" Plan a goal conversationally (honours verify)
103
+ ordewell plan --goal "text" Plan a goal conversationally
104
104
  ordewell run Execute the last generated plan (streams status)
105
105
  ordewell approve Sign off a plan paused for review, and continue it
106
106
  ordewell stop Stop execution of the last session (or --server)
@@ -123,7 +123,6 @@ Tasks (<id> is an order number or a task ID):
123
123
  ordewell continue <id> <msg> Continue a finished task in its saved runner session
124
124
  ordewell checkpoint <id> approve|reject [reason] Answer the checkpoint a task waits at
125
125
  ordewell cancel <id> Kill a running task
126
- ordewell terminal <id> Open a real terminal attached to a task's runner
127
126
 
128
127
  Per-task assignment (omit the value to list the options):
129
128
  ordewell task-runner <id> [runner] Set a task's executor (re-picks its model, effort and mode)
@@ -131,6 +130,7 @@ Per-task assignment (omit the value to list the options):
131
130
  ordewell task-effort <id> [level] Set a task's thinking effort ("default" to clear)
132
131
  ordewell task-mode <id> [mode] Set a task's runner mode
133
132
  ordewell task-deps <id> [a,b|none] Set which earlier tasks a task waits for
133
+ ordewell task-skills <id> [a,b|none] Set the skills attached to a task (applies-to: task); none clears them
134
134
  ordewell task-ops <id> [on|off] Make a task an ops task (runs in your checkout once its dependencies are merged) or a change task
135
135
 
136
136
  Planner, models and runners:
@@ -141,19 +141,14 @@ Planner, models and runners:
141
141
  ordewell runners [<id> on|off] Enable or disable runners (claude-code, opencode, codex)
142
142
  ordewell allowlist set|clear|show Limit which models a runner may use
143
143
  ordewell auto [full|guarded] Autonomy level for new sessions: Full or Guarded
144
- ordewell transport [terminal|structured] Structured (default) or terminal task transport, from the next run
145
- ordewell parallel [<n>] How many AI tasks run at once (default 3; applies to a live run)
144
+ ordewell parallel [<n>] How many AI tasks run at once (default 5; applies to a live run)
146
145
  ordewell refresh Re-discover runners and model catalogs
147
146
  ordewell models List every provider's catalog (works without a server)
148
147
 
149
- Modes:
150
- ordewell tdd [on|off] Toggle Test-Driven Development mode (or show status)
151
- ordewell verify [on|off] Toggle verification mode \u2014 adds a final evidence-based task that runs the full suite (or show status)
152
-
153
148
  Other:
154
149
  ordewell web Start the API server (foreground; --daemon for background)
155
150
  ordewell setup Interactive first-run setup wizard
156
- ordewell plugins list|install|remove|create Manage runner plugins
151
+ ordewell skills List installed skills and shadowed workspace duplicates
157
152
  ordewell --help Show this help
158
153
  ordewell --version Print the installed version (alias: version, -v)
159
154
 
@@ -171,7 +166,7 @@ Plan options:
171
166
  --goal "text" Task description (required)
172
167
  --runner <id> Runner to use (repeatable; default: all enabled runners)
173
168
  --workspace /path Workspace directory (default: cwd)
174
- --no-chat One-shot plan; skips the planner dialogue (and its toggles)
169
+ --no-chat One-shot plan; skips the planner dialogue
175
170
 
176
171
  Run options:
177
172
  --session-id <id> Execute a specific session (default: last session)
@@ -185,6 +180,10 @@ Sessions options:
185
180
  --workspace /path Workspace to list sessions from (default: cwd)
186
181
  --json Output the list as JSON
187
182
 
183
+ Skills options:
184
+ --workspace /path Workspace to read skills from (default: cwd)
185
+ --json Output resolved skills and shadowed duplicates as JSON
186
+
188
187
  Task options (add-task):
189
188
  --title "text" Task title (required)
190
189
  --description "..." Task description (default: the title)
@@ -224,7 +223,7 @@ Environment:
224
223
  ORDEWELL_PORT Daemon port CLI commands target (default: 3742)
225
224
  ORDEWELL_DIRENV false: agents skip the project's direnv .envrc
226
225
  ORDEWELL_RESEARCH_ENABLED true (default) or false
227
- ORDEWELL_MAX_PARALLEL Default: 3 \u2014 set with "ordewell parallel"
226
+ ORDEWELL_MAX_PARALLEL Default: 5, set with "ordewell parallel"
228
227
  `);
229
228
  }
230
229
 
@@ -247,13 +246,13 @@ function oldPidFileFor(port) {
247
246
  return port === DEFAULT_PORT ? (0, import_path2.join)(OLD_CONFIG_DIR, "server.pid") : (0, import_path2.join)(OLD_CONFIG_DIR, `server-${port}.pid`);
248
247
  }
249
248
  function httpGet(url, timeoutMs = 2e3, headers = {}) {
250
- return new Promise((resolve3, reject) => {
249
+ return new Promise((resolve4, reject) => {
251
250
  const req = import_http.default.get(url, { timeout: timeoutMs, headers }, (res) => {
252
251
  let data = "";
253
252
  res.on("data", (chunk) => {
254
253
  data += chunk.toString();
255
254
  });
256
- res.on("end", () => resolve3({ status: res.statusCode || 0, data }));
255
+ res.on("end", () => resolve4({ status: res.statusCode || 0, data }));
257
256
  });
258
257
  req.on("error", reject);
259
258
  req.on("timeout", () => {
@@ -473,7 +472,7 @@ var ApiClient = class _ApiClient {
473
472
  return res.data;
474
473
  }
475
474
  rawRequest(method, urlPath, body) {
476
- return new Promise((resolve3, reject) => {
475
+ return new Promise((resolve4, reject) => {
477
476
  const url = new URL(urlPath, `http://127.0.0.1:${this.port}`);
478
477
  const configuredTimeout = Number.parseInt(process.env.ORDEWELL_HTTP_TIMEOUT_MS || "", 10);
479
478
  const token = this.token();
@@ -498,9 +497,9 @@ var ApiClient = class _ApiClient {
498
497
  });
499
498
  res.on("end", () => {
500
499
  try {
501
- resolve3({ status: res.statusCode || 0, data: JSON.parse(data) });
500
+ resolve4({ status: res.statusCode || 0, data: JSON.parse(data) });
502
501
  } catch {
503
- resolve3({ status: res.statusCode || 0, data });
502
+ resolve4({ status: res.statusCode || 0, data });
504
503
  }
505
504
  });
506
505
  });
@@ -514,17 +513,17 @@ var ApiClient = class _ApiClient {
514
513
  });
515
514
  }
516
515
  async generatePlan(goal, runners2, workspace, sessionId = (0, import_core3.mintSessionId)()) {
517
- const { plan, models, modelsByRunner } = await this.call("POST", `/api/plans/${sessionId}/generate`, "Plan generation failed", {
516
+ const { plan, models, modelsByRunner, notes } = await this.call("POST", `/api/plans/${sessionId}/generate`, "Plan generation failed", {
518
517
  goal,
519
518
  runners: runners2 || void 0,
520
519
  workspace
521
520
  });
522
- return { sessionId, plan, models, modelsByRunner };
521
+ return { sessionId, plan, models, modelsByRunner, ...notes ? { notes } : {} };
523
522
  }
524
523
  /**
525
- * Open the ADR-0002 planner dialogue. Unlike `generatePlan`, this path honours
526
- * the verify toggle, so the returned plan may be a question
527
- * (empty `tasks`, last word in `conversationHistory`) rather than a committed plan.
524
+ * Open the ADR-0002 planner dialogue. Unlike `generatePlan`, the returned plan
525
+ * may be a question (empty `tasks`, last word in `conversationHistory`) rather
526
+ * than a committed plan.
528
527
  */
529
528
  async startConversation(sessionId, goal, runners2, workspace, allowInit) {
530
529
  const res = await this.call("POST", `/api/plans/${sessionId}/converse/start`, "Planning failed", {
@@ -754,7 +753,7 @@ var ApiClient = class _ApiClient {
754
753
  streamExecution(sessionId, onEvent, onReady) {
755
754
  let endStream = () => {
756
755
  };
757
- const stream = new Promise((resolve3, reject) => {
756
+ const stream = new Promise((resolve4, reject) => {
758
757
  this.executionStreams.get(sessionId)?.();
759
758
  const socket = this.openSessionSocket(sessionId);
760
759
  let resolved = false;
@@ -767,7 +766,7 @@ var ApiClient = class _ApiClient {
767
766
  if (resolved) return;
768
767
  resolved = true;
769
768
  socket.close();
770
- resolve3();
769
+ resolve4();
771
770
  };
772
771
  this.executionStreams.set(sessionId, end);
773
772
  endStream = end;
@@ -791,7 +790,7 @@ var ApiClient = class _ApiClient {
791
790
  resolved = true;
792
791
  if (opened) {
793
792
  socket.close();
794
- resolve3("lost");
793
+ resolve4("lost");
795
794
  } else {
796
795
  reject(err);
797
796
  }
@@ -801,7 +800,7 @@ var ApiClient = class _ApiClient {
801
800
  if (!opened) onReady?.(new Error("Execution stream closed before connecting"));
802
801
  if (!resolved) {
803
802
  resolved = true;
804
- resolve3("lost");
803
+ resolve4("lost");
805
804
  }
806
805
  });
807
806
  });
@@ -829,10 +828,10 @@ var ApiClient = class _ApiClient {
829
828
  */
830
829
  streamPlanning(sessionId, onEvent) {
831
830
  const socket = this.openSessionSocket(sessionId);
832
- const ready = new Promise((resolve3) => {
833
- socket.once("open", () => resolve3());
834
- socket.once("error", () => resolve3());
835
- socket.once("close", () => resolve3());
831
+ const ready = new Promise((resolve4) => {
832
+ socket.once("open", () => resolve4());
833
+ socket.once("error", () => resolve4());
834
+ socket.once("close", () => resolve4());
836
835
  });
837
836
  socket.on("message", (data) => {
838
837
  try {
@@ -844,12 +843,6 @@ var ApiClient = class _ApiClient {
844
843
  });
845
844
  return { ready, close: () => socket.close() };
846
845
  }
847
- getCommands() {
848
- return this.call("GET", "/api/commands", "Failed to fetch commands");
849
- }
850
- sendCommand(name, args = {}) {
851
- return this.call("POST", `/api/commands/${name}`, `Command ${name} failed`, { args });
852
- }
853
846
  getSettings() {
854
847
  return this.call("GET", "/api/settings", "Failed to fetch settings");
855
848
  }
@@ -1111,10 +1104,15 @@ function hasCommittedPlan(plan) {
1111
1104
  function lastPlannerMessage(plan) {
1112
1105
  const history = "conversationHistory" in plan && plan.conversationHistory || [];
1113
1106
  for (let i = history.length - 1; i >= 0; i--) {
1114
- if (history[i].role === "assistant") return history[i].content;
1107
+ if (history[i].role === "assistant" && history[i].kind !== "skill_load") return history[i].content;
1115
1108
  }
1116
1109
  return "(the planner returned no plan and no message)";
1117
1110
  }
1111
+ function commitNotes(plan) {
1112
+ const history = "conversationHistory" in plan && plan.conversationHistory || [];
1113
+ const marker2 = history.map((e) => e.kind).lastIndexOf("plan_generated");
1114
+ return marker2 < 0 ? [] : history.slice(marker2 + 1).filter((e) => e.kind === "system").map((e) => e.content);
1115
+ }
1118
1116
  function stdinReader(input = process.stdin) {
1119
1117
  const rl = (0, import_readline.createInterface)({ input, output: process.stderr });
1120
1118
  const buffered = [];
@@ -1122,9 +1120,9 @@ function stdinReader(input = process.stdin) {
1122
1120
  let ended = false;
1123
1121
  const settle = (answer) => {
1124
1122
  if (!waiting) return false;
1125
- const resolve3 = waiting;
1123
+ const resolve4 = waiting;
1126
1124
  waiting = null;
1127
- resolve3(answer);
1125
+ resolve4(answer);
1128
1126
  return true;
1129
1127
  };
1130
1128
  rl.on("line", (line) => {
@@ -1140,8 +1138,8 @@ function stdinReader(input = process.stdin) {
1140
1138
  if (ended) return Promise.resolve(null);
1141
1139
  rl.setPrompt(question);
1142
1140
  rl.prompt();
1143
- return new Promise((resolve3) => {
1144
- waiting = resolve3;
1141
+ return new Promise((resolve4) => {
1142
+ waiting = resolve4;
1145
1143
  });
1146
1144
  },
1147
1145
  close() {
@@ -1275,6 +1273,7 @@ async function handlePlan(subArgs, deps = {}) {
1275
1273
  await stream.ready;
1276
1274
  let plan;
1277
1275
  let models = [];
1276
+ let notes = [];
1278
1277
  if (oneShot) {
1279
1278
  const result = await withSpinner(
1280
1279
  "Researching codebase and building task plan",
@@ -1282,6 +1281,7 @@ async function handlePlan(subArgs, deps = {}) {
1282
1281
  );
1283
1282
  plan = result.plan;
1284
1283
  models = result.models ?? [];
1284
+ notes = result.notes ?? [];
1285
1285
  } else {
1286
1286
  plan = await withSpinner(
1287
1287
  "Researching codebase and building task plan",
@@ -1313,11 +1313,14 @@ ${lastPlannerMessage(plan)}
1313
1313
  plan = await withSpinner("Thinking", () => api.sendConversationMessage(sessionId, reply));
1314
1314
  }
1315
1315
  reader?.close();
1316
+ notes = commitNotes(plan);
1316
1317
  }
1317
1318
  closeStream();
1318
1319
  if (isTty && lastLineLength > 0) process.stderr.write("\r\x1B[2K");
1319
1320
  process.stderr.write("\n");
1320
1321
  printPlan(plan, sessionId, runners2, models);
1322
+ for (const note of notes) console.log(`
1323
+ ${note}`);
1321
1324
  saveLastSession(sessionId, goal, runnersOf(plan, runners2), workspace);
1322
1325
  console.log(`
1323
1326
  Run 'ordewell run' to execute, 'ordewell status' to inspect, or 'ordewell tui' for the full UI.`);
@@ -1520,7 +1523,7 @@ async function askYesNo(question) {
1520
1523
  if (!process.stdin.isTTY) return false;
1521
1524
  const rl = (0, import_readline2.createInterface)({ input: process.stdin, output: process.stderr });
1522
1525
  try {
1523
- const answer = await new Promise((resolve3) => rl.question(`${question} [y/N] `, resolve3));
1526
+ const answer = await new Promise((resolve4) => rl.question(`${question} [y/N] `, resolve4));
1524
1527
  return /^y(es)?$/i.test(answer.trim());
1525
1528
  } finally {
1526
1529
  rl.close();
@@ -1552,7 +1555,8 @@ function toTaskView(task) {
1552
1555
  taskMode: task.taskMode || void 0,
1553
1556
  assignedModel: task.assignedModel ?? void 0,
1554
1557
  subtasks: [...task.subtasks ?? []].sort((a, b) => a.order - b.order).map(toTaskView),
1555
- ...task.ops ? { ops: true } : {}
1558
+ ...task.ops ? { ops: true } : {},
1559
+ ...task.skills?.length ? { skills: [...task.skills] } : {}
1556
1560
  };
1557
1561
  }
1558
1562
  function taskViews(plan) {
@@ -1602,13 +1606,13 @@ async function followExecution(api, sessionId, start, onBlocked) {
1602
1606
  let pausedAtGate = false;
1603
1607
  let noteBlocked = () => {
1604
1608
  };
1605
- const blockedSeen = new Promise((resolve3) => {
1606
- noteBlocked = resolve3;
1609
+ const blockedSeen = new Promise((resolve4) => {
1610
+ noteBlocked = resolve4;
1607
1611
  });
1608
1612
  let settleReady = () => {
1609
1613
  };
1610
- const streamReady = new Promise((resolve3, reject) => {
1611
- settleReady = (error) => error ? reject(error) : resolve3();
1614
+ const streamReady = new Promise((resolve4, reject) => {
1615
+ settleReady = (error) => error ? reject(error) : resolve4();
1612
1616
  });
1613
1617
  const stream = api.streamExecution(sessionId, (event) => {
1614
1618
  if (event.type === "task_started") {
@@ -2031,100 +2035,60 @@ function handleSetup(subArgs) {
2031
2035
  console.log("Review and edit the file, then run: ordewell tui");
2032
2036
  }
2033
2037
 
2034
- // src/commands/plugins.ts
2038
+ // src/commands/skills.ts
2035
2039
  var import_core15 = require("@ordewell/core");
2036
- function handlePlugins(subArgs) {
2037
- const registry = new import_core15.RunnerRegistry();
2038
- registry.loadUserPlugins();
2039
- const sub = subArgs[0];
2040
- if (sub === "list" || !sub) {
2041
- const plugins = registry.list();
2042
- if (plugins.length === 0) {
2043
- console.log("No plugins installed.");
2044
- return;
2045
- }
2046
- const nameWidth = Math.max(...plugins.map((p) => p.manifest.name.length), 4);
2047
- const versionWidth = Math.max(...plugins.map((p) => p.manifest.version.length), 7);
2048
- const sourceWidth = Math.max(...plugins.map((p) => p.source.length), 6);
2049
- console.log(`
2050
- ${plugins.length} plugin(s):
2051
- `);
2052
- console.log(
2053
- `${"Name".padEnd(nameWidth)} ${"Version".padEnd(versionWidth)} ${"Source".padEnd(sourceWidth)} Description`
2054
- );
2055
- console.log(
2056
- `${"-".repeat(nameWidth)} ${"-".repeat(versionWidth)} ${"-".repeat(sourceWidth)} ${"-".repeat(20)}`
2057
- );
2058
- for (const p of plugins) {
2059
- const status = p.source === "builtin" ? "(built-in)" : "user";
2060
- console.log(
2061
- `${p.manifest.name.padEnd(nameWidth)} ${p.manifest.version.padEnd(versionWidth)} ${status.padEnd(sourceWidth)} ${p.manifest.description}`
2062
- );
2063
- }
2064
- return;
2065
- }
2066
- if (sub === "install") {
2067
- const source = subArgs[1];
2068
- if (!source) {
2069
- console.error("Usage: ordewell plugins install <path|github:user/repo|name>");
2070
- process.exit(1);
2071
- }
2072
- try {
2073
- const resolved = (0, import_core15.classifyPluginSource)(source);
2074
- const manifest = resolved.kind === "git" ? registry.installFromGit(resolved.url) : registry.installFromPath(resolved.path);
2075
- console.log(`Installed plugin: ${manifest.name} v${manifest.version}`);
2076
- } catch (err) {
2077
- console.error(`Install failed: ${err.message}`);
2078
- process.exit(1);
2079
- }
2080
- return;
2081
- }
2082
- if (sub === "remove") {
2083
- const name = subArgs[1];
2084
- if (!name) {
2085
- console.error("Usage: ordewell plugins remove <name>");
2086
- process.exit(1);
2087
- }
2088
- try {
2089
- registry.remove(name);
2090
- console.log(`Removed plugin: ${name}`);
2091
- } catch (err) {
2092
- console.error(`Remove failed: ${err.message}`);
2093
- process.exit(1);
2094
- }
2040
+ var import_path5 = require("path");
2041
+ function invocation(skill) {
2042
+ if (skill.userInvocable && skill.modelInvocable) return "both";
2043
+ if (skill.userInvocable) return "user";
2044
+ if (skill.modelInvocable) return "model";
2045
+ return "none";
2046
+ }
2047
+ function handleSkills(subArgs) {
2048
+ if (positionals(subArgs).length > 0) {
2049
+ fail("Usage: ordewell skills [--workspace /path] [--json]");
2050
+ }
2051
+ const workspace = (0, import_path5.resolve)(flag(subArgs, "--workspace") || process.cwd());
2052
+ const catalog = (0, import_core15.createSkillsService)(workspace, new import_core15.EnvConfig().workspaceRepos).readCatalog();
2053
+ const skills = catalog.skills.map((skill) => ({
2054
+ name: skill.name,
2055
+ scope: skill.source,
2056
+ appliesTo: skill.appliesTo,
2057
+ invocation: invocation(skill),
2058
+ path: (0, import_core15.abbreviateHome)(skill.path)
2059
+ }));
2060
+ const shadowed = catalog.shadowed.map(({ skill, shadowedBy }) => ({
2061
+ name: skill.name,
2062
+ path: (0, import_core15.abbreviateHome)(skill.path),
2063
+ shadowedBy: { scope: shadowedBy.source, path: (0, import_core15.abbreviateHome)(shadowedBy.path) }
2064
+ }));
2065
+ const invalid = catalog.invalid.map((skill) => ({
2066
+ name: skill.name,
2067
+ path: (0, import_core15.abbreviateHome)(skill.path),
2068
+ reason: skill.reason
2069
+ }));
2070
+ if (hasFlag(subArgs, "--json")) {
2071
+ console.log(JSON.stringify({ skills, shadowed, invalid }, null, 2));
2095
2072
  return;
2096
2073
  }
2097
- if (sub === "create") {
2098
- const name = subArgs[1];
2099
- if (!name) {
2100
- console.error("Usage: ordewell plugins create <name>");
2101
- process.exit(1);
2102
- }
2103
- const dir = registry.createSkeleton(name, process.cwd());
2104
- console.log(`Created plugin skeleton: ${dir}`);
2105
- console.log(` Edit ${dir}/manifest.json to configure your runner`);
2106
- console.log(` Then install with: ordewell plugins install ${dir}`);
2074
+ const skipped = [
2075
+ ...shadowed.map((skill) => `workspace skill "${skill.name}" shadowed by the ${skill.shadowedBy.scope} copy at ${skill.shadowedBy.path} \xB7 ${skill.path}`),
2076
+ ...invalid.map((skill) => `skill folder "${skill.name}" skipped: ${skill.reason} \xB7 ${skill.path}`)
2077
+ ];
2078
+ if (skills.length === 0) {
2079
+ console.log("No skills installed.");
2080
+ for (const line of skipped) console.log(line);
2107
2081
  return;
2108
2082
  }
2109
- console.error(`Unknown plugins subcommand: ${sub}`);
2110
- console.error("Available: list, install <source>, remove <name>, create <name>");
2111
- process.exit(1);
2112
- }
2113
-
2114
- // src/commands/tdd.ts
2115
- async function handleTdd(subArgs) {
2116
- const action = subArgs[0];
2117
- const port = await ensureDaemon(resolvePort(subArgs));
2118
- const api = new ApiClient(port);
2119
- if (action === "on" || action === "off") {
2120
- const result = await api.sendCommand("tdd", { action });
2121
- const state = result.settings?.tdd;
2122
- console.log(`TDD mode: ${state?.enabled ? "ON" : "OFF"}`);
2123
- } else {
2124
- const settings = await api.getSettings();
2125
- const tdd = settings.tdd;
2126
- console.log(`TDD mode: ${tdd?.enabled ? "ON" : "OFF"}`);
2127
- console.log("Usage: ordewell tdd [on|off]");
2083
+ const headers = ["Name", "Scope", "Applies-to", "Invocation", "SKILL.md"];
2084
+ const rows = skills.map((skill) => [skill.name, skill.scope, skill.appliesTo, skill.invocation, skill.path]);
2085
+ const widths = headers.map((header, i) => Math.max(header.length, ...rows.map((row) => row[i].length)));
2086
+ const format = (row) => row.map((cell, i) => i === row.length - 1 ? cell : cell.padEnd(widths[i])).join(" ");
2087
+ console.log(format(headers));
2088
+ for (const row of rows) console.log(format(row));
2089
+ if (skipped.length > 0) {
2090
+ console.log("");
2091
+ for (const line of skipped) console.log(line);
2128
2092
  }
2129
2093
  }
2130
2094
 
@@ -2214,39 +2178,6 @@ async function handleShow(args, api) {
2214
2178
  }
2215
2179
  }
2216
2180
 
2217
- // src/commands/verify.ts
2218
- async function handleVerify(subArgs, api) {
2219
- const action = subArgs[0];
2220
- const port = api ? void 0 : await ensureDaemon(resolvePort(subArgs));
2221
- const client = api || new ApiClient(port);
2222
- if (action === "on" || action === "off") {
2223
- const result = await client.sendCommand("verify", { action });
2224
- const state = result.settings?.verification;
2225
- console.log(`Verification mode: ${state?.enabled ? "ON" : "OFF"}`);
2226
- } else {
2227
- const settings = await client.getSettings();
2228
- const verification = settings.verification;
2229
- console.log(`Verification mode: ${verification?.enabled ? "ON" : "OFF"}`);
2230
- console.log("Usage: ordewell verify [on|off]");
2231
- }
2232
- }
2233
-
2234
- // src/commands/transport.ts
2235
- var describe = (transport) => `Runner transport: ${transport === "terminal" ? "terminal" : "structured"} \u2014 applies from the next run`;
2236
- async function handleTransport(subArgs, api) {
2237
- const action = subArgs[0];
2238
- const port = api ? void 0 : await ensureDaemon(resolvePort(subArgs));
2239
- const client = api || new ApiClient(port);
2240
- if (action === "terminal" || action === "structured") {
2241
- const result = await client.sendCommand("transport", { action });
2242
- console.log(describe(result.settings?.runnerTransport));
2243
- } else {
2244
- const settings = await client.getSettings();
2245
- console.log(describe(settings.runnerTransport));
2246
- console.log("Usage: ordewell transport [terminal|structured]");
2247
- }
2248
- }
2249
-
2250
2181
  // src/commands/task-control.ts
2251
2182
  var import_core16 = require("@ordewell/core");
2252
2183
  var PAST_TENSE = {
@@ -3267,130 +3198,8 @@ async function handleApprove(subArgs, injectedApi) {
3267
3198
  });
3268
3199
  }
3269
3200
 
3270
- // src/tui/terminalLauncher.ts
3271
- var import_child_process3 = require("child_process");
3272
- var import_util = require("util");
3273
- var import_core22 = require("@ordewell/core");
3274
- var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
3275
- var defaultExecFile = async (command, args) => {
3276
- const { stdout, stderr } = await execFileAsync(command, args);
3277
- return { stdout: String(stdout), stderr: String(stderr) };
3278
- };
3279
- var defaultSpawn = (command, args) => {
3280
- (0, import_child_process3.spawn)(command, args, { detached: true, stdio: "ignore" }).unref();
3281
- };
3282
- var defaultWhich = (bin) => {
3283
- try {
3284
- (0, import_child_process3.execSync)(`${process.platform === "win32" ? "where" : "which"} ${bin}`, { stdio: "ignore" });
3285
- return true;
3286
- } catch {
3287
- return false;
3288
- }
3289
- };
3290
- async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
3291
- const platform = deps.platform ?? process.platform;
3292
- const env = deps.env ?? process.env;
3293
- const hasTmuxImpl = deps.hasTmuxImpl ?? import_core22.hasTmux;
3294
- const execFileImpl = deps.execFileImpl ?? defaultExecFile;
3295
- const spawnImpl = deps.spawnImpl ?? defaultSpawn;
3296
- const which = deps.which ?? defaultWhich;
3297
- if (!hasTmuxImpl()) {
3298
- return { ok: false, message: "No terminal window for this task: tmux is not installed. Install tmux to get one for terminal-transport tasks; a structured task has its log view instead." };
3299
- }
3300
- const session = (0, import_core22.tmuxSessionName)(port);
3301
- const socket = (0, import_core22.tmuxSocketName)(port);
3302
- const base = (0, import_core22.tmuxWindowName)(taskId, planSessionId);
3303
- let windows;
3304
- try {
3305
- const { stdout } = await execFileImpl("tmux", ["-L", socket, "list-windows", "-t", session, "-F", "#{window_name}"]);
3306
- windows = stdout.split("\n").map((line) => line.trim()).filter(Boolean);
3307
- } catch {
3308
- return { ok: false, message: "No terminal session found for this daemon \u2014 is it still running?" };
3309
- }
3310
- const window = latestAttempt(windows, base);
3311
- if (!window) {
3312
- return { ok: false, message: "This task has no live terminal and no saved log \u2014 it may still be pending." };
3313
- }
3314
- const manualHint = `tmux -L ${socket} attach -t ${session}:${window}`;
3315
- const viewer = `${session}-view-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
3316
- const attachCmd = `tmux -L ${socket} new-session -d -s ${viewer} -t ${session} \\; set-option -t ${viewer} destroy-unattached on \\; select-window -t ${viewer}:${window} \\; attach-session -t ${viewer}`;
3317
- const opened = launchTerminal(platform, env, which, spawnImpl, attachCmd);
3318
- if (!opened) {
3319
- return { ok: false, message: `Couldn't open a terminal window automatically \u2014 attach manually: ${manualHint}` };
3320
- }
3321
- return { ok: true, message: "Opened a terminal for this task." };
3322
- }
3323
- function latestAttempt(windows, base) {
3324
- let best = null;
3325
- let bestAttempt = 0;
3326
- for (const name of windows) {
3327
- if (name === base && bestAttempt < 1) {
3328
- best = name;
3329
- bestAttempt = 1;
3330
- continue;
3331
- }
3332
- const suffix = name.startsWith(`${base}-`) ? Number(name.slice(base.length + 1)) : NaN;
3333
- if (Number.isInteger(suffix) && suffix > bestAttempt) {
3334
- best = name;
3335
- bestAttempt = suffix;
3336
- }
3337
- }
3338
- return best;
3339
- }
3340
- function launchTerminal(platform, env, which, spawnImpl, attachCmd) {
3341
- if (platform === "darwin") {
3342
- const script = `tell application "Terminal" to do script "${attachCmd.replace(/"/g, '\\"')}"`;
3343
- spawnImpl("osascript", ["-e", script]);
3344
- return true;
3345
- }
3346
- if (platform === "win32") {
3347
- if (which("wt.exe")) {
3348
- spawnImpl("wt.exe", ["-w", "0", "nt", "bash", "-lc", attachCmd]);
3349
- return true;
3350
- }
3351
- if (which("cmd.exe")) {
3352
- spawnImpl("cmd.exe", ["/c", "start", '""', "bash", "-lc", attachCmd]);
3353
- return true;
3354
- }
3355
- return false;
3356
- }
3357
- if (!env.DISPLAY && !env.WAYLAND_DISPLAY) return false;
3358
- const candidates = [];
3359
- if (env.TERMINAL) candidates.push([env.TERMINAL, ["-e", "bash", "-lc", attachCmd]]);
3360
- candidates.push(
3361
- ["x-terminal-emulator", ["-e", "bash", "-lc", attachCmd]],
3362
- ["gnome-terminal", ["--", "bash", "-lc", attachCmd]],
3363
- ["konsole", ["-e", "bash", "-lc", attachCmd]],
3364
- ["xfce4-terminal", ["-e", `bash -lc ${JSON.stringify(attachCmd)}`]],
3365
- ["xterm", ["-e", "bash", "-lc", attachCmd]]
3366
- );
3367
- for (const [bin, args] of candidates) {
3368
- if (which(bin)) {
3369
- spawnImpl(bin, args);
3370
- return true;
3371
- }
3372
- }
3373
- return false;
3374
- }
3375
-
3376
- // src/commands/terminal.ts
3377
- async function handleTerminal(subArgs, injectedApi) {
3378
- await withResolvedTask(
3379
- subArgs,
3380
- "Usage: ordewell terminal <task-id-or-order> [--session-id <id>]",
3381
- injectedApi,
3382
- async (_api, sessionId, taskId) => {
3383
- const result = await openTaskTerminal(resolvePort(subArgs), sessionId, taskId);
3384
- if (!result.ok) {
3385
- console.error(result.message);
3386
- process.exit(1);
3387
- }
3388
- console.log(result.message);
3389
- }
3390
- );
3391
- }
3392
-
3393
3201
  // src/commands/task-assign.ts
3202
+ var import_core22 = require("@ordewell/core");
3394
3203
  var import_plan_utils = require("@ordewell/core/plan-utils");
3395
3204
 
3396
3205
  // src/tui/taskAssignment.ts
@@ -3430,12 +3239,13 @@ function modesForTask(modesByRunner, task) {
3430
3239
  }
3431
3240
 
3432
3241
  // src/commands/task-assign.ts
3433
- async function withTask(subArgs, usage, injectedApi, run) {
3242
+ async function withTask(subArgs, usage, injectedApi, run, { joinValue = false } = {}) {
3434
3243
  await withResolvedTask(subArgs, usage, injectedApi, async (api, sessionId, taskId, plan) => {
3435
3244
  const tasks = taskViews(plan);
3436
3245
  const task = tasks.find((t) => t.id === taskId);
3437
3246
  if (!task) fail(`Task not found in the plan: ${taskId}`);
3438
- const value = positionals(subArgs)[1];
3247
+ const [, ...rest] = positionals(subArgs);
3248
+ const value = joinValue ? rest.join(" ") || void 0 : rest[0];
3439
3249
  await run(api, sessionId, task, tasks, value);
3440
3250
  });
3441
3251
  }
@@ -3591,6 +3401,35 @@ Dependencies \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}
3591
3401
  console.log(dependencies.length > 0 ? `Task ${(0, import_plan_utils.taskRef)(task)} now depends on ${dependencies.length} task${dependencies.length === 1 ? "" : "s"}.` : `Task ${(0, import_plan_utils.taskRef)(task)} no longer depends on anything.`);
3592
3402
  });
3593
3403
  }
3404
+ var SKILLS_USAGE = "Usage: ordewell task-skills <task-id-or-order> [<name> [<name> \u2026]|<name,name,\u2026>|none] [--session-id <id>] [--workspace <path>]";
3405
+ async function handleTaskSkills(subArgs, injectedApi) {
3406
+ await withTask(subArgs, SKILLS_USAGE, injectedApi, async (api, sessionId, task, _tasks, value) => {
3407
+ if (task.type !== "ai") fail("Manual tasks do not run an executor, so they take no skills.");
3408
+ const catalog = (0, import_core22.createSkillsService)(flag(subArgs, "--workspace") || process.cwd(), new import_core22.EnvConfig().workspaceRepos).listSkills().filter((s) => s.appliesTo === "task");
3409
+ if (!value) {
3410
+ console.log(`
3411
+ Skills \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}
3412
+ `);
3413
+ if (catalog.length === 0) console.log(" No task skills found (applies-to: task in .ordewell/skills/ or ~/.ordewell/skills/).");
3414
+ for (const s of catalog) {
3415
+ console.log(` ${task.skills?.includes(s.name) ? "*" : " "} ${s.name}${s.description ? ` ${s.description}` : ""}`);
3416
+ }
3417
+ for (const name of task.skills ?? []) {
3418
+ if (!catalog.some((s) => s.name === name)) console.log(` * ${name} (not found)`);
3419
+ }
3420
+ console.log(`
3421
+ * = attached. ${SKILLS_USAGE}`);
3422
+ return;
3423
+ }
3424
+ const skills = value.toLowerCase() === "none" ? [] : [...new Set(value.toLowerCase().split(/[,\s]+/).filter(Boolean))];
3425
+ const unknown = skills.filter((name) => !catalog.some((s) => s.name === name));
3426
+ if (unknown.length > 0) {
3427
+ fail(`No task skill named ${unknown.map((n) => `"${n}"`).join(", ")}.`, `Task skills: ${catalog.map((s) => s.name).join(", ") || "none"}`);
3428
+ }
3429
+ await api.updateTask(sessionId, task.id, { skills });
3430
+ console.log(skills.length > 0 ? `Task ${(0, import_plan_utils.taskRef)(task)} skills set to ${skills.join(", ")}.` : `Task ${(0, import_plan_utils.taskRef)(task)} has no skills attached.`);
3431
+ }, { joinValue: true });
3432
+ }
3594
3433
  var OPS_USAGE = "Usage: ordewell task-ops <task-id-or-order> [on|off] [--session-id <id>]";
3595
3434
  async function handleTaskOps(subArgs, injectedApi) {
3596
3435
  await withTask(subArgs, OPS_USAGE, injectedApi, async (api, sessionId, task, tasks, value) => {
@@ -3761,12 +3600,12 @@ var SLASH_COMMANDS = [
3761
3600
  { name: "force-start", usage: "/force-start <id>", description: "Start a task now, ignoring dependencies", category: "tasks" },
3762
3601
  { name: "continue", usage: "/continue <id> <message>", description: "Continue a finished task in its saved runner session, with your message", category: "tasks" },
3763
3602
  { name: "checkpoint", usage: "/checkpoint <id> approve|reject [reason]", description: "Answer the checkpoint a task waits at: approve lets it go on, reject tells it why not", category: "tasks" },
3764
- { name: "terminal", usage: "/terminal <id>", description: "Open a task: its OS terminal, or a structured task's live log", category: "tasks" },
3765
3603
  { name: "task-runner", usage: "/task-runner <id> [runner]", description: "Choose or set a task's executor (re-picks its model and mode)", category: "tasks" },
3766
3604
  { name: "task-model", usage: "/task-model <id> [model]", description: "Choose or set a task's executor model", category: "tasks" },
3767
3605
  { name: "task-effort", usage: "/task-effort <id> [level]", description: "Choose or set a task's thinking effort", category: "tasks" },
3768
3606
  { name: "task-mode", usage: "/task-mode <id> [mode]", description: "Choose or set a task's runner mode", category: "tasks" },
3769
3607
  { name: "task-deps", usage: "/task-deps <id>", description: "Edit which earlier tasks a task waits for", category: "tasks" },
3608
+ { name: "task-skills", usage: "/task-skills <id> [a,b|none]", description: "Choose or set the skills attached to a task; none clears them", category: "tasks" },
3770
3609
  { name: "task-ops", usage: "/task-ops <id> [on|off]", description: "Make a task an ops task \u2014 run in your checkout once its dependencies are merged \u2014 or a change task", category: "tasks" },
3771
3610
  // Models & providers
3772
3611
  { name: "planner", usage: "/planner [<provider>]", description: "Choose who plans \u2014 an API provider or a coding agent (no API key)", category: "models" },
@@ -3776,12 +3615,8 @@ var SLASH_COMMANDS = [
3776
3615
  { name: "allowlist", usage: "/allowlist [set <runner> <ids> | clear <runner>]", description: "Limit which models a runner may use", category: "models" },
3777
3616
  { name: "runners", usage: "/runners [<id> on|off]", description: "Enable or disable runners (claude-code, opencode, codex)", category: "models" },
3778
3617
  { name: "auto", usage: "/auto [full|guarded]", description: "Autonomy level for new plans: Full or Guarded", category: "models" },
3779
- { name: "transport", usage: "/transport [terminal|structured]", description: "Drive tasks through each runner's protocol (structured) or a terminal, from the next run", category: "models" },
3780
3618
  { name: "parallel", usage: "/parallel [<n>]", description: "How many AI tasks run at once (applies to a live run)", category: "models" },
3781
3619
  { name: "refresh", usage: "/refresh", description: "Re-discover runners and model catalogs", category: "models" },
3782
- // Skills
3783
- { name: "tdd", usage: "/tdd [on|off]", description: "Test-Driven Development mode", category: "skills" },
3784
- { name: "verify", usage: "/verify [on|off]", description: "Verification mode \u2014 final evidence-based task that runs the suite", category: "skills" },
3785
3620
  // Sessions
3786
3621
  { name: "sessions", usage: "/sessions", description: "List saved sessions", category: "session" },
3787
3622
  { name: "fork", usage: "/fork", description: "Continue in a copy of this conversation and its tasks; the original stays as it is", category: "session" },
@@ -3802,11 +3637,11 @@ var skillByName = /* @__PURE__ */ new Map();
3802
3637
  function registerSkillCommands(skills) {
3803
3638
  const map = /* @__PURE__ */ new Map();
3804
3639
  for (const skill of skills) {
3805
- if (BY_NAME.has(skill.name)) continue;
3640
+ if (BY_NAME.has(skill.name) || skill.userInvocable === false) continue;
3806
3641
  map.set(skill.name, {
3807
3642
  name: skill.name,
3808
3643
  usage: `/${skill.name}`,
3809
- description: skill.description,
3644
+ description: skill.appliesTo === "task" ? `task skill \xB7 ${skill.description}` : skill.description,
3810
3645
  category: "skills",
3811
3646
  source: "skill"
3812
3647
  });
@@ -4001,9 +3836,29 @@ var ROLE_PREFIX = {
4001
3836
  function messageLines(block, cols) {
4002
3837
  const text = sanitize(block.text);
4003
3838
  const room = Math.max(1, cols - 2);
4004
- const wrapped = block.role === "planner" || block.role === "agent" ? renderMarkdown(text, room) : wrap(text, room);
3839
+ const wrapped = block.role === "planner" || block.role === "agent" ? renderMarkdown(text, room) : block.skills ? skillPaintedLines(text, block.skills, room) : wrap(text, room);
4005
3840
  return wrapped.map((line, i) => i === 0 ? ROLE_PREFIX[block.role](line) : ` ${line}`);
4006
3841
  }
3842
+ function skillPaintedLines(text, skills, room) {
3843
+ const tokens = (0, import_core23.loadedSkillTokens)(text, skills);
3844
+ return wrapLines(text, room).map(({ line, start }) => {
3845
+ let painted = "";
3846
+ let at = 0;
3847
+ for (const token of tokens) {
3848
+ const from = Math.max(token.start - start, at);
3849
+ const to = Math.min(token.end - start, line.length);
3850
+ if (to <= from) continue;
3851
+ painted += line.slice(at, from) + style.cyan(line.slice(from, to));
3852
+ at = to;
3853
+ }
3854
+ return painted + line.slice(at);
3855
+ });
3856
+ }
3857
+ function skillLoadLine(block, cols) {
3858
+ const head = block.attaches ? `\u25CF /${sanitize(block.name)} will be attached to fitting tasks \xB7 ` : block.invokedBy === "planner" ? `\u25CF ${sanitize(block.name)} skill loaded by planner \xB7 ` : `\u25CF /${sanitize(block.name)} skill loaded \xB7 `;
3859
+ const line = truncate(`${head}${truncatePath(sanitize(block.path), Math.max(1, cols - width(head)))}`, cols);
3860
+ return `${style.green("\u25CF")}${style.grey(line.slice(1))}`;
3861
+ }
4007
3862
  function thinkingLines(block, cols, detailAll) {
4008
3863
  const text = sanitize(block.text).trim();
4009
3864
  const head = `\u2234 Thinking (${countOf(text ? text.split(/\s+/).length : 0, "word")})`;
@@ -4047,7 +3902,7 @@ function truncatePath(path, max) {
4047
3902
  if (width(char + tail) > max - 1) break;
4048
3903
  tail = char + tail;
4049
3904
  }
4050
- const slash = tail.indexOf("/");
3905
+ const slash = tail.search(/[\\/]/);
4051
3906
  return `\u2026${slash > 0 ? tail.slice(slash) : tail}`;
4052
3907
  }
4053
3908
  function commandHeader(block, cols, detailAll) {
@@ -4201,6 +4056,8 @@ function blockLines(block, cols, detailAll) {
4201
4056
  return [approvalLine(block, cols)];
4202
4057
  case "plan":
4203
4058
  return [planLine(block, cols)];
4059
+ case "skill_load":
4060
+ return [skillLoadLine(block, cols)];
4204
4061
  // Pinned under the pane rather than drawn in it — see `tokenLine`.
4205
4062
  case "usage":
4206
4063
  return [];
@@ -4216,11 +4073,11 @@ function drawn(block, cols, detailAll) {
4216
4073
  }
4217
4074
  function conversationLines(blocks, cols, detailAll, answering) {
4218
4075
  const lines = [];
4219
- for (const block of blocks) {
4076
+ for (const [i, block] of blocks.entries()) {
4220
4077
  if (block.type === "usage") continue;
4221
4078
  lines.push(...drawn(block, cols, detailAll));
4222
4079
  if (block === answering) lines.push(approvalKeysLine(block, cols));
4223
- lines.push("");
4080
+ if (blocks[i + 1]?.type !== "skill_load") lines.push("");
4224
4081
  }
4225
4082
  return lines;
4226
4083
  }
@@ -4249,10 +4106,6 @@ function tokenLine(blocks, cols) {
4249
4106
 
4250
4107
  // src/tui/state.ts
4251
4108
  var import_core24 = require("@ordewell/core");
4252
- var SKILL_IDS = ["tdd", "verify"];
4253
- function noSkills() {
4254
- return { tdd: false, verify: false };
4255
- }
4256
4109
  function visibleItems(picker2) {
4257
4110
  const filter = picker2.filter.trim().toLowerCase();
4258
4111
  if (!filter) return picker2.items;
@@ -4313,12 +4166,12 @@ function initialState(overrides = {}) {
4313
4166
  taskEditor: null,
4314
4167
  scroll: 0,
4315
4168
  planScroll: null,
4316
- skills: noSkills(),
4317
4169
  runners: [],
4318
4170
  sessions: [],
4319
4171
  rewindTargets: null,
4320
4172
  models: [],
4321
4173
  modesByRunner: {},
4174
+ taskSkills: [],
4322
4175
  orchestratorModels: [],
4323
4176
  providerErrors: {},
4324
4177
  orchestratorModel: "",
@@ -4328,7 +4181,6 @@ function initialState(overrides = {}) {
4328
4181
  configuredProviders: [],
4329
4182
  allowlist: {},
4330
4183
  autonomous: true,
4331
- runnerTransport: "structured",
4332
4184
  mouseCapture: true,
4333
4185
  selection: null,
4334
4186
  workspace: process.cwd(),
@@ -4403,12 +4255,13 @@ function footerHints(state) {
4403
4255
  }
4404
4256
  return [
4405
4257
  ...escHint ? [escHint] : [],
4406
- "enter expand",
4258
+ "\u2192 expand/edit",
4407
4259
  "R runner",
4408
4260
  "o model",
4409
4261
  "e effort",
4410
4262
  "M mode",
4411
4263
  "D deps",
4264
+ "K skills",
4412
4265
  "O ops",
4413
4266
  "f start",
4414
4267
  "E run plan",
@@ -4418,7 +4271,7 @@ function footerHints(state) {
4418
4271
  "s skip",
4419
4272
  "a add",
4420
4273
  "d remove",
4421
- "t terminal",
4274
+ "enter log",
4422
4275
  ...resolveHint,
4423
4276
  "pgup/pgdn scroll",
4424
4277
  "tab chat"
@@ -4739,11 +4592,11 @@ function taskLines(state, planRow, index, cols, orderOf) {
4739
4592
  const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${row.autonomous ? style.yellow(" \u26A1") : ""}` : "";
4740
4593
  const runner = task.assignedRunner ?? "";
4741
4594
  const bodyPad = parent ? " " : " ";
4742
- const structured = task.transport?.kind === "structured";
4743
- const activity = row.approvals ? `${inRow(row.approvals)} \u2014 t opens it` : row.status === "quiet" ? structured ? "quiet" : "quiet \u2014 t opens its terminal" : row.running ? "working" : row.awaiting ? inRow(row.awaiting) : "";
4744
- const meta = [activity, runner, structured ? "structured" : "", model].filter(Boolean).join(" \xB7 ");
4595
+ const activity = row.approvals ? `${inRow(row.approvals)} \u2014 enter opens it` : row.status === "quiet" ? "quiet" : row.running ? "working" : row.awaiting ? inRow(row.awaiting) : "";
4596
+ const skills = (task.skills ?? []).join(" \xB7 ");
4597
+ const meta = [activity, runner, model].filter(Boolean).join(" \xB7 ");
4745
4598
  if (meta) lines.push(style.grey(truncate(`${bodyPad}${meta}`, cols)));
4746
- if (task.transport?.fallback) lines.push(style.yellow(truncate(`${bodyPad}terminal: ${task.transport.fallback}`, cols)));
4599
+ if (skills) lines.push(style.grey(truncate(`${bodyPad}skills: ${skills}`, cols)));
4747
4600
  if (effort || mode) lines.push(style.grey(truncate(`${bodyPad}${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
4748
4601
  if (row.conflict) {
4749
4602
  const where = row.conflict.repo ? ` in ${row.conflict.repo}` : "";
@@ -4798,7 +4651,7 @@ function taskLines(state, planRow, index, cols, orderOf) {
4798
4651
  lines.push(...taskText("Depends on", row.dependencies.join(", "), cols, bodyPad));
4799
4652
  }
4800
4653
  lines.push(style.grey(`${bodyPad}enter save \xB7 esc cancel`));
4801
- lines.push(style.grey(truncate(`${bodyPad}then R runner \xB7 o model \xB7 e effort \xB7 M mode \xB7 D deps`, cols)));
4654
+ lines.push(style.grey(truncate(`${bodyPad}then R runner \xB7 o model \xB7 e effort \xB7 M mode \xB7 D deps \xB7 K skills`, cols)));
4802
4655
  }
4803
4656
  return { lines, editorLine };
4804
4657
  }
@@ -4843,7 +4696,7 @@ function helpLayout(rows, cols) {
4843
4696
  );
4844
4697
  body.push(
4845
4698
  "",
4846
- style.bold("In a task view (t or /terminal on a structured task)"),
4699
+ style.bold("In a task view (enter on a task)"),
4847
4700
  style.grey(" ctrl-s sends now: interrupts the running step, then delivers the composer text \u2014 or, with it empty, the selected queued message"),
4848
4701
  style.grey(" ctrl-n/ctrl-p select a queued message \xB7 ctrl-r removes it \xB7 ctrl-x interrupts"),
4849
4702
  style.grey(" ctrl-y allows a tool request (its keys sit under it) \xB7 ctrl-t allows it for the task \xB7 ctrl-g denies it, with the composer text as the note"),
@@ -4865,7 +4718,7 @@ function render(state) {
4865
4718
  const rowsForBody = bodyRows(state);
4866
4719
  const body = state.overlay ? renderOverlay(state, rowsForBody, cols) : renderBody(state, rowsForBody, cols);
4867
4720
  const lines = [
4868
- renderSkills(state, cols),
4721
+ renderAutonomy(state, cols),
4869
4722
  ...body,
4870
4723
  renderStatus(state, cols),
4871
4724
  ...renderInput(state),
@@ -4950,13 +4803,9 @@ function fit(lines, rows, anchor) {
4950
4803
  const blanks = Array(rows - lines.length).fill("");
4951
4804
  return anchor === "bottom" ? [...blanks, ...lines] : [...lines, ...blanks];
4952
4805
  }
4953
- function renderSkills(state, cols) {
4954
- const badges = SKILL_IDS.map(
4955
- (id) => state.skills[id] ? style.green(`\u25CF ${id}`) : style.grey(`\u25CB ${id}`)
4956
- );
4806
+ function renderAutonomy(state, cols) {
4957
4807
  const auto = state.autonomous ? style.yellow("\u25CF Full") : style.green("\u25CF Guarded");
4958
- const terminal = state.runnerTransport === "terminal" ? [style.yellow("\u25CF terminal")] : [];
4959
- return truncate([...badges, auto, ...terminal].join(" "), cols);
4808
+ return truncate(auto, cols);
4960
4809
  }
4961
4810
  function scrolledBackMark(state) {
4962
4811
  return state.focus !== "plan" && state.scroll > 0 ? "\u2191 scrolled back \xB7 pgdn for live" : "";
@@ -5374,6 +5223,17 @@ function pickerItemsFor(state, action) {
5374
5223
  detail: candidate.status === "completed" ? "already completed" : void 0
5375
5224
  }));
5376
5225
  }
5226
+ if (action.kind === "set-task-skills") {
5227
+ const task = findTask(state.tasks, action.taskId);
5228
+ if (!task) return [];
5229
+ const known = new Set(state.taskSkills.map((s) => s.name));
5230
+ return [
5231
+ ...state.taskSkills.map((s) => ({ id: s.name, label: s.name, detail: s.description || void 0 })),
5232
+ // Attached but not in the catalog (created by an earlier task's worktree): keep it
5233
+ // selectable so confirming the picker does not silently detach it.
5234
+ ...(task.skills ?? []).filter((name) => !known.has(name)).map((name) => ({ id: name, label: `${name} (not found)`, detail: "not found in the catalog" }))
5235
+ ];
5236
+ }
5377
5237
  if (action.kind === "set-task-mode") {
5378
5238
  const task = findTask(state.tasks, action.taskId);
5379
5239
  if (!task) return [];
@@ -5526,11 +5386,6 @@ function openTaskView(state, sessionId, taskId) {
5526
5386
  };
5527
5387
  return step({ ...state, taskView, focus: "chat", scroll: 0 }, [{ type: "openTaskLog", sessionId, taskId }]);
5528
5388
  }
5529
- function openTaskTerminalOrView(state, sessionId, taskId) {
5530
- const task = findTask(state.tasks, taskId);
5531
- if (task?.transport?.kind === "structured") return openTaskView(state, sessionId, taskId);
5532
- return step(state, [{ type: "openTaskTerminal", sessionId, taskId }]);
5533
- }
5534
5389
  function continueTaskStep(state, sessionId, taskId, text) {
5535
5390
  const opened = state.taskView?.taskId === taskId ? step({ ...state, taskView: { ...state.taskView, followLatest: true } }) : openTaskView(state, sessionId, taskId);
5536
5391
  const watch = state.status !== "executing";
@@ -5575,13 +5430,13 @@ function taskLogArrived(state, action) {
5575
5430
  function announceApprovals(state, action) {
5576
5431
  if (state.taskView?.taskId === action.taskId) return state;
5577
5432
  const task = findTask(state.tasks, action.taskId);
5578
- const spoken = action.events.reduce((s, event) => event.type === "approval_requested" ? say(s, "system", `\xB7 Task ${task?.order ?? "?"} waits for approval: ${(0, import_core28.runnerToolSubject)(event.tool, event.args)} \u2014 t on it, then ctrl-y to allow or ctrl-g to deny`) : s, state);
5433
+ const spoken = action.events.reduce((s, event) => event.type === "approval_requested" ? say(s, "system", `\xB7 Task ${task?.order ?? "?"} waits for approval: ${(0, import_core28.runnerToolSubject)(event.tool, event.args)} \u2014 enter on it, then ctrl-y to allow or ctrl-g to deny`) : s, state);
5579
5434
  return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
5580
5435
  }
5581
5436
  function announceCheckpoint(state, action) {
5582
5437
  const task = findTask(state.tasks, action.taskId) ?? state.tasks.find((t) => t.title === action.title);
5583
5438
  const label = task ? `Task ${task.order}` : action.title;
5584
- const where = task?.transport?.kind === "structured" ? "t on it to answer" : `/checkpoint ${task?.order ?? "<id>"} approve|reject to answer`;
5439
+ const where = "enter on it to answer";
5585
5440
  const spoken = say(state, "system", `\xB7 ${label} asks: ${(0, import_core28.truncateCheckpointSummary)(action.summary)} \u2014 ${where}`);
5586
5441
  return { ...spoken, scroll: state.scroll };
5587
5442
  }
@@ -5742,6 +5597,34 @@ function openTaskDepsPicker(state, task) {
5742
5597
  }
5743
5598
  });
5744
5599
  }
5600
+ function openTaskSkillsPicker(state, task) {
5601
+ if (task.type !== "ai") return fail3(state, "Manual tasks do not run an executor, so they take no skills.");
5602
+ const action = { kind: "set-task-skills", taskId: task.id };
5603
+ const items = pickerItemsFor(state, action);
5604
+ if (items.length === 0) {
5605
+ return fail3(state, "No task skills found. A skill with applies-to: task in .ordewell/skills/ or ~/.ordewell/skills/ shows up here.");
5606
+ }
5607
+ return step({
5608
+ ...state,
5609
+ overlay: {
5610
+ kind: "picker",
5611
+ picker: picker(`Skills \xB7 ${(0, import_plan_utils3.titledTaskRef)(task)}`, items, action, {
5612
+ hint: "Attached skills go into this task's prompt when it starts.",
5613
+ multi: true,
5614
+ chosen: task.skills ?? []
5615
+ })
5616
+ }
5617
+ });
5618
+ }
5619
+ function assignTaskSkills(state, sessionId, task, skills) {
5620
+ return step(state, [{
5621
+ type: "updateTask",
5622
+ sessionId,
5623
+ taskId: task.id,
5624
+ changes: { skills },
5625
+ message: skills.length > 0 ? `Task ${(0, import_plan_utils3.taskRef)(task)} skills set to ${skills.join(", ")}.` : `Task ${(0, import_plan_utils3.taskRef)(task)} has no skills attached.`
5626
+ }]);
5627
+ }
5745
5628
  function openTaskModePicker(state, task) {
5746
5629
  if (task.type !== "ai") return fail3(state, "Manual tasks do not have an executor mode.");
5747
5630
  const modes = modesForTask(state.modesByRunner, task);
@@ -5882,6 +5765,20 @@ function taskEffortCommand(state, args) {
5882
5765
  return assignTaskEffort(state, sessionId, task, value);
5883
5766
  });
5884
5767
  }
5768
+ function taskSkillsCommand(state, args) {
5769
+ return taskCommand(state, args[0], (sessionId, taskId) => {
5770
+ const task = findTask(state.tasks, taskId);
5771
+ if (args.length < 2) return openTaskSkillsPicker(state, task);
5772
+ if (task.type !== "ai") return fail3(state, "Manual tasks do not run an executor, so they take no skills.");
5773
+ const value = args.slice(1).join(" ");
5774
+ const names = value.toLowerCase() === "none" ? [] : [...new Set(value.toLowerCase().split(/[,\s]+/).filter(Boolean))];
5775
+ const unknown = names.filter((name) => !state.taskSkills.some((s) => s.name === name));
5776
+ if (unknown.length > 0) {
5777
+ return fail3(state, `No task skill named ${unknown.map((n) => `"${n}"`).join(", ")}. Task skills: ${state.taskSkills.map((s) => s.name).join(", ") || "none"}.`);
5778
+ }
5779
+ return assignTaskSkills(state, sessionId, task, names);
5780
+ });
5781
+ }
5885
5782
  function taskOpsCommand(state, args) {
5886
5783
  return taskCommand(state, args[0], (_sessionId, taskId) => {
5887
5784
  const task = findTask(state.tasks, taskId);
@@ -5952,7 +5849,8 @@ function handlePlanKey(state, key) {
5952
5849
  const row = selectedPlanRow(state);
5953
5850
  if (!row) return step(state);
5954
5851
  const task = row.task;
5955
- if (key.name === "enter" || key.name === "right") {
5852
+ if (key.name === "enter" && state.sessionId) return openTaskView(state, state.sessionId, task.id);
5853
+ if (key.name === "right") {
5956
5854
  const hasSubtasks = (task.subtasks?.length ?? 0) > 0;
5957
5855
  if (hasSubtasks && state.expandedTaskId !== task.id) {
5958
5856
  return step(settlePlan({ ...state, expandedTaskId: task.id, taskEditor: null }));
@@ -5974,7 +5872,6 @@ function handlePlanKey(state, key) {
5974
5872
  }
5975
5873
  if (key.char === "O") return toggleTaskOps(state, task, row.parent !== null);
5976
5874
  if (key.char === "d") return confirmRemoveTask(state, task);
5977
- if (key.char === "t") return openTaskTerminalOrView(state, state.sessionId, task.id);
5978
5875
  if (key.char === "x" && task.isolation?.state === "conflict") {
5979
5876
  return step(state, [{ type: "resolveConflict", sessionId: state.sessionId, taskId: task.id }]);
5980
5877
  }
@@ -5983,6 +5880,7 @@ function handlePlanKey(state, key) {
5983
5880
  if (key.char === "e") return openTaskEffortPicker(state, task);
5984
5881
  if (key.char === "M") return openTaskModePicker(state, task);
5985
5882
  if (key.char === "D") return openTaskDepsPicker(state, task);
5883
+ if (key.char === "K") return openTaskSkillsPicker(state, task);
5986
5884
  return step(state);
5987
5885
  }
5988
5886
  function handleTaskEditKey(state, task, editor, key) {
@@ -6053,13 +5951,10 @@ function plannerEscape(state) {
6053
5951
 
6054
5952
  // src/tui/reducers/commands.ts
6055
5953
  var KNOWN_PROVIDERS = Object.keys(import_core31.ALL_PROVIDERS);
5954
+ var unknownCommand = (state, name) => fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
5955
+ var skillInTaskView = (state, name) => fail3(state, `/${name} is a skill, and skills aren't loaded in a task's chat. To give a task a skill, use /task-skills <id> <name>.`);
6056
5956
  function runCommand(state, { name, args }) {
6057
- if (!findCommand(name)) {
6058
- return fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
6059
- }
6060
- if (SKILL_IDS.includes(name)) {
6061
- return toggleSkill(state, name, args[0]);
6062
- }
5957
+ if (!findCommand(name)) return unknownCommand(state, name);
6063
5958
  switch (name) {
6064
5959
  case "help":
6065
5960
  return step({ ...state, overlay: { kind: "help", scroll: 0 } });
@@ -6093,8 +5988,6 @@ function runCommand(state, { name, args }) {
6093
5988
  return runners(state, args);
6094
5989
  case "auto":
6095
5990
  return setAutonomous(state, args[0]);
6096
- case "transport":
6097
- return setTransport(state, args[0]);
6098
5991
  case "parallel":
6099
5992
  return setMaxParallel(state, args[0]);
6100
5993
  case "mouse":
@@ -6131,8 +6024,6 @@ function runCommand(state, { name, args }) {
6131
6024
  args[0],
6132
6025
  (sessionId, taskId) => step(state, [{ type: "removeTask", sessionId, taskId }])
6133
6026
  );
6134
- case "terminal":
6135
- return taskCommand(state, args[0], (sessionId, taskId) => openTaskTerminalOrView(state, sessionId, taskId));
6136
6027
  case "continue":
6137
6028
  return taskCommand(state, args[0], (sessionId, taskId) => {
6138
6029
  const text = args.slice(1).join(" ").trim();
@@ -6157,6 +6048,8 @@ function runCommand(state, { name, args }) {
6157
6048
  return taskModeCommand(state, args);
6158
6049
  case "task-ops":
6159
6050
  return taskOpsCommand(state, args);
6051
+ case "task-skills":
6052
+ return taskSkillsCommand(state, args);
6160
6053
  case "task-deps":
6161
6054
  return taskCommand(
6162
6055
  state,
@@ -6190,11 +6083,6 @@ function resolveToggle(arg, current) {
6190
6083
  if (value === void 0) return !current;
6191
6084
  return null;
6192
6085
  }
6193
- function toggleSkill(state, skill, arg) {
6194
- const enabled = resolveToggle(arg, state.skills[skill]);
6195
- if (enabled === null) return fail3(state, `Usage: /${skill} [on|off]`);
6196
- return step(state, [{ type: "command", name: skill, action: enabled ? "on" : "off" }]);
6197
- }
6198
6086
  function requestNewSession(state) {
6199
6087
  const hasContent = state.tasks.length > 0 || state.conversation.blocks.length > 0 || state.goal !== "" || state.status !== "idle";
6200
6088
  if (!state.sessionId || !hasContent) return newSession(state);
@@ -6406,11 +6294,6 @@ function setAutonomous(state, arg) {
6406
6294
  if (enabled === null) return fail3(state, "Usage: /auto [full|guarded]");
6407
6295
  return step({ ...state, autonomous: enabled }, [{ type: "setAutonomous", enabled }]);
6408
6296
  }
6409
- function setTransport(state, arg) {
6410
- const value = arg?.toLowerCase() ?? (state.runnerTransport === "structured" ? "terminal" : "structured");
6411
- if (!(0, import_core31.isRunnerTransport)(value)) return fail3(state, "Usage: /transport [terminal|structured]");
6412
- return step(state, [{ type: "setTransport", transport: value }]);
6413
- }
6414
6297
  function setMouseCapture(state, arg) {
6415
6298
  const enabled = resolveToggle(arg, state.mouseCapture);
6416
6299
  if (enabled === null) return fail3(state, "Usage: /mouse [on|off]");
@@ -6588,6 +6471,11 @@ function choose(state, picker2, item) {
6588
6471
  message: picker2.chosen.length > 0 ? `Task ${(0, import_core32.taskRef)(task)} now depends on ${picker2.chosen.length} task${picker2.chosen.length === 1 ? "" : "s"}.` : `Task ${(0, import_core32.taskRef)(task)} no longer depends on anything.`
6589
6472
  }]);
6590
6473
  }
6474
+ if (picker2.action.kind === "set-task-skills") {
6475
+ const task = findTask(state.tasks, picker2.action.taskId);
6476
+ if (!task || !state.sessionId) return step(closed);
6477
+ return assignTaskSkills(closed, state.sessionId, task, picker2.chosen);
6478
+ }
6591
6479
  if (!item) return step(state);
6592
6480
  if (item.disabled) {
6593
6481
  return step(say(state, "error", item.detail ? `${item.label} \u2014 ${item.detail}` : `${item.label} is not available.`));
@@ -6705,28 +6593,17 @@ function toTaskView2(t, index) {
6705
6593
  } : void 0,
6706
6594
  subtasks: Array.isArray(t.subtasks) ? [...t.subtasks].sort((a, b) => wireOrder(a) - wireOrder(b)).map((s, i) => toTaskView2(asRecord(s), i)) : void 0,
6707
6595
  ...t.ops === true ? { ops: true } : {},
6596
+ ...Array.isArray(t.skills) && t.skills.length > 0 ? { skills: t.skills.map((name) => planLabel(name)) } : {},
6708
6597
  ...Array.isArray(t.forcedPastGate) && t.forcedPastGate.length > 0 ? { forcedPastGate: t.forcedPastGate.map((title) => planLabel(title)) } : {}
6709
6598
  };
6710
6599
  }
6711
- var enabledFlag = (value) => typeof value?.enabled === "boolean" ? value.enabled : void 0;
6712
6600
  function applySettings(state, settings) {
6713
- const sources = {
6714
- tdd: settings.tdd,
6715
- verify: settings.verification
6716
- };
6717
- const skills = { ...state.skills };
6718
- for (const id of SKILL_IDS) {
6719
- const flag2 = enabledFlag(sources[id]);
6720
- if (flag2 !== void 0) skills[id] = flag2;
6721
- }
6722
6601
  return {
6723
6602
  ...state,
6724
- skills,
6725
6603
  orchestratorModel: typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : state.orchestratorModel,
6726
6604
  plannerProvider: typeof settings.aiProvider === "string" ? settings.aiProvider : state.plannerProvider,
6727
6605
  plannerEffort: typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : state.plannerEffort,
6728
6606
  maxParallel: typeof settings.maxParallel === "number" ? settings.maxParallel : state.maxParallel,
6729
- runnerTransport: (0, import_core33.isRunnerTransport)(settings.runnerTransport) ? settings.runnerTransport : state.runnerTransport,
6730
6607
  allowlist: settings.modelAllowlist && typeof settings.modelAllowlist === "object" ? settings.modelAllowlist : state.allowlist
6731
6608
  };
6732
6609
  }
@@ -6873,16 +6750,15 @@ function reduceAction(state, action) {
6873
6750
  if (update === void 0) return t;
6874
6751
  const idleSince = update.idleSince ?? null;
6875
6752
  const isolation = update.isolation ?? t.isolation;
6876
- const transport = update.transport;
6877
6753
  const awaitingReason = update.awaitingReason;
6878
6754
  const checkpoint = update.checkpoint;
6879
6755
  const continuable = update.continuable === true;
6880
6756
  const awaitingApproval = update.awaitingApproval;
6881
6757
  const mergeGate = update.mergeGate;
6882
6758
  const forcedPastGate = update.forcedPastGate;
6883
- if (update.status !== t.status || idleSince !== (t.idleSince ?? null) || !sameIsolation(isolation, t.isolation) || !sameTransport(transport, t.transport) || awaitingReason !== t.awaitingReason || checkpoint !== t.checkpoint || continuable !== (t.continuable ?? false) || awaitingApproval !== t.awaitingApproval || !sameList(mergeGate, t.mergeGate) || !sameList(forcedPastGate, t.forcedPastGate)) {
6759
+ if (update.status !== t.status || idleSince !== (t.idleSince ?? null) || !sameIsolation(isolation, t.isolation) || awaitingReason !== t.awaitingReason || checkpoint !== t.checkpoint || continuable !== (t.continuable ?? false) || awaitingApproval !== t.awaitingApproval || !sameList(mergeGate, t.mergeGate) || !sameList(forcedPastGate, t.forcedPastGate)) {
6884
6760
  changed = true;
6885
- return { ...t, status: update.status, idleSince, isolation, transport, awaitingReason, checkpoint, continuable, awaitingApproval, mergeGate, forcedPastGate };
6761
+ return { ...t, status: update.status, idleSince, isolation, awaitingReason, checkpoint, continuable, awaitingApproval, mergeGate, forcedPastGate };
6886
6762
  }
6887
6763
  return t;
6888
6764
  });
@@ -7023,9 +6899,6 @@ function reduceAction(state, action) {
7023
6899
  return step(disarmStop(state));
7024
6900
  }
7025
6901
  }
7026
- function sameTransport(a, b) {
7027
- return a?.kind === b?.kind && a?.fallback === b?.fallback;
7028
- }
7029
6902
  function sameList(a, b) {
7030
6903
  return (a ?? []).join("\0") === (b ?? []).join("\0");
7031
6904
  }
@@ -7095,7 +6968,11 @@ function submit(state) {
7095
6968
  if (!text) return step(state);
7096
6969
  const cleared = { ...state, editor: commit(state.editor) };
7097
6970
  const command = parseSlash(text);
7098
- if (command && findCommand(command.name)?.source !== "skill") return runCommand(cleared, command);
6971
+ if (command) {
6972
+ const known = findCommand(command.name);
6973
+ if (known && known.source !== "skill") return runCommand(cleared, command);
6974
+ if (state.taskView) return known ? skillInTaskView(cleared, command.name) : unknownCommand(cleared, command.name);
6975
+ }
7099
6976
  if (state.taskView) {
7100
6977
  if (!state.sessionId) return step(state);
7101
6978
  const taskId = state.taskView.taskId;
@@ -7162,7 +7039,7 @@ function createApp(deps) {
7162
7039
  }
7163
7040
 
7164
7041
  // src/tui/effects.ts
7165
- var import_child_process4 = require("child_process");
7042
+ var import_child_process3 = require("child_process");
7166
7043
  var import_core36 = require("@ordewell/core");
7167
7044
 
7168
7045
  // src/tui/inbound.ts
@@ -7173,6 +7050,7 @@ var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
7173
7050
  "planner_turn_ended",
7174
7051
  "planner_text_delta",
7175
7052
  "planner_thinking_delta",
7053
+ "planner_skill_loaded",
7176
7054
  "planner_text_retracted",
7177
7055
  "plan_token",
7178
7056
  "plan_generated",
@@ -7380,7 +7258,6 @@ function dispatchLifecycle(dispatch, event, sessionId) {
7380
7258
  status: String(task.status),
7381
7259
  idleSince: task.idleSince ?? null,
7382
7260
  isolation: task.isolation,
7383
- transport: task.transport,
7384
7261
  awaitingReason: (0, import_core35.isAwaitingReason)(task.awaitingReason) ? task.awaitingReason : void 0,
7385
7262
  ...typeof task.checkpoint === "string" && task.checkpoint ? { checkpoint: sanitize(task.checkpoint) } : {},
7386
7263
  ...task.continuable ? { continuable: true } : {},
@@ -7589,18 +7466,6 @@ async function perform(effect, deps) {
7589
7466
  if (effect.answer === "approve") await api.approveTaskCheckpoint(effect.sessionId, effect.taskId);
7590
7467
  else await api.rejectTaskCheckpoint(effect.sessionId, effect.taskId, effect.reason);
7591
7468
  return;
7592
- // A task with a saved log ran structured, whatever its `transport` says
7593
- // after a reload; it has no tmux window, so open the log rather than fail.
7594
- case "openTaskTerminal": {
7595
- const saved = await api.getTaskLogAttempts(effect.sessionId, effect.taskId, workspace).catch(() => []);
7596
- if (saved.length > 0) {
7597
- dispatch({ type: "taskViewRequested", sessionId: effect.sessionId, taskId: effect.taskId });
7598
- return;
7599
- }
7600
- const result = await deps.openTerminal(effect.sessionId, effect.taskId);
7601
- dispatch(result.ok ? { type: "notice", message: result.message } : { type: "failed", message: result.message });
7602
- return;
7603
- }
7604
7469
  // The saved log is read off disk (ADR-0018, P1) so the view opens with the
7605
7470
  // task's history; the live `task_log` stream then catches it up in place.
7606
7471
  case "openTaskLog": {
@@ -7652,18 +7517,6 @@ async function perform(effect, deps) {
7652
7517
  await api.interruptTask(effect.sessionId, effect.taskId);
7653
7518
  dispatch({ type: "notice", message: "Interrupting the task\u2026" });
7654
7519
  return;
7655
- case "command": {
7656
- const result = await api.sendCommand(effect.name, { action: effect.action });
7657
- if (result.settings) dispatch({ type: "settingsLoaded", settings: result.settings });
7658
- dispatch({ type: "notice", message: `${effect.name} is ${effect.action}.` });
7659
- return;
7660
- }
7661
- case "setTransport": {
7662
- const result = await api.sendCommand("transport", { action: effect.transport });
7663
- if (result.settings) dispatch({ type: "settingsLoaded", settings: result.settings });
7664
- dispatch({ type: "notice", message: `Runner transport is ${effect.transport} \u2014 it applies from the next run.` });
7665
- return;
7666
- }
7667
7520
  case "setModel":
7668
7521
  await api.updateSettings({ orchestratorModel: effect.modelId });
7669
7522
  deps.setEnvVar("ORCHESTRATOR_MODEL", effect.modelId);
@@ -7859,7 +7712,7 @@ async function perform(effect, deps) {
7859
7712
  }
7860
7713
  }
7861
7714
  var pipeToClipboardDefault = (command, text) => {
7862
- (0, import_child_process4.execSync)(command, { input: text, stdio: ["pipe", "ignore", "ignore"] });
7715
+ (0, import_child_process3.execSync)(command, { input: text, stdio: ["pipe", "ignore", "ignore"] });
7863
7716
  };
7864
7717
  function osc52(text) {
7865
7718
  return `\x1B]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`;
@@ -7885,8 +7738,8 @@ function copySelection(deps, text) {
7885
7738
  async function withExecutionStream(deps, sessionId, request) {
7886
7739
  let settleReady = () => {
7887
7740
  };
7888
- const streamReady = new Promise((resolve3, reject) => {
7889
- settleReady = (error) => error ? reject(error) : resolve3();
7741
+ const streamReady = new Promise((resolve4, reject) => {
7742
+ settleReady = (error) => error ? reject(error) : resolve4();
7890
7743
  });
7891
7744
  const inbound = inboundFor(deps.api, deps.dispatch, sessionId);
7892
7745
  const stream = deps.api.streamExecution(sessionId, inbound.execution(), settleReady);
@@ -7928,7 +7781,7 @@ function restoredChat(plan, sessionId) {
7928
7781
  function lastAssistantMessage(plan) {
7929
7782
  const history = plan?.conversationHistory ?? [];
7930
7783
  for (let i = history.length - 1; i >= 0; i--) {
7931
- if (history[i].role === "assistant") {
7784
+ if (history[i].role === "assistant" && history[i].kind !== "skill_load") {
7932
7785
  const { content, timestamp } = history[i];
7933
7786
  return { content, ...timestamp ? { timestamp } : {} };
7934
7787
  }
@@ -8272,7 +8125,9 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
8272
8125
  );
8273
8126
  process.exit(1);
8274
8127
  }
8275
- registerSkillCommands((0, import_core37.createSkillsService)(workspace).listSkills());
8128
+ const discoveredSkills = (0, import_core37.createSkillsService)(workspace, new import_core37.EnvConfig().workspaceRepos).listSkills();
8129
+ registerSkillCommands(discoveredSkills);
8130
+ const taskSkills = discoveredSkills.filter((s) => s.appliesTo === "task").map((s) => ({ name: s.name, description: s.description }));
8276
8131
  const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
8277
8132
  const targetPort = explicitPort ? resolvePort(subArgs) : await findFreePort();
8278
8133
  const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(targetPort, { detached: false });
@@ -8282,7 +8137,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
8282
8137
  const autoMode = process.env.ORDEWELL_AUTONOMOUS_MODE;
8283
8138
  const mouseCapture = process.env.ORDEWELL_TUI_MOUSE !== "false" && process.env.ORDEWELL_TUI_MOUSE !== "0";
8284
8139
  const app = createApp({
8285
- initial: { workspace, autonomous: autoMode !== "false" && autoMode !== "0", mouseCapture },
8140
+ initial: { workspace, autonomous: autoMode !== "false" && autoMode !== "0", mouseCapture, taskSkills },
8286
8141
  draw: (frame2) => terminal?.draw(frame2),
8287
8142
  perform: (effect) => runEffect(effect, {
8288
8143
  api,
@@ -8294,7 +8149,6 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
8294
8149
  writeEnvVar(findEnvFile(), key, value);
8295
8150
  process.env[key] = value;
8296
8151
  },
8297
- openTerminal: (sessionId, taskId) => openTaskTerminal(port, sessionId, taskId),
8298
8152
  setMouseCapture: (enabled) => terminal?.setMouse(enabled),
8299
8153
  // The alt screen is up with the cursor hidden, so an OSC 52 write shows
8300
8154
  // nothing — the terminal reads it and answers by taking the clipboard.
@@ -8308,7 +8162,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
8308
8162
  }),
8309
8163
  onExit: () => shutdown(0),
8310
8164
  // The same pointer `ordewell plan` leaves, so `ordewell handoff`,
8311
- // `terminal <n>` and the other one-shot commands act on the session this
8165
+ // The one-shot commands act on the session this
8312
8166
  // TUI is showing instead of saying there is none.
8313
8167
  onSessionChange: (sessionId, state) => {
8314
8168
  try {
@@ -8366,12 +8220,9 @@ var COMMANDS = {
8366
8220
  key: handleKey,
8367
8221
  runners: handleRunners,
8368
8222
  auto: handleAuto,
8369
- transport: handleTransport,
8370
8223
  refresh: handleRefresh,
8371
8224
  setup: handleSetup,
8372
- plugins: handlePlugins,
8373
- "tdd": handleTdd,
8374
- "verify": handleVerify,
8225
+ skills: handleSkills,
8375
8226
  "mark-complete": handleMarkComplete,
8376
8227
  complete: handleMarkComplete,
8377
8228
  uncomplete: handleUncomplete,
@@ -8389,8 +8240,8 @@ var COMMANDS = {
8389
8240
  "task-effort": handleTaskEffort,
8390
8241
  "task-mode": handleTaskMode,
8391
8242
  "task-deps": handleTaskDeps,
8243
+ "task-skills": handleTaskSkills,
8392
8244
  "task-ops": handleTaskOps,
8393
- terminal: handleTerminal,
8394
8245
  "sessions": handleSessions,
8395
8246
  fork: handleFork,
8396
8247
  compact: handleCompact,
@@ -8401,10 +8252,10 @@ var COMMANDS = {
8401
8252
 
8402
8253
  // src/version.ts
8403
8254
  var import_fs7 = require("fs");
8404
- var import_path5 = require("path");
8255
+ var import_path6 = require("path");
8405
8256
  function cliVersion() {
8406
8257
  try {
8407
- const pkg = JSON.parse((0, import_fs7.readFileSync)((0, import_path5.join)(__dirname, "..", "package.json"), "utf8"));
8258
+ const pkg = JSON.parse((0, import_fs7.readFileSync)((0, import_path6.join)(__dirname, "..", "package.json"), "utf8"));
8408
8259
  const version = pkg.version;
8409
8260
  return typeof version === "string" && version ? version : "unknown";
8410
8261
  } catch {