@zhijiewang/openharness 2.5.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,7 +21,7 @@ AI coding agent in your terminal. Works with any LLM -- free local models or clo
21
21
  <img src="assets/openharness_v0.11.1_4.gif" alt="OpenHarness demo" width="800" />
22
22
  </p>
23
23
 
24
- [![npm version](https://img.shields.io/npm/v/@zhijiewang/openharness)](https://www.npmjs.com/package/@zhijiewang/openharness) [![npm downloads](https://img.shields.io/npm/dm/@zhijiewang/openharness)](https://www.npmjs.com/package/@zhijiewang/openharness) [![license](https://img.shields.io/npm/l/@zhijiewang/openharness)](LICENSE) ![tests](https://img.shields.io/badge/tests-784-brightgreen) ![tools](https://img.shields.io/badge/tools-41-blue) ![Node.js 18+](https://img.shields.io/badge/node-18%2B-green) ![TypeScript](https://img.shields.io/badge/typescript-strict-blue) [![GitHub stars](https://img.shields.io/github/stars/zhijiewong/openharness)](https://github.com/zhijiewong/openharness) [![GitHub issues](https://img.shields.io/github/issues-raw/zhijiewong/openharness)](https://github.com/zhijiewong/openharness/issues) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](https://github.com/zhijiewong/openharness/pulls)
24
+ [![npm version](https://img.shields.io/npm/v/@zhijiewang/openharness)](https://www.npmjs.com/package/@zhijiewang/openharness) [![npm downloads](https://img.shields.io/npm/dm/@zhijiewang/openharness)](https://www.npmjs.com/package/@zhijiewang/openharness) [![license](https://img.shields.io/npm/l/@zhijiewang/openharness)](LICENSE) ![tests](https://img.shields.io/badge/tests-890-brightgreen) ![tools](https://img.shields.io/badge/tools-42-blue) ![Node.js 18+](https://img.shields.io/badge/node-18%2B-green) ![TypeScript](https://img.shields.io/badge/typescript-strict-blue) [![GitHub stars](https://img.shields.io/github/stars/zhijiewong/openharness)](https://github.com/zhijiewong/openharness) [![GitHub issues](https://img.shields.io/github/issues-raw/zhijiewong/openharness)](https://github.com/zhijiewong/openharness/issues) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](https://github.com/zhijiewong/openharness/pulls)
25
25
 
26
26
  ---
27
27
 
@@ -0,0 +1,6 @@
1
+ /**
2
+ * AI commands — /plan, /review, /roles, /agents, /plugins, /btw, /loop
3
+ */
4
+ import type { CommandHandler } from "./types.js";
5
+ export declare function registerAICommands(register: (name: string, description: string, handler: CommandHandler) => void): void;
6
+ //# sourceMappingURL=ai.d.ts.map
@@ -0,0 +1,244 @@
1
+ /**
2
+ * AI commands — /plan, /review, /roles, /agents, /plugins, /btw, /loop
3
+ */
4
+ import { gitDiff, isGitRepo } from "../git/index.js";
5
+ import { handleCybergotchiCommand } from "./cybergotchi.js";
6
+ export function registerAICommands(register) {
7
+ register("btw", "Ask a side question (ephemeral, no tools, not saved to history)", (args) => {
8
+ if (!args.trim()) {
9
+ return { output: "Usage: /btw <your question>", handled: true };
10
+ }
11
+ return {
12
+ output: `[btw] ${args.trim()}`,
13
+ handled: false,
14
+ prependToPrompt: `[Side question — answer briefly without using any tools. This is ephemeral and not part of the main conversation.]\n\n${args.trim()}`,
15
+ };
16
+ });
17
+ register("loop", "Run a prompt repeatedly with self-paced timing", (args) => {
18
+ const input = args.trim();
19
+ if (!input) {
20
+ return {
21
+ output: "Usage: /loop [interval] <prompt or /command>\n\nExamples:\n /loop check if the build passed\n /loop 5m /review\n\nOmit the interval to let the model self-pace via ScheduleWakeup.",
22
+ handled: true,
23
+ };
24
+ }
25
+ const intervalMatch = input.match(/^(\d+)(s|m|h)\s+(.+)$/);
26
+ let intervalMs = null;
27
+ let prompt;
28
+ if (intervalMatch) {
29
+ const [, num, unit, rest] = intervalMatch;
30
+ const multipliers = { s: 1000, m: 60000, h: 3600000 };
31
+ intervalMs = parseInt(num, 10) * multipliers[unit];
32
+ prompt = rest;
33
+ }
34
+ else {
35
+ prompt = input;
36
+ }
37
+ const mode = intervalMs
38
+ ? `Fixed interval: ${intervalMatch[1]}${intervalMatch[2]}`
39
+ : "Dynamic (model self-paces via ScheduleWakeup)";
40
+ return {
41
+ output: `[loop] ${mode}\nPrompt: ${prompt}`,
42
+ handled: false,
43
+ prependToPrompt: intervalMs
44
+ ? `You are in LOOP MODE (fixed interval: ${intervalMs / 1000}s). Execute this task, then use ScheduleWakeup with delaySeconds=${intervalMs / 1000} to schedule the next iteration.\n\nTask: ${prompt}`
45
+ : `You are in LOOP MODE (dynamic pacing). Execute this task, then use ScheduleWakeup to schedule the next iteration at an appropriate interval. Choose your delay based on what you're waiting for. Omit the ScheduleWakeup call to end the loop.\n\nTask: ${prompt}`,
46
+ };
47
+ });
48
+ register("plan", "Enter plan mode", (_args) => {
49
+ const task = _args.trim();
50
+ if (!task) {
51
+ return { output: "Usage: /plan <what you want to build>", handled: true };
52
+ }
53
+ return {
54
+ output: `[plan mode] ${task}`,
55
+ handled: false,
56
+ prependToPrompt: `You are in PLAN MODE. Do NOT write any code yet.\n\n1. Call EnterPlanMode to create a plan file in .oh/plans/\n2. Write your detailed implementation plan to that file (files to create/modify, key functions/types, data flow, edge cases)\n3. When the plan is complete, call ExitPlanMode to signal readiness for review\n\nTask: ${task}`,
57
+ };
58
+ });
59
+ register("review", "Review recent code changes", () => {
60
+ if (!isGitRepo()) {
61
+ return { output: "Not a git repository.", handled: true };
62
+ }
63
+ const diff = gitDiff();
64
+ if (!diff)
65
+ return { output: "No changes to review.", handled: true };
66
+ const lines = diff.split("\n").length;
67
+ return {
68
+ output: `[review] ${lines} lines of diff`,
69
+ handled: false,
70
+ prependToPrompt: `Review these uncommitted changes and give feedback on correctness, style, and potential issues:\n\n\`\`\`diff\n${diff}\n\`\`\`\n\n`,
71
+ };
72
+ });
73
+ register("roles", "List available agent specialization roles", () => {
74
+ const { listRoles } = require("../agents/roles.js");
75
+ const roles = listRoles();
76
+ const lines = ["Available agent roles:\n"];
77
+ for (const role of roles) {
78
+ lines.push(` ${role.id.padEnd(18)} ${role.name}`);
79
+ lines.push(` ${"".padEnd(18)} ${role.description}`);
80
+ if (role.suggestedTools?.length) {
81
+ lines.push(` ${"".padEnd(18)} Tools: ${role.suggestedTools.join(", ")}`);
82
+ }
83
+ lines.push("");
84
+ }
85
+ lines.push("Usage: Agent({ subagent_type: 'code-reviewer', prompt: '...' })");
86
+ return { output: lines.join("\n"), handled: true };
87
+ });
88
+ register("agents", "Discover running openHarness agents on this machine", () => {
89
+ const { discoverAgents } = require("../services/a2a.js");
90
+ const agents = discoverAgents();
91
+ if (agents.length === 0) {
92
+ return {
93
+ output: "No other openHarness agents running on this machine.\n\nOther oh sessions will appear here automatically via the A2A protocol.",
94
+ handled: true,
95
+ };
96
+ }
97
+ const lines = [`Running Agents (${agents.length}):\n`];
98
+ for (const agent of agents) {
99
+ const age = Math.round((Date.now() - agent.registeredAt) / 60_000);
100
+ lines.push(` ${agent.name}`);
101
+ lines.push(` ID: ${agent.id}`);
102
+ lines.push(` Provider: ${agent.provider ?? "unknown"} / ${agent.model ?? "unknown"}`);
103
+ lines.push(` Dir: ${agent.workingDir ?? "unknown"}`);
104
+ lines.push(` Endpoint: ${agent.endpoint.type}${agent.endpoint.port ? `:${agent.endpoint.port}` : ""}`);
105
+ lines.push(` Uptime: ${age}m`);
106
+ lines.push(` Caps: ${agent.capabilities.map((c) => c.name).join(", ")}`);
107
+ lines.push("");
108
+ }
109
+ lines.push("Send messages with: Agent({ prompt: 'ask the other agent...', allowed_tools: ['SendMessage'] })");
110
+ return { output: lines.join("\n"), handled: true };
111
+ });
112
+ register("plugins", "Manage plugins: list, search, install, uninstall, marketplace", (args) => {
113
+ const { discoverPlugins, discoverSkills } = require("../harness/plugins.js");
114
+ const { searchMarketplace, installPlugin, uninstallPlugin, getInstalledPlugins, listMarketplaces, addMarketplace, removeMarketplace, formatMarketplaceSearch, formatInstalledPlugins, } = require("../harness/marketplace.js");
115
+ const parts = args.trim().split(/\s+/);
116
+ const subcommand = parts[0] ?? "";
117
+ const rest = parts.slice(1).join(" ");
118
+ if (subcommand === "marketplace") {
119
+ const action = parts[1];
120
+ const source = parts.slice(2).join(" ");
121
+ if (action === "add" && source) {
122
+ const mp = addMarketplace(source);
123
+ if (mp)
124
+ return { output: `Added marketplace "${mp.name}" (${mp.plugins.length} plugins)`, handled: true };
125
+ return { output: `Failed to add marketplace from "${source}"`, handled: true };
126
+ }
127
+ if (action === "remove" && source) {
128
+ return {
129
+ output: removeMarketplace(source) ? `Removed marketplace "${source}"` : `Marketplace "${source}" not found`,
130
+ handled: true,
131
+ };
132
+ }
133
+ const mps = listMarketplaces();
134
+ if (mps.length === 0) {
135
+ return {
136
+ output: "No marketplaces configured.\n\nAdd one:\n /plugins marketplace add owner/repo\n /plugins marketplace add https://example.com/plugins",
137
+ handled: true,
138
+ };
139
+ }
140
+ const lines = [`Marketplaces (${mps.length}):\n`];
141
+ for (const mp of mps) {
142
+ lines.push(` ${mp.name} — ${mp.plugins.length} plugins`);
143
+ }
144
+ return { output: lines.join("\n"), handled: true };
145
+ }
146
+ if (subcommand === "search") {
147
+ const query = rest || "all";
148
+ const results = searchMarketplace(query === "all" ? "" : query);
149
+ return { output: formatMarketplaceSearch(results), handled: true };
150
+ }
151
+ if (subcommand === "install" && rest) {
152
+ const [name, marketplace] = rest.split("@");
153
+ const result = installPlugin(name, marketplace);
154
+ if (result) {
155
+ return {
156
+ output: `Installed ${result.name}@${result.version} from ${result.marketplace}\nCached at: ${result.cachePath}`,
157
+ handled: true,
158
+ };
159
+ }
160
+ return {
161
+ output: `Failed to install "${rest}". Is it listed in a marketplace?\nRun /plugins search ${name} to check.`,
162
+ handled: true,
163
+ };
164
+ }
165
+ if (subcommand === "uninstall" && rest) {
166
+ return { output: uninstallPlugin(rest) ? `Uninstalled "${rest}"` : `Plugin "${rest}" not found`, handled: true };
167
+ }
168
+ const plugins = discoverPlugins();
169
+ const skills = discoverSkills();
170
+ const marketplacePlugins = getInstalledPlugins();
171
+ const lines = [];
172
+ if (marketplacePlugins.length > 0) {
173
+ lines.push(formatInstalledPlugins(marketplacePlugins));
174
+ lines.push("");
175
+ }
176
+ if (plugins.length > 0) {
177
+ lines.push(`Local Plugins (${plugins.length}):`);
178
+ for (const p of plugins) {
179
+ lines.push(` ${p.name}@${p.version} — ${p.description || "no description"}`);
180
+ }
181
+ lines.push("");
182
+ }
183
+ if (skills.length > 0) {
184
+ lines.push(`Skills (${skills.length}):`);
185
+ for (const s of skills) {
186
+ lines.push(` ${s.source}:${s.name} — ${s.description || ""}`);
187
+ }
188
+ lines.push("");
189
+ }
190
+ if (lines.length === 0) {
191
+ lines.push("No plugins or skills installed.");
192
+ }
193
+ lines.push("");
194
+ lines.push("Commands:");
195
+ lines.push(" /plugins search <query> Search marketplaces");
196
+ lines.push(" /plugins install <name> Install from marketplace");
197
+ lines.push(" /plugins uninstall <name> Remove a plugin");
198
+ lines.push(" /plugins marketplace add <src> Add a marketplace");
199
+ lines.push(" /plugins marketplace List marketplaces");
200
+ return { output: lines.join("\n"), handled: true };
201
+ });
202
+ register("cybergotchi", "Manage your cybergotchi — feed · pet · rest · status · rename · reset", (args) => {
203
+ return handleCybergotchiCommand(args);
204
+ });
205
+ register("summarize", "Summarize the current conversation", (_args, ctx) => {
206
+ if (ctx.messages.length === 0) {
207
+ return { output: "No messages to summarize.", handled: true };
208
+ }
209
+ return {
210
+ output: `[summarize] ${ctx.messages.length} messages`,
211
+ handled: false,
212
+ prependToPrompt: `Summarize this conversation concisely. Highlight the main topics discussed, decisions made, and any pending action items. Be brief but thorough.`,
213
+ };
214
+ });
215
+ register("explain", "Explain a file or concept", (args) => {
216
+ const topic = args.trim();
217
+ if (!topic) {
218
+ return {
219
+ output: "Usage: /explain <file-path or concept>\n\nExamples:\n /explain src/index.ts\n /explain dependency injection\n /explain the authentication flow",
220
+ handled: true,
221
+ };
222
+ }
223
+ return {
224
+ output: `[explain] ${topic}`,
225
+ handled: false,
226
+ prependToPrompt: `Explain the following clearly and concisely. If it's a file path, read and explain its purpose, structure, and key functions. If it's a concept, explain it in the context of this project.\n\nTopic: ${topic}`,
227
+ };
228
+ });
229
+ register("fix", "Fix a specific issue", (args) => {
230
+ const issue = args.trim();
231
+ if (!issue) {
232
+ return {
233
+ output: "Usage: /fix <issue description>\n\nExamples:\n /fix the failing test in auth.test.ts\n /fix TypeScript errors in src/utils.ts\n /fix the broken import on line 42",
234
+ handled: true,
235
+ };
236
+ }
237
+ return {
238
+ output: `[fix] ${issue}`,
239
+ handled: false,
240
+ prependToPrompt: `Fix the following issue. Diagnose the root cause, implement the fix, and verify it works. Be precise and minimal in your changes.\n\nIssue: ${issue}`,
241
+ };
242
+ });
243
+ }
244
+ //# sourceMappingURL=ai.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Git commands — /diff, /undo, /rewind, /commit, /log
3
+ */
4
+ import type { CommandHandler } from "./types.js";
5
+ export declare function registerGitCommands(register: (name: string, description: string, handler: CommandHandler) => void): void;
6
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Git commands — /diff, /undo, /rewind, /commit, /log
3
+ */
4
+ import { execSync } from "node:child_process";
5
+ import { gitBranch, gitCommit, gitDiff, gitLog, gitUndo, isGitRepo } from "../git/index.js";
6
+ export function registerGitCommands(register) {
7
+ register("diff", "Show uncommitted git changes", () => {
8
+ if (!isGitRepo()) {
9
+ return { output: "Not a git repository.", handled: true };
10
+ }
11
+ const diff = gitDiff();
12
+ return { output: diff || "No uncommitted changes.", handled: true };
13
+ });
14
+ register("undo", "Undo last AI commit", () => {
15
+ if (!isGitRepo()) {
16
+ return { output: "Not a git repository.", handled: true };
17
+ }
18
+ const success = gitUndo();
19
+ return {
20
+ output: success ? "Undone. Last AI commit reverted." : "Nothing to undo (last commit wasn't from OpenHarness).",
21
+ handled: true,
22
+ };
23
+ });
24
+ register("rewind", "Restore files from checkpoint (interactive picker or last)", (args) => {
25
+ const { rewindLastCheckpoint, listCheckpoints, checkpointCount } = require("../harness/checkpoints.js");
26
+ const checkpoints = listCheckpoints();
27
+ if (checkpoints.length === 0) {
28
+ return { output: "No checkpoints available. Checkpoints are created before file modifications.", handled: true };
29
+ }
30
+ const idx = args.trim();
31
+ if (!idx) {
32
+ const lines = [`Checkpoints (${checkpoints.length}):\n`];
33
+ for (let i = checkpoints.length - 1; i >= 0; i--) {
34
+ const cp = checkpoints[i];
35
+ const age = Math.round((Date.now() - cp.timestamp) / 60_000);
36
+ lines.push(` ${i + 1}. [${age}m ago] ${cp.description}`);
37
+ lines.push(` Files: ${cp.files.join(", ")}`);
38
+ }
39
+ lines.push("");
40
+ lines.push("Usage: /rewind <number> to restore a specific checkpoint");
41
+ lines.push(" /rewind last to restore the most recent");
42
+ return { output: lines.join("\n"), handled: true };
43
+ }
44
+ if (idx === "last") {
45
+ const cp = rewindLastCheckpoint();
46
+ if (!cp)
47
+ return { output: "No checkpoints.", handled: true };
48
+ return {
49
+ output: `Rewound: ${cp.description}\nRestored ${cp.files.length} file(s): ${cp.files.join(", ")}\n${checkpointCount()} checkpoint(s) remaining.`,
50
+ handled: true,
51
+ };
52
+ }
53
+ const num = parseInt(idx, 10);
54
+ if (Number.isNaN(num) || num < 1 || num > checkpoints.length) {
55
+ return { output: `Invalid checkpoint number. Use 1-${checkpoints.length}.`, handled: true };
56
+ }
57
+ let restored = 0;
58
+ while (checkpointCount() >= num) {
59
+ const cp = rewindLastCheckpoint();
60
+ if (!cp)
61
+ break;
62
+ restored++;
63
+ if (checkpointCount() < num)
64
+ break;
65
+ }
66
+ return {
67
+ output: `Rewound ${restored} checkpoint(s) to point #${num}.\n${checkpointCount()} checkpoint(s) remaining.`,
68
+ handled: true,
69
+ };
70
+ });
71
+ register("commit", "Create a git commit", (args) => {
72
+ if (!isGitRepo()) {
73
+ return { output: "Not a git repository.", handled: true };
74
+ }
75
+ const message = args.trim() || "manual commit";
76
+ const success = gitCommit(message);
77
+ return { output: success ? `Committed: ${message}` : "Nothing to commit.", handled: true };
78
+ });
79
+ register("log", "Show recent git commits", () => {
80
+ if (!isGitRepo()) {
81
+ return { output: "Not a git repository.", handled: true };
82
+ }
83
+ return { output: gitLog(10) || "No commits yet.", handled: true };
84
+ });
85
+ register("review-pr", "Review a pull request", (args) => {
86
+ const pr = args.trim();
87
+ if (!pr) {
88
+ return { output: "Usage: /review-pr <number or URL>", handled: true };
89
+ }
90
+ return {
91
+ output: `[review-pr] ${pr}`,
92
+ handled: false,
93
+ prependToPrompt: `Review pull request ${pr}. Use the Bash tool to run 'gh pr view ${pr} --json title,body,additions,deletions,files' and 'gh pr diff ${pr}' to fetch the PR details and diff. Then provide a thorough code review covering correctness, style, and potential issues.`,
94
+ };
95
+ });
96
+ register("pr-comments", "View comments on a pull request", (args) => {
97
+ const pr = args.trim();
98
+ if (!pr) {
99
+ return { output: "Usage: /pr-comments <number or URL>", handled: true };
100
+ }
101
+ return {
102
+ output: `[pr-comments] ${pr}`,
103
+ handled: false,
104
+ prependToPrompt: `Fetch and summarize the comments on pull request ${pr}. Use the Bash tool to run 'gh pr view ${pr} --json comments,reviews' to get all comments and review feedback. Present a clear summary of the discussion.`,
105
+ };
106
+ });
107
+ register("release-notes", "Generate release notes from recent commits", (args) => {
108
+ if (!isGitRepo()) {
109
+ return { output: "Not a git repository.", handled: true };
110
+ }
111
+ const range = args.trim() || "HEAD~10..HEAD";
112
+ let log;
113
+ try {
114
+ log = execSync(`git log --oneline ${range}`, { encoding: "utf-8" }).trim();
115
+ }
116
+ catch {
117
+ log = gitLog(10) || "";
118
+ }
119
+ if (!log)
120
+ return { output: "No commits found for release notes.", handled: true };
121
+ return {
122
+ output: `[release-notes] ${range}`,
123
+ handled: false,
124
+ prependToPrompt: `Generate release notes from these commits. Group by category (features, fixes, chores). Use markdown formatting.\n\nCommits:\n${log}`,
125
+ };
126
+ });
127
+ register("stash", "Show git stash list", () => {
128
+ if (!isGitRepo()) {
129
+ return { output: "Not a git repository.", handled: true };
130
+ }
131
+ let stashList;
132
+ try {
133
+ stashList = execSync("git stash list", { encoding: "utf-8" }).trim();
134
+ }
135
+ catch {
136
+ return { output: "Could not retrieve stash list.", handled: true };
137
+ }
138
+ if (!stashList)
139
+ return { output: "No stashes found.", handled: true };
140
+ return { output: `Git stashes:\n${stashList}`, handled: true };
141
+ });
142
+ register("branch", "Show or switch git branch", (args) => {
143
+ if (!isGitRepo()) {
144
+ return { output: "Not a git repository.", handled: true };
145
+ }
146
+ const target = args.trim();
147
+ if (!target) {
148
+ const current = gitBranch();
149
+ let branches;
150
+ try {
151
+ branches = execSync("git branch --list", { encoding: "utf-8" }).trim();
152
+ }
153
+ catch {
154
+ branches = current;
155
+ }
156
+ return { output: `Current branch: ${current}\n\n${branches}`, handled: true };
157
+ }
158
+ try {
159
+ execSync(`git checkout ${target}`, { encoding: "utf-8" });
160
+ return { output: `Switched to branch: ${target}`, handled: true };
161
+ }
162
+ catch {
163
+ return { output: `Failed to switch to branch: ${target}. Does it exist?`, handled: true };
164
+ }
165
+ });
166
+ }
167
+ //# sourceMappingURL=git.js.map
@@ -3,38 +3,17 @@
3
3
  *
4
4
  * Commands are processed in the REPL before being sent to the LLM.
5
5
  * If input starts with /, it's treated as a command.
6
+ *
7
+ * Command implementations are split into domain-specific modules:
8
+ * session.ts — /clear, /compact, /export, /history, /browse, /resume, /fork, /pin, /unpin
9
+ * git.ts — /diff, /undo, /rewind, /commit, /log
10
+ * info.ts — /help, /cost, /status, /config, /files, /model, /memory, /doctor, /context, /mcp, /init
11
+ * settings.ts — /theme, /companion, /fast, /keys, /effort, /sandbox, /permissions, /allowed-tools
12
+ * ai.ts — /plan, /review, /roles, /agents, /plugins, /btw, /loop, /cybergotchi
13
+ * skills.ts — /skill-create, /skill-delete, /skill-edit, /skill-search, /skill-install
6
14
  */
7
- import type { Message } from "../types/message.js";
8
- export type CommandResult = {
9
- /** Text output to display */
10
- output: string;
11
- /** If true, don't send to LLM */
12
- handled: boolean;
13
- /** If set, clear messages */
14
- clearMessages?: boolean;
15
- /** If set, update model */
16
- newModel?: string;
17
- /** If set, replace messages with compacted version */
18
- compactedMessages?: Message[];
19
- /** If true, open the cybergotchi setup UI */
20
- openCybergotchiSetup?: boolean;
21
- /** If set, resume this session ID */
22
- resumeSessionId?: string;
23
- /** If set, prepend this text to the user's prompt before sending to LLM */
24
- prependToPrompt?: string;
25
- /** If set, toggle fast mode */
26
- toggleFastMode?: boolean;
27
- };
28
- export type CommandContext = {
29
- messages: Message[];
30
- model: string;
31
- providerName: string;
32
- permissionMode: string;
33
- totalCost: number;
34
- totalInputTokens: number;
35
- totalOutputTokens: number;
36
- sessionId: string;
37
- };
15
+ export type { CommandContext, CommandHandler, CommandResult } from "./types.js";
16
+ import type { CommandContext, CommandResult } from "./types.js";
38
17
  /**
39
18
  * Check if input is a slash command. If so, execute it.
40
19
  */