mcoda 0.1.70 → 0.1.72

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.
@@ -1 +1 @@
1
- {"version":3,"file":"McodaEntrypoint.d.ts","sourceRoot":"","sources":["../../src/bin/McodaEntrypoint.ts"],"names":[],"mappings":";AAqCA,qBAAa,eAAe;WACb,GAAG,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;CAuPxE"}
1
+ {"version":3,"file":"McodaEntrypoint.d.ts","sourceRoot":"","sources":["../../src/bin/McodaEntrypoint.ts"],"names":[],"mappings":";AAsCA,qBAAa,eAAe;WACb,GAAG,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;CA4PxE"}
@@ -5,6 +5,7 @@ import packageJson from '../../package.json' with { type: 'json' };
5
5
  import { AgentsCommands } from '../commands/agents/AgentsCommands.js';
6
6
  import { CloudCommands } from '../commands/cloud/CloudCommands.js';
7
7
  import { SelfHostedCommands } from '../commands/self-hosted/SelfHostedCommands.js';
8
+ import { WorkersCommands } from '../commands/workers/WorkersCommands.js';
8
9
  import { ConfigCommands } from '../commands/config/ConfigCommands.js';
9
10
  import { ConsentCommands } from '../commands/consent/ConsentCommands.js';
10
11
  import { GatewayAgentCommand } from '../commands/agents/GatewayAgentCommand.js';
@@ -89,13 +90,14 @@ export class McodaEntrypoint {
89
90
  return;
90
91
  }
