wraptc 1.0.3 → 1.0.4

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 (71) hide show
  1. package/bin/wraptc +4 -4
  2. package/package.json +2 -2
  3. package/src/cli/__tests__/cli.test.ts +337 -0
  4. package/src/cli/index.ts +149 -0
  5. package/src/core/__tests__/fixtures/configs/project-config.json +14 -0
  6. package/src/core/__tests__/fixtures/configs/system-config.json +14 -0
  7. package/src/core/__tests__/fixtures/configs/user-config.json +15 -0
  8. package/src/core/__tests__/integration/integration.test.ts +241 -0
  9. package/src/core/__tests__/integration/mock-coder-adapter.test.ts +243 -0
  10. package/src/core/__tests__/test-utils.ts +136 -0
  11. package/src/core/__tests__/unit/adapters/runner.test.ts +302 -0
  12. package/src/core/__tests__/unit/basic-test.test.ts +44 -0
  13. package/src/core/__tests__/unit/basic.test.ts +12 -0
  14. package/src/core/__tests__/unit/config.test.ts +244 -0
  15. package/src/core/__tests__/unit/error-patterns.test.ts +181 -0
  16. package/src/core/__tests__/unit/memory-monitor.test.ts +354 -0
  17. package/src/core/__tests__/unit/plugin/registry.test.ts +356 -0
  18. package/src/core/__tests__/unit/providers/codex.test.ts +173 -0
  19. package/src/core/__tests__/unit/providers/configurable.test.ts +429 -0
  20. package/src/core/__tests__/unit/providers/gemini.test.ts +251 -0
  21. package/src/core/__tests__/unit/providers/opencode.test.ts +258 -0
  22. package/src/core/__tests__/unit/providers/qwen-code.test.ts +195 -0
  23. package/src/core/__tests__/unit/providers/simple-codex.test.ts +18 -0
  24. package/src/core/__tests__/unit/router.test.ts +967 -0
  25. package/src/core/__tests__/unit/state.test.ts +1079 -0
  26. package/src/core/__tests__/unit/unified/capabilities.test.ts +186 -0
  27. package/src/core/__tests__/unit/wrap-terminalcoder.test.ts +32 -0
  28. package/src/core/adapters/builtin/codex.ts +35 -0
  29. package/src/core/adapters/builtin/gemini.ts +34 -0
  30. package/src/core/adapters/builtin/index.ts +31 -0
  31. package/src/core/adapters/builtin/mock-coder.ts +148 -0
  32. package/src/core/adapters/builtin/qwen.ts +34 -0
  33. package/src/core/adapters/define.ts +48 -0
  34. package/src/core/adapters/index.ts +43 -0
  35. package/src/core/adapters/loader.ts +143 -0
  36. package/src/core/adapters/provider-bridge.ts +190 -0
  37. package/src/core/adapters/runner.ts +437 -0
  38. package/src/core/adapters/types.ts +172 -0
  39. package/src/core/config.ts +290 -0
  40. package/src/core/define-provider.ts +212 -0
  41. package/src/core/error-patterns.ts +147 -0
  42. package/src/core/index.ts +130 -0
  43. package/src/core/memory-monitor.ts +171 -0
  44. package/src/core/plugin/builtin.ts +87 -0
  45. package/src/core/plugin/index.ts +34 -0
  46. package/src/core/plugin/registry.ts +350 -0
  47. package/src/core/plugin/types.ts +209 -0
  48. package/src/core/provider-factory.ts +397 -0
  49. package/src/core/provider-loader.ts +171 -0
  50. package/src/core/providers/codex.ts +56 -0
  51. package/src/core/providers/configurable.ts +637 -0
  52. package/src/core/providers/custom.ts +261 -0
  53. package/src/core/providers/gemini.ts +41 -0
  54. package/src/core/providers/index.ts +383 -0
  55. package/src/core/providers/opencode.ts +168 -0
  56. package/src/core/providers/qwen-code.ts +41 -0
  57. package/src/core/router.ts +370 -0
  58. package/src/core/state.ts +258 -0
  59. package/src/core/types.ts +206 -0
  60. package/src/core/unified/capabilities.ts +184 -0
  61. package/src/core/unified/errors.ts +141 -0
  62. package/src/core/unified/index.ts +29 -0
  63. package/src/core/unified/output.ts +189 -0
  64. package/src/core/wrap-terminalcoder.ts +245 -0
  65. package/src/mcp/__tests__/server.test.ts +295 -0
  66. package/src/mcp/server.ts +284 -0
  67. package/src/test-fixtures/mock-coder.sh +194 -0
  68. package/dist/cli/index.js +0 -16501
  69. package/dist/core/index.js +0 -7531
  70. package/dist/mcp/server.js +0 -14568
  71. package/dist/wraptc-1.0.3.tgz +0 -0
