mirror of
https://github.com/affaan-m/ECC.git
synced 2026-08-17 21:15:40 +02:00
364 lines
12 KiB
JavaScript
Executable File
364 lines
12 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
||
|
||
"use strict";
|
||
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
const { spawnSync } = require("child_process");
|
||
const {
|
||
createSafeItoEnvironment,
|
||
createSafeItoInvocationEnvironment,
|
||
getInvocationCommand,
|
||
} = require("./lib/ito-environment");
|
||
const {
|
||
authorizeEccCapability,
|
||
parseItoCapabilities,
|
||
} = require("./lib/ito-capabilities");
|
||
|
||
const CANONICAL_REPOSITORY = "https://github.com/Ito-Markets/ito-cloud-runtime.git";
|
||
const CANONICAL_PACKAGE_PATH = "cli/ito-compute-cli";
|
||
const CANONICAL_ENTRY_SEGMENTS = Object.freeze([
|
||
...CANONICAL_PACKAGE_PATH.split("/"),
|
||
"dist",
|
||
"bin",
|
||
"ito.js",
|
||
]);
|
||
const EXECUTABLE_OVERRIDE = "ECC_ITO_CLI_EXECUTABLE";
|
||
const MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
|
||
const CAPABILITY_TIMEOUT_MS = 10 * 1000;
|
||
const NODE_QUALIFICATION_TIMEOUT_MS = 31 * 60 * 1000;
|
||
|
||
function showHelp() {
|
||
process.stdout.write(`
|
||
ECC × Itô local CLI bridge
|
||
|
||
Usage:
|
||
ecc ito login [--no-browser]
|
||
ecc ito logout
|
||
ecc ito auth
|
||
ecc ito find <all required RFQ options>
|
||
ecc ito status
|
||
ecc ito workload-status <run-id>
|
||
ecc ito capabilities
|
||
ecc ito evals --cluster <id> --live-sixtytwo --nodes <list> --config-dir <dir>
|
||
ecc ito <command> --json
|
||
|
||
The bridge invokes the separately installed canonical Itô CLI and returns its
|
||
real stdout, stderr, and exit code unchanged. "ecc ito login" delegates to the
|
||
canonical CLI's device authorization. It opens the Itô verification page by default
|
||
and persists its device token in macOS Keychain. Pass --no-browser to
|
||
suppress that handoff. ECC itself performs no browser automation and adds no
|
||
lock, workload, inference, or purchase path.
|
||
"ecc ito auth" is validation-only and never starts device login.
|
||
"ecc ito logout" asks the canonical CLI to revoke the current device credential
|
||
and remove its local copy only after remote revocation is confirmed.
|
||
|
||
Important:
|
||
- "find" reads live inventory and submits an authenticated RFQ.
|
||
- Obtain explicit buyer authority and every hard constraint before invoking it.
|
||
- "status" reads live RFQ and procurement status.
|
||
- "workload-status" reads an existing entitled workload without changing it.
|
||
- The installed canonical CLI must advertise every command through the local-only
|
||
ito.cli.capabilities.v1 contract. ECC derives read-only forwarding from that
|
||
contract and blocks workload start, cancel, cleanup, purchase, and unknown effects.
|
||
- "evals" invokes only the canonical CLI's double-opt-in, pinned
|
||
sixtytwo-cli node-qualification adapter against explicit nodes.
|
||
- Node qualification cannot rent, launch, recover, repair, or purchase.
|
||
- Inventory and RFQs are not reservations; only a returned firm quote is firm.
|
||
|
||
The canonical package is currently unpublished. Install it locally:
|
||
Canonical source: Ito-Markets/ito-cloud-runtime/${CANONICAL_PACKAGE_PATH}
|
||
git clone ${CANONICAL_REPOSITORY}
|
||
cd ito-cloud-runtime/${CANONICAL_PACKAGE_PATH}
|
||
npm ci
|
||
npm run check
|
||
|
||
Then set ${EXECUTABLE_OVERRIDE} to the explicit absolute built entry:
|
||
/absolute/path/to/ito-cloud-runtime/${CANONICAL_PACKAGE_PATH}/dist/bin/ito.js
|
||
|
||
For safety, ECC never discovers this credential-bearing client through PATH.
|
||
|
||
The same package's MCP server exposes only:
|
||
ito_auth
|
||
ito_find
|
||
ito_status
|
||
|
||
Configure the MCP command as "node" with this absolute argument:
|
||
/absolute/path/to/ito-cloud-runtime/${CANONICAL_PACKAGE_PATH}/dist/bin/ito-mcp.js
|
||
|
||
Device login never inherits ITO_API_KEY. The auth, find, and status commands
|
||
forward ITO_API_KEY directly when configured; ITO_AUTH_MODE=legacy is not
|
||
required. The canonical client stores device credentials in macOS Keychain by
|
||
default; file-token fallback remains explicit and must use restrictive settings.
|
||
Never put a key or token in arguments, tracked files, or chat.
|
||
|
||
Live node qualification requires ITO_ENABLE_SIXTYTWO_LIVE=1,
|
||
--live-sixtytwo, an explicit node list, and an existing absolute config
|
||
directory. It forwards only named SIXTYTWO_API_TOKEN/SIXTYTWO_TOKEN and SSH
|
||
agent state; ITO_API_KEY is intentionally excluded. The canonical CLI requires
|
||
sixtytwo-cli==0.3.33 and fails closed.
|
||
`);
|
||
}
|
||
|
||
function requiredOptionValue(args, option) {
|
||
const indexes = args
|
||
.map((value, index) => (value === option ? index : -1))
|
||
.filter((index) => index >= 0);
|
||
if (indexes.length !== 1) {
|
||
throw new Error(`${option} is required exactly once for live node qualification.`);
|
||
}
|
||
const value = args[indexes[0] + 1];
|
||
if (!value?.trim() || value.startsWith("--")) {
|
||
throw new Error(`${option} requires a non-empty value for live node qualification.`);
|
||
}
|
||
return value;
|
||
}
|
||
|
||
function validateNodeQualificationArgs(args, environment) {
|
||
if (environment.ITO_ENABLE_SIXTYTWO_LIVE !== "1") {
|
||
throw new Error(
|
||
"Live node qualification requires ITO_ENABLE_SIXTYTWO_LIVE=1 before any process is started."
|
||
);
|
||
}
|
||
if (args.filter((value) => value === "--live-sixtytwo").length !== 1) {
|
||
throw new Error(
|
||
"Live node qualification requires --live-sixtytwo exactly once before any process is started."
|
||
);
|
||
}
|
||
requiredOptionValue(args, "--cluster");
|
||
const nodes = requiredOptionValue(args, "--nodes");
|
||
if (!nodes.split(",").every((node) => node.trim().length > 0)) {
|
||
throw new Error("--nodes must explicitly list one or more non-empty nodes.");
|
||
}
|
||
const configDirectory = requiredOptionValue(args, "--config-dir");
|
||
if (!path.isAbsolute(configDirectory)) {
|
||
throw new Error("--config-dir must be an existing absolute directory.");
|
||
}
|
||
try {
|
||
const resolved = fs.realpathSync.native(configDirectory);
|
||
if (
|
||
!fs.statSync(resolved).isDirectory()
|
||
|| !fs.statSync(path.join(resolved, "sixtytwo.yaml")).isFile()
|
||
) {
|
||
throw new Error("invalid qualification configuration");
|
||
}
|
||
} catch {
|
||
throw new Error(
|
||
"--config-dir must exist and contain a regular sixtytwo.yaml before any process is started."
|
||
);
|
||
}
|
||
}
|
||
|
||
function parseArgs(argv, environment = process.env) {
|
||
const args = [...argv];
|
||
if (
|
||
args.length === 0
|
||
|| args.includes("--help")
|
||
|| args.includes("-h")
|
||
) {
|
||
return Object.freeze({ help: true, invocationArgs: [] });
|
||
}
|
||
|
||
if (environment.ECC_DRY_RUN === "1" || args.includes("--dry-run")) {
|
||
throw new Error(
|
||
"Itô compute has no paper or dry-run success mode. No CLI operation was invoked."
|
||
);
|
||
}
|
||
|
||
const jsonIndexes = args
|
||
.map((value, index) => (value === "--json" ? index : -1))
|
||
.filter((index) => index >= 0);
|
||
if (jsonIndexes.length > 1) {
|
||
throw new Error("--json may only be provided once");
|
||
}
|
||
const withoutJson = args.filter((value) => value !== "--json");
|
||
const command = withoutJson.shift();
|
||
if (typeof command !== "string" || !/^[a-z][a-z0-9-]{0,63}$/.test(command)) {
|
||
throw new Error(`Invalid Itô command ${JSON.stringify(command || "(missing)")}.`);
|
||
}
|
||
if (command === "auth" && withoutJson.includes("--no-browser")) {
|
||
throw new Error("--no-browser is valid only for ecc ito login; auth is validation-only.");
|
||
}
|
||
if (command === "evals") {
|
||
validateNodeQualificationArgs(withoutJson, environment);
|
||
}
|
||
|
||
return Object.freeze({
|
||
help: false,
|
||
command,
|
||
invocationArgs: Object.freeze([
|
||
...(jsonIndexes.length === 1 ? ["--json"] : []),
|
||
command,
|
||
...withoutJson,
|
||
]),
|
||
});
|
||
}
|
||
|
||
function resolveItoExecutable(environment = process.env) {
|
||
const configured = environment[EXECUTABLE_OVERRIDE]?.trim();
|
||
if (!configured) {
|
||
throw new Error([
|
||
"The canonical ito-compute-cli is unpublished and ECC will not resolve",
|
||
`a credential-bearing "ito" executable from PATH. Build it from`,
|
||
`${CANONICAL_REPOSITORY.replace(/\.git$/, "")}/${CANONICAL_PACKAGE_PATH},`,
|
||
"run npm ci and npm run check, then set",
|
||
`${EXECUTABLE_OVERRIDE} to the explicit absolute dist/bin/ito.js path.`,
|
||
].join(" "));
|
||
}
|
||
|
||
if (!path.isAbsolute(configured)) {
|
||
throw new Error(
|
||
`${EXECUTABLE_OVERRIDE} must be an absolute path explicitly configured by the operator.`
|
||
);
|
||
}
|
||
return assertUsableExecutable(configured);
|
||
}
|
||
|
||
function assertUsableExecutable(candidate) {
|
||
let canonicalCandidate;
|
||
try {
|
||
canonicalCandidate = fs.realpathSync.native(candidate);
|
||
} catch {
|
||
throw new Error(
|
||
`${EXECUTABLE_OVERRIDE} does not point to a readable local Itô CLI file.`
|
||
);
|
||
}
|
||
if (!isCanonicalItoEntry(canonicalCandidate)) {
|
||
throw new Error(
|
||
`${EXECUTABLE_OVERRIDE} must point to the canonical dist/bin/ito.js entry.`
|
||
);
|
||
}
|
||
if (!isUsableExecutable(canonicalCandidate)) {
|
||
throw new Error(
|
||
`${EXECUTABLE_OVERRIDE} does not point to a readable local Itô CLI file.`
|
||
);
|
||
}
|
||
return canonicalCandidate;
|
||
}
|
||
|
||
function isCanonicalItoEntry(candidate) {
|
||
const pathSegments = path
|
||
.normalize(candidate)
|
||
.split(path.sep)
|
||
.filter(Boolean);
|
||
if (pathSegments.length < CANONICAL_ENTRY_SEGMENTS.length) return false;
|
||
const candidateTail = pathSegments.slice(-CANONICAL_ENTRY_SEGMENTS.length);
|
||
return candidateTail.every((segment, index) => {
|
||
const expected = CANONICAL_ENTRY_SEGMENTS[index];
|
||
return process.platform === "win32"
|
||
? segment.toLowerCase() === expected.toLowerCase()
|
||
: segment === expected;
|
||
});
|
||
}
|
||
|
||
function isUsableExecutable(candidate) {
|
||
try {
|
||
const info = fs.statSync(candidate);
|
||
if (!info.isFile()) return false;
|
||
fs.accessSync(candidate, fs.constants.R_OK);
|
||
return true;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function buildInvocation(executable, args) {
|
||
if (!isCanonicalItoEntry(executable)) {
|
||
throw new Error(
|
||
`Refusing to invoke an Itô CLI shim. Set ${EXECUTABLE_OVERRIDE} to the absolute dist/bin/ito.js path.`
|
||
);
|
||
}
|
||
return Object.freeze({
|
||
executable: process.execPath,
|
||
args: Object.freeze([executable, ...args]),
|
||
});
|
||
}
|
||
|
||
function discoverItoCapabilities(executable, environment = process.env) {
|
||
const invocation = buildInvocation(executable, ["capabilities", "--json"]);
|
||
const result = spawnSync(invocation.executable, invocation.args, {
|
||
cwd: process.cwd(),
|
||
encoding: "utf8",
|
||
env: { ...createSafeItoEnvironment(environment) },
|
||
stdio: ["ignore", "pipe", "pipe"],
|
||
maxBuffer: MAX_OUTPUT_BYTES,
|
||
timeout: CAPABILITY_TIMEOUT_MS,
|
||
shell: false,
|
||
windowsHide: true,
|
||
});
|
||
if (result.error) {
|
||
throw new Error(`The local Itô capability contract could not be read: ${result.error.message}`);
|
||
}
|
||
if (result.status !== 0) {
|
||
throw new Error("The local Itô CLI rejected credential-free capability discovery.");
|
||
}
|
||
return parseItoCapabilities(result.stdout || "");
|
||
}
|
||
|
||
function invokeIto(executable, args, environment = process.env, capability) {
|
||
const invocation = buildInvocation(executable, args);
|
||
const command = getInvocationCommand(args);
|
||
const isNodeQualification = command === "evals";
|
||
const isDeviceLogin = command === "login";
|
||
const result = spawnSync(invocation.executable, invocation.args, {
|
||
cwd: process.cwd(),
|
||
encoding: "utf8",
|
||
// Keep policy helpers immutable for callers, but give child-process
|
||
// instrumentation its own mutable copy (for example NODE_V8_COVERAGE).
|
||
env: {
|
||
...createSafeItoInvocationEnvironment(environment, args, { capability }),
|
||
},
|
||
stdio: isDeviceLogin ? "inherit" : ["pipe", "pipe", "pipe"],
|
||
maxBuffer: MAX_OUTPUT_BYTES,
|
||
timeout: isNodeQualification ? NODE_QUALIFICATION_TIMEOUT_MS : undefined,
|
||
shell: false,
|
||
windowsHide: true,
|
||
});
|
||
|
||
if (result.stdout) process.stdout.write(result.stdout);
|
||
if (result.stderr) process.stderr.write(result.stderr);
|
||
if (result.error) {
|
||
throw new Error(`The local Itô CLI could not be started: ${result.error.message}`);
|
||
}
|
||
if (typeof result.status === "number") return result.status;
|
||
if (result.signal) {
|
||
throw new Error(`The local Itô CLI terminated by signal ${result.signal}.`);
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
function main(argv = process.argv.slice(2), environment = process.env) {
|
||
try {
|
||
const parsed = parseArgs(argv, environment);
|
||
if (parsed.help) {
|
||
showHelp();
|
||
return 0;
|
||
}
|
||
const executable = resolveItoExecutable(environment);
|
||
const manifest = discoverItoCapabilities(executable, environment);
|
||
const capability = authorizeEccCapability(manifest, parsed.command);
|
||
return invokeIto(executable, parsed.invocationArgs, environment, capability);
|
||
} catch (error) {
|
||
console.error(`Error: ${error.message}`);
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
if (require.main === module) {
|
||
process.exitCode = main();
|
||
}
|
||
|
||
module.exports = Object.freeze({
|
||
CANONICAL_PACKAGE_PATH,
|
||
CANONICAL_REPOSITORY,
|
||
CAPABILITY_TIMEOUT_MS,
|
||
EXECUTABLE_OVERRIDE,
|
||
NODE_QUALIFICATION_TIMEOUT_MS,
|
||
buildInvocation,
|
||
discoverItoCapabilities,
|
||
invokeIto,
|
||
main,
|
||
parseArgs,
|
||
resolveItoExecutable,
|
||
});
|