mcoda 0.1.75 → 0.1.78
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/CHANGELOG.md +8 -0
- package/README.md +2 -0
- package/dist/bin/McodaEntrypoint.d.ts.map +1 -1
- package/dist/bin/McodaEntrypoint.js +26 -12
- package/dist/commands/gpu/GpuCommands.d.ts +8 -0
- package/dist/commands/gpu/GpuCommands.d.ts.map +1 -0
- package/dist/commands/gpu/GpuCommands.js +382 -0
- package/dist/commands/jobs/JobsCommands.d.ts.map +1 -1
- package/dist/commands/jobs/JobsCommands.js +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
- Initial npm packaging scaffold for the mcoda CLI.
|
|
5
5
|
- Added bundled mswarm consent terms plus guided `mcoda setup`/postinstall consent bootstrap for installed CLI packages.
|
|
6
6
|
|
|
7
|
+
## 0.1.78
|
|
8
|
+
- Added owner-local GPU job commands: `mcoda gpu list`, `mcoda gpu ops`, and GPU-aware `mcoda job artifact upload|run|status|logs|events|artifacts|cancel|retry`.
|
|
9
|
+
- Added README and usage docs for the generic GPU job connection flags and environment fallbacks.
|
|
10
|
+
|
|
11
|
+
## 0.1.76
|
|
12
|
+
- Added top-level help aliases: `mcoda help`, `mcoda --help`, `mcoda -h`, and `mcoda -H`.
|
|
13
|
+
- Release v0.1.76.
|
|
14
|
+
|
|
7
15
|
## 0.1.9
|
|
8
16
|
- Release v0.1.9.
|
|
9
17
|
|
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ mcoda docs pdr generate --workspace-root . --project WEB --rfp-path docs/rfp/web
|
|
|
34
34
|
- Execution: `mcoda add-tests`, `mcoda work-on-tasks`, `mcoda code-review`, `mcoda qa-tasks`
|
|
35
35
|
- Backlog: `mcoda backlog`, `mcoda task`
|
|
36
36
|
- Jobs/telemetry: `mcoda jobs`, `mcoda tokens`, `mcoda telemetry`
|
|
37
|
+
- Owner-local GPU jobs: `mcoda gpu list`, `mcoda gpu ops`, `mcoda job artifact upload|run|status|logs|events|artifacts|cancel|retry`
|
|
37
38
|
- Agents: `mcoda test-agent`, `mcoda agent-run`
|
|
38
39
|
- Updates: `mcoda update --check`
|
|
39
40
|
|
|
@@ -45,6 +46,7 @@ If that sufficiency pass errors, create-tasks continues (fail-open) and records
|
|
|
45
46
|
Environment variables are optional overrides for workspace settings:
|
|
46
47
|
- `MCODA_DOCDEX_URL` to point at a docdex server.
|
|
47
48
|
- `MCODA_API_BASE_URL` or `MCODA_JOBS_API_URL` for job APIs.
|
|
49
|
+
- `MCODA_MSWARM_NODE_BASE_URL`, `MCODA_MSWARM_NODE_ID`, and `MCODA_MSWARM_NODE_SIGNING_SECRET` for owner-local generic GPU job commands.
|
|
48
50
|
- `MCODA_TELEMETRY` set to `off` to disable telemetry.
|
|
49
51
|
- `MCODA_STREAM_IO=1` to emit agent I/O lines to stderr.
|
|
50
52
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McodaEntrypoint.d.ts","sourceRoot":"","sources":["../../src/bin/McodaEntrypoint.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"McodaEntrypoint.d.ts","sourceRoot":"","sources":["../../src/bin/McodaEntrypoint.ts"],"names":[],"mappings":";AAyDA,qBAAa,eAAe;WACb,GAAG,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;CAwPxE"}
|
|
@@ -6,6 +6,7 @@ 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
8
|
import { WorkersCommands } from '../commands/workers/WorkersCommands.js';
|
|
9
|
+
import { GpuCommands } from '../commands/gpu/GpuCommands.js';
|
|
9
10
|
import { ConfigCommands } from '../commands/config/ConfigCommands.js';
|
|
10
11
|
import { ConsentCommands } from '../commands/consent/ConsentCommands.js';
|
|
11
12
|
import { GatewayAgentCommand } from '../commands/agents/GatewayAgentCommand.js';
|
|
@@ -35,6 +36,21 @@ import { AgentRunCommand } from '../commands/agents/AgentRunCommand.js';
|
|
|
35
36
|
import { SetWorkspaceCommand } from '../commands/workspace/SetWorkspaceCommand.js';
|
|
36
37
|
import { ProjectGuidanceCommand } from '../commands/workspace/ProjectGuidanceCommand.js';
|
|
37
38
|
import { MswarmConfigStore } from '@mcoda/core';
|
|
39
|
+
const TOP_LEVEL_USAGE = 'Usage: mcoda <agent|cloud|cloud-agent|self-hosted|self-hosted-agent|workers|worker|gpu|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' +
|
|
40
|
+
'Setup: use `mcoda setup` after installation (or accept the postinstall prompt) to complete the mandatory mswarm telemetry consent flow.\n' +
|
|
41
|
+
'Config: use `mcoda config set mswarm-api-key <KEY>` to persist an encrypted mswarm API key in the resolved global mcoda config file.\n' +
|
|
42
|
+
'Consent: use `mcoda consent accept` before other commands if you need to complete consent outside the guided setup flow.\n' +
|
|
43
|
+
'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' +
|
|
44
|
+
'Cloud agents: use `mcoda cloud agent list|details|sync` to discover and materialize mswarm-managed remote agents.\n' +
|
|
45
|
+
'Self-hosted agents: use `mcoda self-hosted agent list|details|sync` to discover and materialize owner-hosted mswarm agents.\n' +
|
|
46
|
+
'Workers: use `mcoda workers list|details|sync|run` to discover, materialize, and invoke mswarm Workers.\n' +
|
|
47
|
+
'GPU jobs: use `mcoda gpu list` and `mcoda job artifact upload|run|status|logs|events|artifacts|cancel` for owner-local generic GPU jobs.\n' +
|
|
48
|
+
'Expose this machine: install `@mcoda/mswarm`, then run `mswarm install <KEY>`.\n' +
|
|
49
|
+
'Aliases: `tasks order-by-deps` forwards to `order-tasks` (dependency-aware ordering), `task`/`task-detail` show a single task.\n' +
|
|
50
|
+
'Help: use `mcoda help`, `mcoda --help`, `mcoda -h`, or `mcoda -H` for this overview.\n' +
|
|
51
|
+
'Job commands (mcoda job --help for details): list|status|watch|logs|inspect|resume|cancel|tokens plus GPU job artifact|run|events|artifacts with node options\n' +
|
|
52
|
+
'Jobs API required for job commands (set MCODA_API_BASE_URL/MCODA_JOBS_API_URL or workspace api.baseUrl). status/watch/logs exit non-zero on failed/cancelled jobs per SDS.';
|
|
53
|
+
const TOP_LEVEL_HELP_COMMANDS = new Set(['--help', '-h', '-H', 'help']);
|
|
38
54
|
export class McodaEntrypoint {
|
|
39
55
|
static async run(argv = process.argv.slice(2)) {
|
|
40
56
|
const applyCodexNoSandboxFlag = (value) => {
|
|
@@ -89,19 +105,13 @@ export class McodaEntrypoint {
|
|
|
89
105
|
console.log(packageJson.version ?? 'dev');
|
|
90
106
|
return;
|
|
91
107
|
}
|
|
108
|
+
if (command && TOP_LEVEL_HELP_COMMANDS.has(command)) {
|
|
109
|
+
// eslint-disable-next-line no-console
|
|
110
|
+
console.log(TOP_LEVEL_USAGE);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
92
113
|
if (!command) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
'Setup: use `mcoda setup` after installation (or accept the postinstall prompt) to complete the mandatory mswarm telemetry consent flow.\n' +
|
|
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' +
|
|
96
|
-
'Consent: use `mcoda consent accept` before other commands if you need to complete consent outside the guided setup flow.\n' +
|
|
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' +
|
|
98
|
-
'Cloud agents: use `mcoda cloud agent list|details|sync` to discover and materialize mswarm-managed remote agents.\n' +
|
|
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' +
|
|
101
|
-
'Expose this machine: install `@mcoda/mswarm`, then run `mswarm install <KEY>`.\n' +
|
|
102
|
-
'Aliases: `tasks order-by-deps` forwards to `order-tasks` (dependency-aware ordering), `task`/`task-detail` show a single task.\n' +
|
|
103
|
-
'Job commands (mcoda job --help for details): list|status|watch|logs|inspect|resume|cancel|tokens\n' +
|
|
104
|
-
'Jobs API required for job commands (set MCODA_API_BASE_URL/MCODA_JOBS_API_URL or workspace api.baseUrl). status/watch/logs exit non-zero on failed/cancelled jobs per SDS.');
|
|
114
|
+
throw new Error(TOP_LEVEL_USAGE);
|
|
105
115
|
}
|
|
106
116
|
if (!['config', 'consent', 'setup'].includes(command)) {
|
|
107
117
|
const consentState = await new MswarmConfigStore().readState();
|
|
@@ -127,6 +137,10 @@ export class McodaEntrypoint {
|
|
|
127
137
|
await WorkersCommands.run(rest);
|
|
128
138
|
return;
|
|
129
139
|
}
|
|
140
|
+
if (command === 'gpu') {
|
|
141
|
+
await GpuCommands.run(rest);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
130
144
|
if (command === 'config') {
|
|
131
145
|
await ConfigCommands.run(rest);
|
|
132
146
|
return;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GpuCommands.d.ts","sourceRoot":"","sources":["../../../src/commands/gpu/GpuCommands.ts"],"names":[],"mappings":"AAoSA,qBAAa,WAAW;WACT,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAiChD;AAED,qBAAa,cAAc;IACzB,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO;WAQ/B,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CA8FhD"}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import YAML from "yaml";
|
|
5
|
+
import { MswarmApi } from "@mcoda/core";
|
|
6
|
+
const GPU_USAGE = `
|
|
7
|
+
Usage: mcoda gpu <list|ops> [options]
|
|
8
|
+
|
|
9
|
+
Commands:
|
|
10
|
+
gpu list List owner-local GPU/job capability projection
|
|
11
|
+
gpu ops Show owner-local generic job queue, usage, quota, and audit summary
|
|
12
|
+
|
|
13
|
+
Connection options:
|
|
14
|
+
--node-base-url <URL> Owner-local node URL (or MCODA_MSWARM_NODE_BASE_URL)
|
|
15
|
+
--node-id <ID> Node id for signing capability requests
|
|
16
|
+
--signing-secret <KEY> Owner-local generic job signing secret
|
|
17
|
+
--token <TOKEN> Pre-signed capability or ops token
|
|
18
|
+
--timeout-ms <N> Request timeout
|
|
19
|
+
--audit-limit <N> Audit rows for gpu ops (default 50, max 250)
|
|
20
|
+
--audit-offset <N> Audit row offset for gpu ops
|
|
21
|
+
--json Emit JSON
|
|
22
|
+
`.trim();
|
|
23
|
+
const JOB_USAGE = `
|
|
24
|
+
Usage: mcoda job <artifact|run|status|logs|events|artifacts|cancel|retry> [options]
|
|
25
|
+
|
|
26
|
+
GPU job commands:
|
|
27
|
+
job artifact upload <FILE>
|
|
28
|
+
--job-id <ID> --request-id <ID> --node-id <ID> --job-type <TYPE>
|
|
29
|
+
[--artifact-path <PATH>] [--artifact-name <NAME>] [--content-type <TYPE>]
|
|
30
|
+
job run --job-file <FILE> [--wait] [--json]
|
|
31
|
+
job run --type <TYPE> --payload-file <FILE> [--wait] [--json]
|
|
32
|
+
job status <JOB_ID> [--job-file <FILE> | --node-id ... --request-id ... --job-type ...]
|
|
33
|
+
job logs <JOB_ID>
|
|
34
|
+
job events <JOB_ID>
|
|
35
|
+
job artifacts <JOB_ID>
|
|
36
|
+
job cancel <JOB_ID>
|
|
37
|
+
job retry <JOB_ID>
|
|
38
|
+
|
|
39
|
+
Connection options:
|
|
40
|
+
--node-base-url <URL> Owner-local node URL (or MCODA_MSWARM_NODE_BASE_URL)
|
|
41
|
+
--node-id <ID> Node id for signing requests
|
|
42
|
+
--signing-secret <KEY> Owner-local generic job signing secret
|
|
43
|
+
--token <TOKEN> Pre-signed generic job token
|
|
44
|
+
--timeout-ms <N> Request timeout
|
|
45
|
+
--json Emit JSON
|
|
46
|
+
`.trim();
|
|
47
|
+
const GPU_JOB_SUBCOMMANDS = new Set(["artifact", "run", "events", "artifacts"]);
|
|
48
|
+
const SHARED_JOB_SUBCOMMANDS = new Set(["status", "logs", "cancel", "retry"]);
|
|
49
|
+
const GPU_JOB_FLAGS = new Set([
|
|
50
|
+
"--gpu",
|
|
51
|
+
"--node-base-url",
|
|
52
|
+
"--node-id",
|
|
53
|
+
"--signing-secret",
|
|
54
|
+
"--token",
|
|
55
|
+
"--job-file",
|
|
56
|
+
"--payload-file",
|
|
57
|
+
"--job-type",
|
|
58
|
+
"--type",
|
|
59
|
+
"--schema-version",
|
|
60
|
+
"--request-id"
|
|
61
|
+
]);
|
|
62
|
+
const TERMINAL_STATES = new Set(["succeeded", "failed", "cancelled", "expired", "blocked"]);
|
|
63
|
+
const parseArgs = (argv) => {
|
|
64
|
+
const flags = {};
|
|
65
|
+
const positionals = [];
|
|
66
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
67
|
+
const arg = argv[index];
|
|
68
|
+
if (arg.startsWith("--")) {
|
|
69
|
+
const key = arg.replace(/^--/, "");
|
|
70
|
+
const next = argv[index + 1];
|
|
71
|
+
if (next && !next.startsWith("--")) {
|
|
72
|
+
const current = flags[key];
|
|
73
|
+
if (current === undefined)
|
|
74
|
+
flags[key] = next;
|
|
75
|
+
else if (Array.isArray(current))
|
|
76
|
+
flags[key] = [...current, next];
|
|
77
|
+
else if (typeof current === "string")
|
|
78
|
+
flags[key] = [current, next];
|
|
79
|
+
else
|
|
80
|
+
flags[key] = [next];
|
|
81
|
+
index += 1;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
flags[key] = true;
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
positionals.push(arg);
|
|
89
|
+
}
|
|
90
|
+
return { flags, positionals };
|
|
91
|
+
};
|
|
92
|
+
const resolveString = (value) => {
|
|
93
|
+
if (value === undefined || typeof value === "boolean")
|
|
94
|
+
return undefined;
|
|
95
|
+
return Array.isArray(value) ? value[value.length - 1] : value;
|
|
96
|
+
};
|
|
97
|
+
const resolvePositiveInt = (value, label) => {
|
|
98
|
+
const raw = resolveString(value);
|
|
99
|
+
if (raw === undefined)
|
|
100
|
+
return undefined;
|
|
101
|
+
const parsed = Number.parseInt(raw, 10);
|
|
102
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
103
|
+
throw new Error(`Invalid ${label}; expected a positive integer`);
|
|
104
|
+
}
|
|
105
|
+
return parsed;
|
|
106
|
+
};
|
|
107
|
+
const resolveNonNegativeInt = (value, label) => {
|
|
108
|
+
const raw = resolveString(value);
|
|
109
|
+
if (raw === undefined)
|
|
110
|
+
return undefined;
|
|
111
|
+
const parsed = Number.parseInt(raw, 10);
|
|
112
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
113
|
+
throw new Error(`Invalid ${label}; expected a non-negative integer`);
|
|
114
|
+
}
|
|
115
|
+
return parsed;
|
|
116
|
+
};
|
|
117
|
+
const nodeBaseUrl = (parsed) => resolveString(parsed.flags["node-base-url"]) || process.env.MCODA_MSWARM_NODE_BASE_URL;
|
|
118
|
+
const nodeId = (parsed) => resolveString(parsed.flags["node-id"]) || process.env.MCODA_MSWARM_NODE_ID || process.env.MSWARM_SELF_HOSTED_NODE_ID;
|
|
119
|
+
const signingSecret = (parsed) => resolveString(parsed.flags["signing-secret"]) ||
|
|
120
|
+
process.env.MCODA_MSWARM_NODE_SIGNING_SECRET ||
|
|
121
|
+
process.env.MSWARM_SELF_HOSTED_INVOCATION_SIGNING_SECRET;
|
|
122
|
+
const baseAuth = (parsed) => ({
|
|
123
|
+
nodeBaseUrl: nodeBaseUrl(parsed),
|
|
124
|
+
token: resolveString(parsed.flags.token),
|
|
125
|
+
signingSecret: signingSecret(parsed),
|
|
126
|
+
tokenTtlSeconds: resolvePositiveInt(parsed.flags["token-ttl-seconds"], "--token-ttl-seconds")
|
|
127
|
+
});
|
|
128
|
+
const createApi = async (parsed) => MswarmApi.create({
|
|
129
|
+
baseUrl: nodeBaseUrl(parsed),
|
|
130
|
+
apiKey: resolveString(parsed.flags["api-key"]),
|
|
131
|
+
timeoutMs: resolvePositiveInt(parsed.flags["timeout-ms"], "--timeout-ms")
|
|
132
|
+
});
|
|
133
|
+
const readStructuredFile = async (file) => {
|
|
134
|
+
const raw = await readFile(file, "utf8");
|
|
135
|
+
if (file.endsWith(".yaml") || file.endsWith(".yml")) {
|
|
136
|
+
return YAML.parse(raw);
|
|
137
|
+
}
|
|
138
|
+
return JSON.parse(raw);
|
|
139
|
+
};
|
|
140
|
+
const asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
|
|
141
|
+
? value
|
|
142
|
+
: {};
|
|
143
|
+
const readJobEnvelope = async (parsed) => {
|
|
144
|
+
const file = resolveString(parsed.flags["job-file"]) || resolveString(parsed.flags["payload-file"]);
|
|
145
|
+
if (!file) {
|
|
146
|
+
throw new Error("GPU job run requires --job-file or --payload-file");
|
|
147
|
+
}
|
|
148
|
+
const payload = asRecord(await readStructuredFile(file));
|
|
149
|
+
if (typeof payload.job_id === "string" && typeof payload.request_id === "string" && typeof payload.node_id === "string") {
|
|
150
|
+
return payload;
|
|
151
|
+
}
|
|
152
|
+
const schemaVersion = resolveString(parsed.flags["schema-version"]) ||
|
|
153
|
+
(typeof payload.schema_version === "string" ? payload.schema_version : "2026-06-14");
|
|
154
|
+
const jobType = resolveString(parsed.flags.type) ||
|
|
155
|
+
resolveString(parsed.flags["job-type"]) ||
|
|
156
|
+
(typeof payload.job_type === "string" ? payload.job_type : undefined);
|
|
157
|
+
if (!jobType) {
|
|
158
|
+
throw new Error("--job-file/--payload-file must contain job_type or use --type/--job-type");
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
job_id: resolveString(parsed.flags["job-id"]) || `job-${randomUUID()}`,
|
|
162
|
+
request_id: resolveString(parsed.flags["request-id"]) || `req-${randomUUID()}`,
|
|
163
|
+
node_id: requireText(nodeId(parsed), "--node-id"),
|
|
164
|
+
job: {
|
|
165
|
+
...payload,
|
|
166
|
+
schema_version: schemaVersion,
|
|
167
|
+
job_type: jobType
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
const referenceFromArgs = async (parsed, explicitJobId) => {
|
|
172
|
+
const jobFile = resolveString(parsed.flags["job-file"]) || resolveString(parsed.flags["payload-file"]);
|
|
173
|
+
if (jobFile) {
|
|
174
|
+
const job = await readJobEnvelope(parsed);
|
|
175
|
+
return {
|
|
176
|
+
...baseAuth(parsed),
|
|
177
|
+
jobId: explicitJobId || job.job_id,
|
|
178
|
+
nodeId: job.node_id,
|
|
179
|
+
requestId: job.request_id,
|
|
180
|
+
schemaVersion: job.job.schema_version,
|
|
181
|
+
jobType: job.job.job_type
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
...baseAuth(parsed),
|
|
186
|
+
jobId: requireText(explicitJobId || resolveString(parsed.flags["job-id"]), "JOB_ID"),
|
|
187
|
+
nodeId: nodeId(parsed),
|
|
188
|
+
requestId: resolveString(parsed.flags["request-id"]),
|
|
189
|
+
schemaVersion: resolveString(parsed.flags["schema-version"]) || "2026-06-14",
|
|
190
|
+
jobType: resolveString(parsed.flags["job-type"]) || resolveString(parsed.flags.type)
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
function requireText(value, label) {
|
|
194
|
+
if (!value || !value.trim()) {
|
|
195
|
+
throw new Error(`${label} is required`);
|
|
196
|
+
}
|
|
197
|
+
return value.trim();
|
|
198
|
+
}
|
|
199
|
+
const printJsonOrValue = (value, json) => {
|
|
200
|
+
// eslint-disable-next-line no-console
|
|
201
|
+
console.log(json ? JSON.stringify(value, null, 2) : formatValue(value));
|
|
202
|
+
};
|
|
203
|
+
const formatValue = (value) => {
|
|
204
|
+
if (typeof value === "string")
|
|
205
|
+
return value;
|
|
206
|
+
return JSON.stringify(value, null, 2);
|
|
207
|
+
};
|
|
208
|
+
const printSnapshot = (snapshot, json) => {
|
|
209
|
+
if (json) {
|
|
210
|
+
printJsonOrValue(snapshot, true);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const job = snapshot.job;
|
|
214
|
+
// eslint-disable-next-line no-console
|
|
215
|
+
console.log(`${job.job_id} ${job.state}`);
|
|
216
|
+
if (job.backpressure?.message) {
|
|
217
|
+
// eslint-disable-next-line no-console
|
|
218
|
+
console.log(job.backpressure.message);
|
|
219
|
+
}
|
|
220
|
+
if (job.result?.error?.message) {
|
|
221
|
+
// eslint-disable-next-line no-console
|
|
222
|
+
console.log(job.result.error.message);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const formatOpsSummary = (summary) => {
|
|
226
|
+
const lines = [
|
|
227
|
+
`${summary.node.node_id} ${summary.node.generic_jobs_enabled ? "generic-jobs-enabled" : "generic-jobs-disabled"}`,
|
|
228
|
+
`queue active=${summary.queue.active_jobs} queued=${summary.queue.queued_jobs} terminal=${summary.queue.terminal_jobs}`,
|
|
229
|
+
`quota available=${summary.quota.available_slots}/${summary.quota.max_concurrent_jobs} production_enforced=${summary.quota.production_enforced}`,
|
|
230
|
+
`usage total=${summary.usage.total_jobs} succeeded=${summary.usage.succeeded_jobs} failed=${summary.usage.failed_jobs} cancelled=${summary.usage.cancelled_jobs} blocked=${summary.usage.blocked_jobs} gpu_seconds=${summary.usage.gpu_seconds}`,
|
|
231
|
+
];
|
|
232
|
+
if (summary.queue.jobs.length) {
|
|
233
|
+
lines.push("jobs:");
|
|
234
|
+
for (const job of summary.queue.jobs.slice(0, 10)) {
|
|
235
|
+
lines.push(` ${job.job_id} ${job.state} ${job.job_type} tenant=${job.tenant_id}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (summary.audit.events.length) {
|
|
239
|
+
lines.push(`audit offset=${summary.audit.offset} limit=${summary.audit.limit} total=${summary.audit.total}:`);
|
|
240
|
+
for (const event of summary.audit.events.slice(0, 10)) {
|
|
241
|
+
lines.push(` ${event.timestamp} ${event.action} ${event.job_id}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return lines.join("\n");
|
|
245
|
+
};
|
|
246
|
+
const waitForJob = async (api, reference, intervalMs) => {
|
|
247
|
+
let snapshot = await api.getGenericJob(reference);
|
|
248
|
+
while (!TERMINAL_STATES.has(snapshot.job.state)) {
|
|
249
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
250
|
+
snapshot = await api.getGenericJob(reference);
|
|
251
|
+
}
|
|
252
|
+
return snapshot;
|
|
253
|
+
};
|
|
254
|
+
export class GpuCommands {
|
|
255
|
+
static async run(argv) {
|
|
256
|
+
const [subcommand, ...rest] = argv;
|
|
257
|
+
if (!subcommand || argv.includes("--help") || argv.includes("-h")) {
|
|
258
|
+
// eslint-disable-next-line no-console
|
|
259
|
+
console.log(GPU_USAGE);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (subcommand !== "list" && subcommand !== "ops") {
|
|
263
|
+
throw new Error(`Unknown gpu subcommand: ${subcommand}`);
|
|
264
|
+
}
|
|
265
|
+
const parsed = parseArgs(rest);
|
|
266
|
+
const api = await createApi(parsed);
|
|
267
|
+
try {
|
|
268
|
+
if (subcommand === "list") {
|
|
269
|
+
const capabilities = await api.listGpuCapabilities({
|
|
270
|
+
...baseAuth(parsed),
|
|
271
|
+
nodeId: nodeId(parsed)
|
|
272
|
+
});
|
|
273
|
+
printJsonOrValue(capabilities, Boolean(parsed.flags.json));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const ops = await api.getGenericJobOps({
|
|
277
|
+
...baseAuth(parsed),
|
|
278
|
+
nodeId: nodeId(parsed),
|
|
279
|
+
auditLimit: resolvePositiveInt(parsed.flags["audit-limit"], "--audit-limit"),
|
|
280
|
+
auditOffset: resolveNonNegativeInt(parsed.flags["audit-offset"], "--audit-offset")
|
|
281
|
+
});
|
|
282
|
+
// eslint-disable-next-line no-console
|
|
283
|
+
console.log(parsed.flags.json ? JSON.stringify(ops, null, 2) : formatOpsSummary(ops));
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
await api.close();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
export class GpuJobCommands {
|
|
291
|
+
static shouldHandle(argv) {
|
|
292
|
+
const [subcommand] = argv;
|
|
293
|
+
if (!subcommand)
|
|
294
|
+
return false;
|
|
295
|
+
if (GPU_JOB_SUBCOMMANDS.has(subcommand))
|
|
296
|
+
return true;
|
|
297
|
+
if (!SHARED_JOB_SUBCOMMANDS.has(subcommand))
|
|
298
|
+
return false;
|
|
299
|
+
return argv.some((arg) => GPU_JOB_FLAGS.has(arg) || arg.startsWith("--node-base-url="));
|
|
300
|
+
}
|
|
301
|
+
static async run(argv) {
|
|
302
|
+
const [subcommand, ...rest] = argv;
|
|
303
|
+
if (!subcommand || argv.includes("--help") || argv.includes("-h")) {
|
|
304
|
+
// eslint-disable-next-line no-console
|
|
305
|
+
console.log(JOB_USAGE);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const parsed = parseArgs(rest);
|
|
309
|
+
const api = await createApi(parsed);
|
|
310
|
+
try {
|
|
311
|
+
switch (subcommand) {
|
|
312
|
+
case "artifact": {
|
|
313
|
+
const action = parsed.positionals[0];
|
|
314
|
+
const file = parsed.positionals[1];
|
|
315
|
+
if (action !== "upload" || !file) {
|
|
316
|
+
throw new Error("Usage: mcoda job artifact upload <FILE> [options]");
|
|
317
|
+
}
|
|
318
|
+
const content = await readFile(file);
|
|
319
|
+
const artifactPath = resolveString(parsed.flags["artifact-path"]) || basename(file);
|
|
320
|
+
const result = await api.uploadGenericJobArtifact({
|
|
321
|
+
...(await referenceFromArgs(parsed, resolveString(parsed.flags["job-id"]))),
|
|
322
|
+
name: resolveString(parsed.flags["artifact-name"]) || basename(file),
|
|
323
|
+
path: artifactPath,
|
|
324
|
+
contentBase64: content.toString("base64"),
|
|
325
|
+
contentType: resolveString(parsed.flags["content-type"]),
|
|
326
|
+
sha256: createHash("sha256").update(content).digest("hex"),
|
|
327
|
+
sizeBytes: content.length
|
|
328
|
+
});
|
|
329
|
+
printJsonOrValue(result, Boolean(parsed.flags.json));
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
case "run": {
|
|
333
|
+
const job = await readJobEnvelope(parsed);
|
|
334
|
+
const snapshot = await api.runGenericJob(job, baseAuth(parsed));
|
|
335
|
+
const finalSnapshot = parsed.flags.wait
|
|
336
|
+
? await waitForJob(api, await referenceFromArgs(parsed, job.job_id), resolvePositiveInt(parsed.flags["interval-ms"], "--interval-ms") || 1000)
|
|
337
|
+
: snapshot;
|
|
338
|
+
printSnapshot(finalSnapshot, Boolean(parsed.flags.json));
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
case "status": {
|
|
342
|
+
const snapshot = await api.getGenericJob(await referenceFromArgs(parsed, parsed.positionals[0]));
|
|
343
|
+
printSnapshot(snapshot, Boolean(parsed.flags.json));
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
case "logs": {
|
|
347
|
+
const result = await api.getGenericJobLogs(await referenceFromArgs(parsed, parsed.positionals[0]));
|
|
348
|
+
if (parsed.flags.json)
|
|
349
|
+
printJsonOrValue(result, true);
|
|
350
|
+
else
|
|
351
|
+
printJsonOrValue(result.logs.map((log) => log.message).join("\n"), false);
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
case "events": {
|
|
355
|
+
const result = await api.getGenericJobEvents(await referenceFromArgs(parsed, parsed.positionals[0]));
|
|
356
|
+
printJsonOrValue(result, Boolean(parsed.flags.json));
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
case "artifacts": {
|
|
360
|
+
const result = await api.getGenericJobArtifacts(await referenceFromArgs(parsed, parsed.positionals[0]));
|
|
361
|
+
printJsonOrValue(result, Boolean(parsed.flags.json));
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
case "cancel": {
|
|
365
|
+
const result = await api.cancelGenericJob(await referenceFromArgs(parsed, parsed.positionals[0]));
|
|
366
|
+
printSnapshot(result, Boolean(parsed.flags.json));
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
case "retry": {
|
|
370
|
+
const result = await api.retryGenericJob(await referenceFromArgs(parsed, parsed.positionals[0]));
|
|
371
|
+
printSnapshot(result, Boolean(parsed.flags.json));
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
default:
|
|
375
|
+
throw new Error(`Unknown GPU job subcommand: ${subcommand}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
finally {
|
|
379
|
+
await api.close();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JobsCommands.d.ts","sourceRoot":"","sources":["../../../src/commands/jobs/JobsCommands.ts"],"names":[],"mappings":"AACA,OAAO,EAAsE,aAAa,EAA0C,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"JobsCommands.d.ts","sourceRoot":"","sources":["../../../src/commands/jobs/JobsCommands.ts"],"names":[],"mappings":"AACA,OAAO,EAAsE,aAAa,EAA0C,MAAM,aAAa,CAAC;AAIxJ,KAAK,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEvG,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,aAAa,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAuCD,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,EAAE,KAAG,aAyH7C,CAAC;AA8BF,eAAO,MAAM,mBAAmB,GAAI,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM,GAAG,SAiBhF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM,GAAG,SAsB/E,CAAC;AAaF,eAAO,MAAM,eAAe,GAAI,MAAM,aAAa,EAAE,KAAG,IA8CvD,CAAC;AAwJF,qBAAa,YAAY;WACV,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAiPhD"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { JobInsightsService, JobResumeService, JobService, TelemetryService, WorkspaceResolver } from "@mcoda/core";
|
|
3
|
+
import { GpuJobCommands } from "../gpu/GpuCommands.js";
|
|
3
4
|
const usage = `mcoda job <list|status|watch|logs|inspect|resume|cancel|tokens> ...
|
|
4
5
|
|
|
5
6
|
Commands:
|
|
@@ -401,6 +402,10 @@ const handleTokens = async (parsed, workspace) => {
|
|
|
401
402
|
};
|
|
402
403
|
export class JobsCommands {
|
|
403
404
|
static async run(argv) {
|
|
405
|
+
if (GpuJobCommands.shouldHandle(argv)) {
|
|
406
|
+
await GpuJobCommands.run(argv);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
404
409
|
const parsed = parseJobArgs(argv);
|
|
405
410
|
const workspace = await WorkspaceResolver.resolveWorkspace({
|
|
406
411
|
cwd: process.cwd(),
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './bin/McodaEntrypoint.js';
|
|
|
3
3
|
export * from './commands/cloud/CloudCommands.js';
|
|
4
4
|
export * from './commands/self-hosted/SelfHostedCommands.js';
|
|
5
5
|
export * from './commands/workers/WorkersCommands.js';
|
|
6
|
+
export * from './commands/gpu/GpuCommands.js';
|
|
6
7
|
export * from './commands/config/ConfigCommands.js';
|
|
7
8
|
export * from './commands/consent/ConsentCommands.js';
|
|
8
9
|
export * from './commands/docs/DocsCommands.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,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"}
|
|
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,+BAA+B,CAAC;AAC9C,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
|
@@ -3,6 +3,7 @@ export * from './bin/McodaEntrypoint.js';
|
|
|
3
3
|
export * from './commands/cloud/CloudCommands.js';
|
|
4
4
|
export * from './commands/self-hosted/SelfHostedCommands.js';
|
|
5
5
|
export * from './commands/workers/WorkersCommands.js';
|
|
6
|
+
export * from './commands/gpu/GpuCommands.js';
|
|
6
7
|
export * from './commands/config/ConfigCommands.js';
|
|
7
8
|
export * from './commands/consent/ConsentCommands.js';
|
|
8
9
|
export * from './commands/docs/DocsCommands.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcoda",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.78",
|
|
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.
|
|
51
|
-
"@mcoda/shared": "0.1.
|
|
50
|
+
"@mcoda/core": "0.1.78",
|
|
51
|
+
"@mcoda/shared": "0.1.78"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@mcoda/
|
|
55
|
-
"@mcoda/
|
|
54
|
+
"@mcoda/integrations": "0.1.78",
|
|
55
|
+
"@mcoda/db": "0.1.78"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "tsc -p tsconfig.json",
|