package/bin/wraptc CHANGED
@@ -4,9 +4,9 @@ const args = process.argv.slice(2);
4
4
 
5
5
  // Check if --mcp flag is present or first command is 'mcp'
6
6
  if (args.includes('--mcp') || args[0] === 'mcp') {
7
- // Run MCP server
8
- await import('../dist/mcp/server.js');
7
+ // Run MCP server from source
8
+ import('../src/mcp/server.ts');
9
9
  } else {
10
- // Run CLI
11
- await import('../dist/cli/index.js');
10
+ // Run CLI from source
11
+ import('../src/cli/index.ts');
12
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wraptc",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Unified CLI wrapper for multiple coding AI agents with intelligent routing",
5
5
  "type": "module",
6
6
  "main": "./dist/core/index.js",
@@ -63,7 +63,7 @@
63
63
  "typescript": "^5.0.0"
64
64
  },
65
65
  "files": [
66
- "dist",
66
+ "src",
67
67
  "bin",
68
68
  "README.md",
69
69
  "LICENSE",
@@ -0,0 +1,337 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+ import { WrapTerminalCoder } from "../../core/index.js";
3
+ import { ConfigLoader } from "../../core/index.js";
4
+ import { StateManager } from "../../core/index.js";
5
+ import { Router } from "../../core/index.js";
6
+
7
+ // Mock the core modules
8
+ mock.module("../../core/index.js", () => {
9
+ return {
10
+ WrapTerminalCoder: {
11
+ create: mock(async () => ({
12
+ providers: new Map(),
13
+ config: {},
14
+ stateManager: new StateManager(),
15
+ router: new Router(new Map(), { config: {}, stateManager: new StateManager() }),
16
+ route: mock(async (req: any) => ({
17
+ provider: "gemini",
18
+ text: "Mock response",
19
+ usage: { inputTokens: 10, outputTokens: 20 },
20
+ meta: { elapsedMs: 100 },
21
+ })),
22
+ routeStream: async function* (req: any) {
23
+ yield {
24
+ provider: "gemini",
25
+ text: "Mock stream response",
26
+ usage: { inputTokens: 10, outputTokens: 20 },
27
+ meta: { elapsedMs: 100 },
28
+ };
29
+ },
30
+ getProviderInfo: mock(async () => [
31
+ {
32
+ id: "gemini",
33
+ displayName: "Gemini CLI",
34
+ requestsToday: 5,
35
+ outOfCreditsUntil: undefined,
36
+ },
37
+ ]),
38
+ })),
39
+ },
40
+ ConfigLoader: mock(() => ({
41
+ loadConfig: mock(async () => ({
42
+ routing: { defaultOrder: ["gemini", "qwen-code", "codex"] },
43
+ providers: {},
44
+ credits: { providers: {} },
45
+ })),
46
+ })),
47
+ StateManager: mock(() => ({
48
+ initialize: mock(async () => {}),
49
+ getProviderState: mock(async () => ({
50
+ requestsToday: 0,
51
+ outOfCreditsUntil: undefined,
52
+ lastErrors: [],
53
+ })),
54
+ recordSuccess: mock(async () => {}),
55
+ recordError: mock(async () => {}),
56
+ })),
57
+ Router: mock(() => ({
58
+ route: mock(async (req: any) => ({
59
+ provider: "gemini",
60
+ text: "Mock response",
61
+ })),
62
+ })),
63
+ };
64
+ });
65
+
66
+ describe("CLI Integration", () => {
67
+ describe("Providers Command", () => {
68
+ test("should list providers", async () => {
69
+ const wtc = await WrapTerminalCoder.create();
70
+ const providers = await wtc.getProviderInfo();
71
+
72
+ expect(providers).toBeInstanceOf(Array);
73
+ expect(providers.length).toBeGreaterThan(0);
74
+ expect(providers[0]).toHaveProperty("id");
75
+ expect(providers[0]).toHaveProperty("displayName");
76
+ });
77
+ });
78
+
79
+ describe("Ask Command", () => {
80
+ test("should process basic request", async () => {
81
+ const wtc = await WrapTerminalCoder.create();
82
+ const req = {
83
+ prompt: "Hello world",
84
+ mode: "generate",
85
+ stream: false,
86
+ };
87
+
88
+ const response = await wtc.route(req);
89
+
90
+ expect(response).toHaveProperty("provider");
91
+ expect(response).toHaveProperty("text");
92
+ expect(response.text).toBeDefined();
93
+ });
94
+
95
+ test("should handle explicit provider", async () => {
96
+ const wtc = await WrapTerminalCoder.create();
97
+ const req = {
98
+ prompt: "Test",
99
+ mode: "generate",
100
+ stream: false,
101
+ provider: "gemini",
102
+ };
103
+
104
+ const response = await wtc.route(req);
105
+
106
+ expect(response.provider).toBe("gemini");
107
+ });
108
+
109
+ test("should handle different modes", async () => {
110
+ const wtc = await WrapTerminalCoder.create();
111
+ const modes = ["generate", "edit", "explain", "test"];
112
+
113
+ for (const mode of modes) {
114
+ const req = {
115
+ prompt: "Test",
116
+ mode,
117
+ stream: false,
118
+ };
119
+
120
+ const response = await wtc.route(req);
121
+ expect(response).toBeDefined();
122
+ }
123
+ });
124
+
125
+ test("should handle file context", async () => {
126
+ const wtc = await WrapTerminalCoder.create();
127
+ const req = {
128
+ prompt: "Explain this code",
129
+ mode: "explain",
130
+ fileContext: ["src/file1.ts", "src/file2.ts"],
131
+ stream: false,
132
+ };
133
+
134
+ const response = await wtc.route(req);
135
+ expect(response).toBeDefined();
136
+ });
137
+ });
138
+
139
+ describe("Configuration", () => {
140
+ test("should load configuration", async () => {
141
+ const configLoader = new ConfigLoader();
142
+ const config = await configLoader.loadConfig();
143
+
144
+ expect(config).toHaveProperty("routing");
145
+ expect(config).toHaveProperty("providers");
146
+ expect(config).toHaveProperty("credits");
147
+ });
148
+
149
+ test("should handle config file path", async () => {
150
+ const configLoader = new ConfigLoader({
151
+ projectConfigPath: "/test/path/config.json",
152
+ });
153
+ const config = await configLoader.loadConfig();
154
+
155
+ expect(config).toBeDefined();
156
+ });
157
+ });
158
+
159
+ describe("State Management", () => {
160
+ test("should track provider state", async () => {
161
+ const stateManager = new StateManager();
162
+ await stateManager.initialize();
163
+
164
+ const state = await stateManager.getProviderState("gemini");
165
+ expect(state).toHaveProperty("requestsToday");
166
+ expect(state).toHaveProperty("outOfCreditsUntil");
167
+ });
168
+
169
+ test("should record successful requests", async () => {
170
+ // Override mock for this test to track state changes
171
+ const mockGetState = mock(async () => ({
172
+ requestsToday: 1,
173
+ outOfCreditsUntil: undefined,
174
+ lastErrors: [],
175
+ }));
176
+ const stateManager = new StateManager();
177
+ stateManager.getProviderState = mockGetState;
178
+
179
+ await stateManager.recordSuccess("gemini");
180
+
181
+ const state = await stateManager.getProviderState("gemini");
182
+ expect(state.requestsToday).toBeGreaterThan(0);
183
+ });
184
+ });
185
+ });
186
+
187
+ describe("CLI Commands", () => {
188
+ describe("Providers Command", () => {
189
+ test("should return JSON array of providers", async () => {
190
+ const wtc = await WrapTerminalCoder.create();
191
+ const providers = await wtc.getProviderInfo();
192
+
193
+ expect(Array.isArray(providers)).toBe(true);
194
+ providers.forEach((provider: any) => {
195
+ expect(provider).toHaveProperty("id");
196
+ expect(provider).toHaveProperty("displayName");
197
+ });
198
+ });
199
+
200
+ test("should show correct provider status", async () => {
201
+ const wtc = await WrapTerminalCoder.create();
202
+ const providers = await wtc.getProviderInfo();
203
+
204
+ providers.forEach((provider: any) => {
205
+ expect(typeof provider.requestsToday).toBe("number");
206
+ expect(provider.requestsToday).toBeGreaterThanOrEqual(0);
207
+ });
208
+ });
209
+ });
210
+
211
+ describe("Ask Command", () => {
212
+ test("should handle basic ask command", async () => {
213
+ const wtc = await WrapTerminalCoder.create();
214
+ const req = {
215
+ prompt: "Write hello world",
216
+ mode: "generate",
217
+ stream: false,
218
+ };
219
+
220
+ const response = await wtc.route(req);
221
+
222
+ expect(response).toHaveProperty("text");
223
+ expect(response.text).toBeDefined();
224
+ expect(response.text.length).toBeGreaterThan(0);
225
+ });
226
+
227
+ test("should handle ask with provider flag", async () => {
228
+ const wtc = await WrapTerminalCoder.create();
229
+ const req = {
230
+ prompt: "Test",
231
+ mode: "generate",
232
+ stream: false,
233
+ provider: "gemini",
234
+ };
235
+
236
+ const response = await wtc.route(req);
237
+
238
+ expect(response.provider).toBe("gemini");
239
+ });
240
+
241
+ test("should handle ask with file context", async () => {
242
+ const wtc = await WrapTerminalCoder.create();
243
+ const req = {
244
+ prompt: "Refactor this",
245
+ mode: "edit",
246
+ fileContext: ["src/utils.ts"],
247
+ stream: false,
248
+ };
249
+
250
+ const response = await wtc.route(req);
251
+ expect(response).toBeDefined();
252
+ });
253
+
254
+ test("should handle streaming mode", async () => {
255
+ const wtc = await WrapTerminalCoder.create();
256
+ const req = {
257
+ prompt: "Write a long document",
258
+ mode: "generate",
259
+ stream: true,
260
+ };
261
+
262
+ const responses = [];
263
+ for await (const response of wtc.routeStream(req)) {
264
+ responses.push(response);
265
+ }
266
+
267
+ expect(responses.length).toBeGreaterThan(0);
268
+ expect(responses[responses.length - 1]).toHaveProperty("meta");
269
+ });
270
+ });
271
+
272
+ describe("Output Formats", () => {
273
+ test("should support JSON output format", async () => {
274
+ const wtc = await WrapTerminalCoder.create();
275
+ const req = {
276
+ prompt: "Test",
277
+ mode: "generate",
278
+ stream: false,
279
+ };
280
+
281
+ const response = await wtc.route(req);
282
+
283
+ expect(response).toHaveProperty("provider");
284
+ expect(response).toHaveProperty("text");
285
+ expect(response).toHaveProperty("usage");
286
+ expect(response).toHaveProperty("meta");
287
+ });
288
+ });
289
+
290
+ describe("Error Handling", () => {
291
+ test("should handle provider not found", async () => {
292
+ const wtc = await WrapTerminalCoder.create();
293
+
294
+ // Override route to throw error for this test
295
+ wtc.route = mock(async (req: any) => {
296
+ if (req.provider === "nonexistent") {
297
+ throw new Error("Provider not found: nonexistent");
298
+ }
299
+ return {
300
+ provider: "gemini",
301
+ text: "Mock response",
302
+ usage: { inputTokens: 10, outputTokens: 20 },
303
+ meta: { elapsedMs: 100 },
304
+ };
305
+ });
306
+
307
+ const req = {
308
+ prompt: "Test",
309
+ provider: "nonexistent",
310
+ mode: "generate",
311
+ stream: false,
312
+ };
313
+
314
+ await expect(wtc.route(req)).rejects.toThrow("Provider not found");
315
+ });
316
+ });
317
+ });
318
+
319
+ describe("Configuration Validation", () => {
320
+ test("should validate configuration with Zod", async () => {
321
+ const configLoader = new ConfigLoader();
322
+ const config = await configLoader.loadConfig();
323
+
324
+ expect(config).toBeDefined();
325
+ expect(config).toHaveProperty("routing");
326
+ expect(config).toHaveProperty("providers");
327
+ expect(config).toHaveProperty("credits");
328
+ });
329
+
330
+ test("should handle invalid configuration", async () => {
331
+ const configLoader = new ConfigLoader({
332
+ projectConfigPath: "/invalid/path/config.json",
333
+ });
334
+
335
+ await expect(configLoader.loadConfig()).resolves.toBeDefined();
336
+ });
337
+ });
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from "commander";
4
+ import { WrapTerminalCoder } from "../core/index.js";
5
+ import { CodingRequestSchema } from "../core/index.js";
6
+ import type { CodingRequest } from "../core/index.js";
7
+ import { runMCPServer } from "../mcp/server.js";
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name("wraptc")
13
+ .description("wraptc - unified CLI for multiple coding agents")
14
+ .version("0.1.0")
15
+ .option("--mcp", "Start MCP server mode");
16
+
17
+ // Handle MCP mode flag
18
+ program.hook("preAction", async (thisCommand) => {
19
+ const options = thisCommand.opts();
20
+ if (options.mcp) {
21
+ await runMCPServer();
22
+ process.exit(0);
23
+ }
24
+ });
25
+
26
+ program
27
+ .command("ask")
28
+ .alias("a")
29
+ .description("Send a coding request to the best available provider")
30
+ .argument("[prompt]", "The coding prompt (can also use -p or pipe from stdin)")
31
+ .option("-p, --prompt <prompt>", "The prompt to send to the coding agent")
32
+ .option("--use <provider>", "Specific provider to use (qwen, gemini, codex)")
33
+ .option("--mode <mode>", "Task type: generate, edit, explain, test", "generate")
34
+ .option("--lang <language>", "Language hint (js, ts, py, etc.)")
35
+ .option("-f, --file <files...>", "Include file(s) for context")
36
+ .option("-t, --temperature <temp>", "Creativity level (0-2)", Number.parseFloat)
37
+ .option("-s, --stream", "Stream responses in real-time")
38
+ .option("-o, --output <format>", "Output format: text, json", "text")
39
+ .option("-d, --dry-run", "Show which provider will be used")
40
+ .option("-c, --config <path>", "Custom config file path")
41
+ .option("--timeout <seconds>", "Request timeout in seconds", Number.parseInt)
42
+ .option("--retry <count>", "Retry failed requests", Number.parseInt)
43
+ .action(async (promptArg, options) => {
44
+ try {
45
+ // Handle positional argument or option
46
+ let prompt = promptArg || options.prompt;
47
+
48
+ // Validate prompt
49
+ if (!prompt && process.stdin.isTTY) {
50
+ console.error("Error: Prompt is required (use positional arg, -p, or pipe from stdin)");
51
+ console.error("Examples:");
52
+ console.error(" wraptc 'Write a function'");
53
+ console.error(" wraptc -p 'Write a function'");
54
+ console.error(" echo 'Write a function' | wraptc");
55
+ process.exit(1);
56
+ }
57
+
58
+ // Read from stdin if prompt not provided
59
+ if (!prompt && !process.stdin.isTTY) {
60
+ prompt = await Bun.stdin.text();
61
+ }
62
+
63
+ // Build request
64
+ const request: CodingRequest = {
65
+ prompt: prompt.trim(),
66
+ mode: options.mode,
67
+ language: options.lang,
68
+ fileContext: options.file,
69
+ temperature: options.temperature,
70
+ stream: options.stream,
71
+ provider: options.use, // Changed from options.provider
72
+ };
73
+
74
+ // Validate request
75
+ const parsed = CodingRequestSchema.safeParse(request);
76
+ if (!parsed.success) {
77
+ console.error("Error: Invalid request:", parsed.error.errors);
78
+ process.exit(1);
79
+ }
80
+
81
+ // Create WrapTerminalCoder instance
82
+ const wtc = await WrapTerminalCoder.create({
83
+ configPath: options.config,
84
+ });
85
+
86
+ // Dry run mode
87
+ if (options.dryRun) {
88
+ console.log("Would use provider:", request.provider || "auto-selected");
89
+ process.exit(0);
90
+ }
91
+
92
+ // Execute request
93
+ if (request.stream) {
94
+ for await (const event of wtc.routeStream(request)) {
95
+ if (options.output === "json") {
96
+ console.log(JSON.stringify(event));
97
+ } else {
98
+ console.log(event.text);
99
+ }
100
+ }
101
+ } else {
102
+ const response = await wtc.route(request);
103
+
104
+ if (options.output === "json") {
105
+ console.log(JSON.stringify(response));
106
+ } else {
107
+ console.log(response.text);
108
+ }
109
+ }
110
+ } catch (error) {
111
+ console.error("Error:", error instanceof Error ? error.message : String(error));
112
+ process.exit(1);
113
+ }
114
+ });
115
+
116
+ program
117
+ .command("providers")
118
+ .description("List available providers and their status")
119
+ .option("-c, --config <path>", "Custom config file path")
120
+ .action(async (options) => {
121
+ try {
122
+ const wtc = await WrapTerminalCoder.create({
123
+ configPath: options.config,
124
+ });
125
+
126
+ const providers = await wtc.getProviderInfo();
127
+
128
+ console.log("Available Providers:");
129
+ for (const provider of providers) {
130
+ const status = provider.outOfCreditsUntil
131
+ ? `⚠️ Out of credits until ${new Date(provider.outOfCreditsUntil).toLocaleString()}`
132
+ : `✅ ${provider.requestsToday} requests today`;
133
+
134
+ console.log(` ${provider.displayName} (${provider.id}): ${status}`);
135
+ }
136
+ } catch (error) {
137
+ console.error("Error:", error instanceof Error ? error.message : String(error));
138
+ process.exit(1);
139
+ }
140
+ });
141
+
142
+ program
143
+ .command("mcp")
144
+ .description("Start MCP server")
145
+ .action(async () => {
146
+ await runMCPServer();
147
+ });
148
+
149
+ program.parse();
@@ -0,0 +1,14 @@
1
+ {
2
+ "routing": {
3
+ "defaultOrder": ["project-provider", "user-provider", "system-provider"]
4
+ },
5
+ "providers": {
6
+ "project-provider": {
7
+ "binary": "./project-provider",
8
+ "args": ["--project-flag"],
9
+ "jsonMode": "none",
10
+ "streamingMode": "line",
11
+ "capabilities": ["generate", "edit", "test"]
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "routing": {
3
+ "defaultOrder": ["system-provider"]
4
+ },
5
+ "providers": {
6
+ "system-provider": {
7
+ "binary": "/usr/bin/system-provider",
8
+ "args": [],
9
+ "jsonMode": "none",
10
+ "streamingMode": "line",
11
+ "capabilities": ["generate"]
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "routing": {
3
+ "defaultOrder": ["user-provider", "system-provider"]
4
+ },
5
+ "providers": {
6
+ "user-provider": {
7
+ "binary": "/usr/local/bin/user-provider",
8
+ "args": ["--user-flag"],
9
+ "jsonMode": "flag",
10
+ "jsonFlag": "--json",
11
+ "streamingMode": "jsonl",
12
+ "capabilities": ["generate", "edit"]
13
+ }
14
+ }
15
+ }