91
92
  if (!command) {
92
- throw new Error('Usage: mcoda <agent|cloud|cloud-agent|self-hosted|self-hosted-agent|config|consent|setup|gateway-agent|test-agent|agent-run|routing|docs|openapi|job|jobs|tokens|telemetry|create-tasks|migrate-tasks|refine-tasks|task-sufficiency-audit|sds-preflight|order-tasks|tasks|add-tests|work-on-tasks|gateway-trio|code-review|qa-tasks|backlog|task|task-detail|estimate|update|set-workspace|project-guidance|pdr|sds> [...args]\n' +
93
+ throw new Error('Usage: mcoda <agent|cloud|cloud-agent|self-hosted|self-hosted-agent|workers|worker|config|consent|setup|gateway-agent|test-agent|agent-run|routing|docs|openapi|job|jobs|tokens|telemetry|create-tasks|migrate-tasks|refine-tasks|task-sufficiency-audit|sds-preflight|order-tasks|tasks|add-tests|work-on-tasks|gateway-trio|code-review|qa-tasks|backlog|task|task-detail|estimate|update|set-workspace|project-guidance|pdr|sds> [...args]\n' +
93
94
  'Setup: use `mcoda setup` after installation (or accept the postinstall prompt) to complete the mandatory mswarm telemetry consent flow.\n' +
94
95
  'Config: use `mcoda config set mswarm-api-key <KEY>` to persist an encrypted mswarm API key in the resolved global mcoda config file.\n' +
95
96
  'Consent: use `mcoda consent accept` before other commands if you need to complete consent outside the guided setup flow.\n' +
96
97
  'Routing: use `mcoda routing defaults` to view/update workspace/global defaults, `mcoda routing preview|explain` to inspect agent selection/provenance (override → workspace_default → global_default).\n' +
97
98
  'Cloud agents: use `mcoda cloud agent list|details|sync` to discover and materialize mswarm-managed remote agents.\n' +
98
99
  'Self-hosted agents: use `mcoda self-hosted agent list|details|sync` to discover and materialize owner-hosted mswarm agents.\n' +
100
+ 'Workers: use `mcoda workers list|details|sync|run` to discover, materialize, and invoke mswarm Workers.\n' +
99
101
  'Expose this machine: install `@mcoda/mswarm`, then run `mswarm install <KEY>`.\n' +
100
102
  'Aliases: `tasks order-by-deps` forwards to `order-tasks` (dependency-aware ordering), `task`/`task-detail` show a single task.\n' +
101
103
  'Job commands (mcoda job --help for details): list|status|watch|logs|inspect|resume|cancel|tokens\n' +
@@ -121,6 +123,10 @@ export class McodaEntrypoint {
121
123
  await SelfHostedCommands.run(rest);
122
124
  return;
123
125
  }
126
+ if (command === 'workers' || command === 'worker') {
127
+ await WorkersCommands.run(rest);
128
+ return;
129
+ }
124
130
  if (command === 'config') {
125
131
  await ConfigCommands.run(rest);
126
132
  return;
@@ -0,0 +1,4 @@
1
+ export declare class WorkersCommands {
2
+ static run(argv: string[]): Promise<void>;
3
+ }
4
+ //# sourceMappingURL=WorkersCommands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkersCommands.d.ts","sourceRoot":"","sources":["../../../src/commands/workers/WorkersCommands.ts"],"names":[],"mappings":"AA0PA,qBAAa,eAAe;WACb,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAwFhD"}
@@ -0,0 +1,301 @@
1
+ import { MswarmApi, } from "@mcoda/core";
2
+ import { readFile } from "node:fs/promises";
3
+ const USAGE = `
4
+ Usage: mcoda workers <list|details|sync|run> [options]
5
+
6
+ Subcommands:
7
+ list List mswarm Workers as mcoda agents (supports --json)
8
+ --limit <N> Limit returned workers
9
+ --include-disabled Include disabled Workers in the catalog result (default)
10
+ --enabled-only Hide disabled Workers
11
+ details <SLUG> Show a single mswarm Worker (supports --json)
12
+ sync Sync mswarm Workers into the local mcoda registry
13
+ --limit <N> Limit synced workers
14
+ --include-disabled Sync disabled Workers too (default)
15
+ --enabled-only Sync active Workers only
16
+ --prune Remove previously synced Workers missing from the current catalog result
17
+ --agent-slug-prefix <P>
18
+ Override the local managed-worker slug prefix
19
+ run <SLUG> [TEXT...] Run a Worker through mswarm
20
+ --input <TEXT> Worker input text
21
+ --payload-file <FILE> Read JSON payload from a file
22
+ --payload-stdin Read JSON or text payload from stdin
23
+ --idempotency-key <K> Forward an idempotency key
24
+
25
+ Connection options:
26
+ --base-url <URL> Override MCODA_MSWARM_BASE_URL (default: https://api.mswarm.org/)
27
+ --api-key <KEY> Override MCODA_MSWARM_API_KEY
28
+ --timeout-ms <N> Override MCODA_MSWARM_TIMEOUT_MS
29
+
30
+ Environment:
31
+ MCODA_MSWARM_BASE_URL
32
+ MCODA_MSWARM_API_KEY
33
+ MCODA_MSWARM_TIMEOUT_MS
34
+ MCODA_MSWARM_WORKER_AGENT_SLUG_PREFIX
35
+
36
+ Flags:
37
+ --json Emit JSON for supported commands
38
+ --help Show this help
39
+ `.trim();
40
+ const MAX_WORKER_PAYLOAD_BYTES = 5 * 1024 * 1024;
41
+ const parseArgs = (argv) => {
42
+ const flags = {};
43
+ const positionals = [];
44
+ for (let index = 0; index < argv.length; index += 1) {
45
+ const arg = argv[index];
46
+ if (arg.startsWith("--")) {
47
+ const key = arg.replace(/^--/, "");
48
+ const next = argv[index + 1];
49
+ if (next && !next.startsWith("--")) {
50
+ const current = flags[key];
51
+ if (current === undefined)
52
+ flags[key] = next;
53
+ else if (Array.isArray(current))
54
+ flags[key] = [...current, next];
55
+ else if (typeof current === "string")
56
+ flags[key] = [current, next];
57
+ else
58
+ flags[key] = [next];
59
+ index += 1;
60
+ }
61
+ else {
62
+ flags[key] = true;
63
+ }
64
+ continue;
65
+ }
66
+ positionals.push(arg);
67
+ }
68
+ return { flags, positionals };
69
+ };
70
+ const resolveString = (value) => {
71
+ if (value === undefined || typeof value === "boolean")
72
+ return undefined;
73
+ return Array.isArray(value) ? value[value.length - 1] : value;
74
+ };
75
+ const resolvePositiveInt = (value, label) => {
76
+ const raw = resolveString(value);
77
+ if (raw === undefined)
78
+ return undefined;
79
+ const parsed = Number.parseInt(raw, 10);
80
+ if (!Number.isFinite(parsed) || parsed <= 0) {
81
+ throw new Error(`Invalid ${label}; expected a positive integer`);
82
+ }
83
+ return parsed;
84
+ };
85
+ const formatNumber = (value) => value === undefined || Number.isNaN(value) ? "-" : String(value);
86
+ const formatBoolean = (value) => value === undefined ? "-" : value ? "yes" : "no";
87
+ const formatCapabilities = (capabilities) => capabilities && capabilities.length > 0 ? capabilities.join(",") : "-";
88
+ const asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
89
+ ? value
90
+ : undefined;
91
+ const formatString = (value) => typeof value === "string" && value.trim() ? value.trim() : "-";
92
+ const formatWorkerAgent = (worker) => {
93
+ const selected = asRecord(worker.worker?.selected_agent);
94
+ return formatString(selected?.slug ?? selected?.model ?? worker.default_model);
95
+ };
96
+ const pad = (value, width) => value.padEnd(width, " ");
97
+ const renderTable = (headers, rows) => {
98
+ const widths = headers.map((header, columnIndex) => Math.max(header.length, ...rows.map((row) => row[columnIndex]?.length ?? 0)));
99
+ return [
100
+ headers.map((header, index) => pad(header, widths[index] ?? header.length)).join(" "),
101
+ widths.map((width) => "-".repeat(width)).join(" "),
102
+ ...rows.map((row) => row.map((cell, index) => pad(cell, widths[index] ?? cell.length)).join(" ")),
103
+ ].join("\n");
104
+ };
105
+ const printWorkerList = (workers) => {
106
+ if (workers.length === 0) {
107
+ // eslint-disable-next-line no-console
108
+ console.log("No mswarm Workers found.");
109
+ return;
110
+ }
111
+ const rows = workers.map((worker) => [
112
+ worker.slug,
113
+ worker.worker?.name ?? worker.display_name ?? "-",
114
+ formatBoolean(worker.worker?.enabled),
115
+ worker.worker?.status ?? worker.health_status ?? "-",
116
+ formatWorkerAgent(worker),
117
+ formatBoolean(worker.worker?.docdex_enabled),
118
+ formatString(asRecord(worker.worker?.config_health)?.status),
119
+ ]);
120
+ // eslint-disable-next-line no-console
121
+ console.log(renderTable(["WORKER SLUG", "NAME", "ENABLED", "STATUS", "AGENT", "DOCDEX", "CONFIG"], rows));
122
+ };
123
+ const printWorkerDetails = (worker) => {
124
+ const entries = [
125
+ ["Slug", worker.slug],
126
+ ["Remote slug", worker.remote_slug ?? "-"],
127
+ ["Name", worker.worker?.name ?? worker.display_name ?? "-"],
128
+ ["Provider", worker.provider],
129
+ ["Adapter", worker.adapter ?? "-"],
130
+ ["Model", worker.default_model],
131
+ ["Rating", formatNumber(worker.rating)],
132
+ ["Reasoning rating", formatNumber(worker.reasoning_rating)],
133
+ ["Max complexity", formatNumber(worker.max_complexity)],
134
+ ["Supports tools", formatBoolean(worker.supports_tools)],
135
+ ["Health", worker.health_status ?? "-"],
136
+ ["Capabilities", formatCapabilities(worker.capabilities)],
137
+ ];
138
+ const labelWidth = Math.max(...entries.map(([label]) => label.length));
139
+ for (const [label, value] of entries) {
140
+ // eslint-disable-next-line no-console
141
+ console.log(`${label.padEnd(labelWidth, " ")} : ${value}`);
142
+ }
143
+ };
144
+ const printSyncSummary = (summary) => {
145
+ // eslint-disable-next-line no-console
146
+ console.log(`Synced ${summary.agents.length} Workers (created=${summary.created}, updated=${summary.updated}, deleted=${summary.deleted}).`);
147
+ if (summary.agents.length === 0)
148
+ return;
149
+ const rows = summary.agents.map((record) => [
150
+ record.remoteSlug,
151
+ record.localSlug,
152
+ record.action,
153
+ record.defaultModel,
154
+ ]);
155
+ // eslint-disable-next-line no-console
156
+ console.log(renderTable(["REMOTE SLUG", "LOCAL SLUG", "ACTION", "MODEL"], rows));
157
+ };
158
+ const ensurePayloadSize = (raw, label) => {
159
+ const bytes = Buffer.byteLength(raw, "utf8");
160
+ if (bytes > MAX_WORKER_PAYLOAD_BYTES) {
161
+ throw new Error(`${label} exceeds ${MAX_WORKER_PAYLOAD_BYTES} bytes`);
162
+ }
163
+ };
164
+ const readStdin = async () => new Promise((resolve, reject) => {
165
+ let data = "";
166
+ process.stdin.setEncoding("utf8");
167
+ process.stdin.on("data", (chunk) => {
168
+ data += chunk;
169
+ if (Buffer.byteLength(data, "utf8") > MAX_WORKER_PAYLOAD_BYTES) {
170
+ reject(new Error(`stdin payload exceeds ${MAX_WORKER_PAYLOAD_BYTES} bytes`));
171
+ }
172
+ });
173
+ process.stdin.on("error", reject);
174
+ process.stdin.on("end", () => resolve(data));
175
+ });
176
+ const parsePayloadText = (raw, label) => {
177
+ ensurePayloadSize(raw, label);
178
+ const trimmed = raw.trim();
179
+ if (!trimmed) {
180
+ return {};
181
+ }
182
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
183
+ try {
184
+ return JSON.parse(trimmed);
185
+ }
186
+ catch (error) {
187
+ throw new Error(`${label} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
188
+ }
189
+ }
190
+ return { text: raw };
191
+ };
192
+ const resolveRunPayload = async (parsed) => {
193
+ const sources = [
194
+ resolveString(parsed.flags["payload-file"]) !== undefined,
195
+ parsed.flags["payload-stdin"] === true,
196
+ resolveString(parsed.flags.input) !== undefined || parsed.positionals.length > 1,
197
+ ].filter(Boolean).length;
198
+ if (sources > 1) {
199
+ throw new Error("Use only one of --payload-file, --payload-stdin, --input, or positional TEXT");
200
+ }
201
+ const payloadFile = resolveString(parsed.flags["payload-file"]);
202
+ if (payloadFile) {
203
+ return parsePayloadText(await readFile(payloadFile, "utf8"), "--payload-file");
204
+ }
205
+ if (parsed.flags["payload-stdin"] === true) {
206
+ return parsePayloadText(await readStdin(), "--payload-stdin");
207
+ }
208
+ const input = resolveString(parsed.flags.input) ?? parsed.positionals.slice(1).join(" ");
209
+ return { text: input };
210
+ };
211
+ const resolveIncludeDisabled = (parsed) => parsed.flags["enabled-only"] === true ? false : true;
212
+ export class WorkersCommands {
213
+ static async run(argv) {
214
+ const [rawSubcommand, ...rest] = argv;
215
+ if (!rawSubcommand || argv.includes("--help") || argv.includes("-h")) {
216
+ // eslint-disable-next-line no-console
217
+ console.log(USAGE);
218
+ return;
219
+ }
220
+ const subcommand = rawSubcommand === "detail" || rawSubcommand === "show" ? "details" : rawSubcommand;
221
+ const parsed = parseArgs(rest);
222
+ const api = await MswarmApi.create({
223
+ baseUrl: resolveString(parsed.flags["base-url"]),
224
+ apiKey: resolveString(parsed.flags["api-key"]),
225
+ timeoutMs: resolvePositiveInt(parsed.flags["timeout-ms"], "--timeout-ms"),
226
+ workerAgentSlugPrefix: resolveString(parsed.flags["agent-slug-prefix"]),
227
+ });
228
+ try {
229
+ switch (subcommand) {
230
+ case "list": {
231
+ const workers = await api.listAllWorkers({
232
+ limit: resolvePositiveInt(parsed.flags.limit, "--limit"),
233
+ includeDisabled: resolveIncludeDisabled(parsed),
234
+ });
235
+ if (parsed.flags.json) {
236
+ // eslint-disable-next-line no-console
237
+ console.log(JSON.stringify(workers, null, 2));
238
+ }
239
+ else {
240
+ printWorkerList(workers);
241
+ }
242
+ break;
243
+ }
244
+ case "details": {
245
+ const slug = parsed.positionals[0];
246
+ if (!slug)
247
+ throw new Error("Usage: mcoda workers details <SLUG> [--json]");
248
+ const worker = await api.getWorker(slug);
249
+ if (parsed.flags.json) {
250
+ // eslint-disable-next-line no-console
251
+ console.log(JSON.stringify(worker, null, 2));
252
+ }
253
+ else {
254
+ printWorkerDetails(worker);
255
+ }
256
+ break;
257
+ }
258
+ case "sync": {
259
+ const summary = await api.syncWorkers({
260
+ limit: resolvePositiveInt(parsed.flags.limit, "--limit"),
261
+ includeDisabled: resolveIncludeDisabled(parsed),
262
+ pruneMissing: Boolean(parsed.flags.prune),
263
+ });
264
+ if (parsed.flags.json) {
265
+ // eslint-disable-next-line no-console
266
+ console.log(JSON.stringify(summary, null, 2));
267
+ }
268
+ else {
269
+ printSyncSummary(summary);
270
+ }
271
+ break;
272
+ }
273
+ case "run": {
274
+ const slug = parsed.positionals[0];
275
+ if (!slug)
276
+ throw new Error("Usage: mcoda workers run <SLUG> [TEXT...]");
277
+ const result = await api.runWorker(slug, await resolveRunPayload(parsed), { idempotencyKey: resolveString(parsed.flags["idempotency-key"]) });
278
+ if (parsed.flags.json) {
279
+ // eslint-disable-next-line no-console
280
+ console.log(JSON.stringify(result, null, 2));
281
+ }
282
+ else {
283
+ const output = typeof result.output === "string"
284
+ ? result.output
285
+ : typeof result.result?.output === "string"
286
+ ? String(result.result.output)
287
+ : JSON.stringify(result, null, 2);
288
+ // eslint-disable-next-line no-console
289
+ console.log(output);
290
+ }
291
+ break;
292
+ }
293
+ default:
294
+ throw new Error(`Unknown workers subcommand: ${subcommand}`);
295
+ }
296
+ }
297
+ finally {
298
+ await api.close();
299
+ }
300
+ }
301
+ }
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export * from './commands/agents/AgentsCommands.js';
2
2
  export * from './bin/McodaEntrypoint.js';
3
3
  export * from './commands/cloud/CloudCommands.js';
4
4
  export * from './commands/self-hosted/SelfHostedCommands.js';
5
+ export * from './commands/workers/WorkersCommands.js';
5
6
  export * from './commands/config/ConfigCommands.js';
6
7
  export * from './commands/consent/ConsentCommands.js';
7
8
  export * from './commands/docs/DocsCommands.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,0BAA0B,CAAC;AACzC,cAAc,mCAAmC,CAAC;AAClD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,qCAAqC,CAAC;AACpD,cAAc,uCAAuC,CAAC;AACtD,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,oDAAoD,CAAC;AACnE,cAAc,4CAA4C,CAAC;AAC3D,cAAc,uCAAuC,CAAC;AACtD,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,uCAAuC,CAAC;AACtD,cAAc,kCAAkC,CAAC;AACjD,cAAc,uCAAuC,CAAC;AACtD,cAAc,gDAAgD,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,0BAA0B,CAAC;AACzC,cAAc,mCAAmC,CAAC;AAClD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,uCAAuC,CAAC;AACtD,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,oDAAoD,CAAC;AACnE,cAAc,4CAA4C,CAAC;AAC3D,cAAc,uCAAuC,CAAC;AACtD,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,uCAAuC,CAAC;AACtD,cAAc,kCAAkC,CAAC;AACjD,cAAc,uCAAuC,CAAC;AACtD,cAAc,gDAAgD,CAAC"}
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ export * from './commands/agents/AgentsCommands.js';
2
2
  export * from './bin/McodaEntrypoint.js';
3
3
  export * from './commands/cloud/CloudCommands.js';
4
4
  export * from './commands/self-hosted/SelfHostedCommands.js';
5
+ export * from './commands/workers/WorkersCommands.js';
5
6
  export * from './commands/config/ConfigCommands.js';
6
7
  export * from './commands/consent/ConsentCommands.js';
7
8
  export * from './commands/docs/DocsCommands.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcoda",
3
- "version": "0.1.70",
3
+ "version": "0.1.72",
4
4
  "description": "Local-first CLI for planning, documentation, and execution workflows with agent assistance.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -47,12 +47,12 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "yaml": "^2.4.2",
50
- "@mcoda/core": "0.1.70",
51
- "@mcoda/shared": "0.1.70"
50
+ "@mcoda/core": "0.1.72",
51
+ "@mcoda/shared": "0.1.72"
52
52
  },
53
53
  "devDependencies": {
54
- "@mcoda/db": "0.1.70",
55
- "@mcoda/integrations": "0.1.70"
54
+ "@mcoda/db": "0.1.72",
55
+ "@mcoda/integrations": "0.1.72"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsc -p tsconfig.json",