@allocator-one/infraone 0.0.0-stage → 1.17.1

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.
@@ -0,0 +1,372 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ guardBrokenPipes,
4
+ runOperatorMount
5
+ } from "./chunk-67NX7MJ2.js";
6
+ import {
7
+ InfraoneClient,
8
+ commandSpecs,
9
+ describeSession,
10
+ loadSpec,
11
+ login,
12
+ logout,
13
+ mountCommands,
14
+ refreshSpec,
15
+ resolveConfig,
16
+ transferFor,
17
+ whoami
18
+ } from "./chunk-XA5UDNJI.js";
19
+ import "./chunk-WSDFLMO6.js";
20
+ import {
21
+ CommandError,
22
+ HttpError,
23
+ LocalError,
24
+ TransportError,
25
+ UsageError,
26
+ clean,
27
+ table
28
+ } from "./chunk-BHBEIT5I.js";
29
+ import {
30
+ CLI_VERSION
31
+ } from "./chunk-5THYIKLW.js";
32
+
33
+ // src/infraone/cli.ts
34
+ import { createInterface } from "readline";
35
+ import { Command, CommanderError } from "commander";
36
+
37
+ // src/infraone/run.ts
38
+ import { randomUUID } from "crypto";
39
+
40
+ // src/infraone/output.ts
41
+ var MAX_COLUMNS = 6;
42
+ var LEADING = ["id", "name", "title", "type", "kind", "status", "role", "email"];
43
+ function render(data, schema) {
44
+ if (Array.isArray(data)) return renderList(data, schema?.type === "array" ? schema.items ?? null : schema);
45
+ if (isRecord(data)) return renderRecord(data, schema);
46
+ if (data === null || data === void 0) return "";
47
+ return `${clean(data)}
48
+ `;
49
+ }
50
+ function renderList(rows, itemSchema) {
51
+ if (rows.length === 0) return "No results.\n";
52
+ const records = rows.filter(isRecord);
53
+ if (records.length !== rows.length) return rows.map((row) => `${cell(row)}
54
+ `).join("");
55
+ const columns = listColumns(records, itemSchema);
56
+ if (columns.length === 0) return rows.map((row) => `${cell(row)}
57
+ `).join("");
58
+ return table(
59
+ columns.map((column) => column.toUpperCase()),
60
+ records.map((record) => columns.map((column) => cell(record[column])))
61
+ );
62
+ }
63
+ function renderRecord(record, schema) {
64
+ const keys = ordered([.../* @__PURE__ */ new Set([...Object.keys(schema?.properties ?? {}), ...Object.keys(record)])]).filter(
65
+ (key) => key in record
66
+ );
67
+ if (keys.length === 0) return "";
68
+ const width = Math.max(...keys.map((key) => key.length));
69
+ return keys.map((key) => `${key.padEnd(width)} ${cell(record[key])}
70
+ `).join("");
71
+ }
72
+ function listColumns(records, itemSchema) {
73
+ const fromSchema = Object.entries(itemSchema?.properties ?? {}).filter(([, property]) => property.type !== "object" && property.type !== "array").map(([name]) => name);
74
+ const candidates = fromSchema.length > 0 ? fromSchema : [...new Set(records.flatMap((record) => Object.keys(record)))].filter(
75
+ (key) => records.some((record) => isScalar(record[key]))
76
+ );
77
+ return ordered(candidates).slice(0, MAX_COLUMNS);
78
+ }
79
+ function ordered(keys) {
80
+ const leading = LEADING.filter((key) => keys.includes(key));
81
+ const rest = keys.filter((key) => !LEADING.includes(key));
82
+ return [...leading, ...rest.filter((key) => !key.endsWith("_id")), ...rest.filter((key) => key.endsWith("_id"))];
83
+ }
84
+ function cell(value) {
85
+ if (value === null || value === void 0) return "";
86
+ if (typeof value === "object") return clean(JSON.stringify(value));
87
+ return clean(value);
88
+ }
89
+ function isScalar(value) {
90
+ return value === null || ["string", "number", "boolean"].includes(typeof value);
91
+ }
92
+ function isRecord(value) {
93
+ return typeof value === "object" && value !== null && !Array.isArray(value);
94
+ }
95
+
96
+ // src/infraone/run.ts
97
+ var NEVER_SENT = /* @__PURE__ */ new Set(["ECONNREFUSED", "ENOTFOUND", "EAI_AGAIN", "EHOSTUNREACH", "ENETUNREACH", "CERT_HAS_EXPIRED", "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN", "UNABLE_TO_VERIFY_LEAF_SIGNATURE", "ERR_TLS_CERT_ALTNAME_INVALID"]);
98
+ var MAX_PAGES = 1e4;
99
+ async function runOperation(context, command, invocation) {
100
+ if (!context.signedIn) throw new UsageError(context.loginHint);
101
+ const path = command.path.replace(/\{([a-z][a-z0-9_]*)\}/g, (_, name) => {
102
+ const value = invocation.pathValues[name];
103
+ if (value === void 0 || value === "") throw new UsageError(`${name} must not be empty`);
104
+ if (value === "." || value === "..") throw new UsageError(`${name} cannot be ${value}`);
105
+ return encodeURIComponent(value);
106
+ });
107
+ const confirmed = await confirm(context, command, invocation);
108
+ const writes = command.risk !== "read";
109
+ if (writes) context.stderr(`\u2192 ${await context.target()}
110
+ `);
111
+ let data;
112
+ const transfer = transferFor(command);
113
+ if (transfer !== null) {
114
+ data = await transfer.run({
115
+ client: context.client,
116
+ command,
117
+ invocation,
118
+ path,
119
+ operations: context.operations,
120
+ confirmed,
121
+ interactive: context.interactive,
122
+ prompt: context.prompt,
123
+ target: context.target,
124
+ stderr: context.stderr
125
+ });
126
+ } else {
127
+ data = await send(context, command, path, invocation, confirmed, writes);
128
+ }
129
+ if (invocation.json) {
130
+ context.stdout(JSON.stringify(data ?? null) + "\n");
131
+ return;
132
+ }
133
+ const human = render(data, command.paged ? { type: "array", ...command.dataSchema === null ? {} : { items: command.dataSchema } } : command.dataSchema);
134
+ context.stdout(human === "" && writes ? "Done.\n" : human);
135
+ }
136
+ async function confirm(context, command, invocation) {
137
+ if (command.risk !== "destructive" && command.risk !== "outward") return false;
138
+ if (invocation.yes) return true;
139
+ const name = command.words.join(" ");
140
+ if (!context.interactive) {
141
+ throw new UsageError(
142
+ `${name} is ${article(command.risk)} ${command.risk} operation and needs your confirmation: run it at a terminal, or pass --yes \u2014 nothing was sent`
143
+ );
144
+ }
145
+ const what = command.risk === "destructive" ? "It deletes or revokes something" : "It reaches people outside Infra One";
146
+ const answer = await context.prompt(`${clean(command.summary || name)} \u2014 ${what}, in ${await context.target()}. Continue? [y/N] `);
147
+ if (!/^y(es)?$/i.test(answer.trim())) throw new LocalError("cancelled \u2014 nothing was sent");
148
+ return true;
149
+ }
150
+ async function send(context, command, path, invocation, confirmed, writes) {
151
+ const request = async (query) => {
152
+ let answer2;
153
+ try {
154
+ answer2 = await context.client.request(command.method, path, {
155
+ query,
156
+ body: invocation.body,
157
+ confirmed,
158
+ ...writes ? { idempotencyKey: randomUUID() } : {}
159
+ });
160
+ } catch (error) {
161
+ if (writes && error instanceof TransportError && !NEVER_SENT.has(error.code ?? "")) {
162
+ throw new CommandError(
163
+ `outcome unknown: the write may have reached the API (${error.message}) \u2014 check before running it again`
164
+ );
165
+ }
166
+ throw error;
167
+ }
168
+ context.observe(answer2);
169
+ return answer2;
170
+ };
171
+ const first = await request(invocation.query);
172
+ if (!command.paged) return first.body["data"];
173
+ const rows = [];
174
+ let answer = first;
175
+ const seen = /* @__PURE__ */ new Set();
176
+ for (let page = 1; ; page++) {
177
+ const data = answer.body["data"];
178
+ if (!Array.isArray(data)) throw new CommandError("the API answered a list with no data list");
179
+ rows.push(...data);
180
+ const next = nextCursor(answer);
181
+ if (next === null) return rows;
182
+ if (!invocation.all) {
183
+ context.stderr(`More results: --cursor ${clean(next)} for the next page, or --all for every page.
184
+ `);
185
+ return rows;
186
+ }
187
+ if (seen.has(next) || page >= MAX_PAGES) throw new CommandError("the API kept answering with more pages \u2014 stopped");
188
+ seen.add(next);
189
+ answer = await request({ ...invocation.query, cursor: next });
190
+ }
191
+ }
192
+ function nextCursor(answer) {
193
+ const meta = answer.body["meta"];
194
+ if (typeof meta !== "object" || meta === null) return null;
195
+ const next = meta["next_cursor"];
196
+ return typeof next === "string" && next !== "" ? next : null;
197
+ }
198
+ function article(word) {
199
+ return /^[aeiou]/.test(word) ? "an" : "a";
200
+ }
201
+
202
+ // src/infraone/cli.ts
203
+ var PREFIX = "infraone: ";
204
+ async function run(args, io) {
205
+ const resolved = resolveConfig(io.env);
206
+ if (!resolved.ok) {
207
+ io.stderr(PREFIX + resolved.message + "\n");
208
+ return 2;
209
+ }
210
+ const config = resolved.config;
211
+ const client = new InfraoneClient(config.apiUrl, config.token, io.fetchImpl);
212
+ if (args.length === 0) {
213
+ const program = buildProgram(config, client, await loadSpec(config), io, { observed: null });
214
+ program.outputHelp({ error: true });
215
+ return 2;
216
+ }
217
+ let loaded = await loadSpec(config);
218
+ const state = { observed: null };
219
+ let code = await attempt(buildProgram(config, client, loaded, io, state), args, io, true);
220
+ if (code === UNKNOWN_COMMAND) {
221
+ loaded = await refreshSpec(config, client) ?? loaded;
222
+ code = await attempt(buildProgram(config, client, loaded, io, state), args, io, false);
223
+ }
224
+ if (state.observed !== null && state.observed !== loaded.fingerprint) await refreshSpec(config, client);
225
+ return code;
226
+ }
227
+ var UNKNOWN_COMMAND = -1;
228
+ async function attempt(program, args, io, quietUnknown) {
229
+ const errors = [];
230
+ program.configureOutput({
231
+ writeOut: io.stdout,
232
+ // Held back on a first attempt, so an unknown command that a fresher
233
+ // spec knows prints nothing about the miss.
234
+ writeErr: (text) => quietUnknown ? errors.push(text) : io.stderr(text),
235
+ outputError: (text, write) => write(text)
236
+ });
237
+ for (const command of allCommands(program)) command.configureOutput(program.configureOutput());
238
+ try {
239
+ await program.parseAsync(args, { from: "user" });
240
+ return 0;
241
+ } catch (error) {
242
+ if (error instanceof CommanderError) {
243
+ if (quietUnknown && error.code === "commander.unknownCommand") return UNKNOWN_COMMAND;
244
+ for (const text of errors) io.stderr(text);
245
+ if (error.code === "commander.helpDisplayed" || error.code === "commander.version") return 0;
246
+ return 2;
247
+ }
248
+ for (const text of errors) io.stderr(text);
249
+ return reportError(error, io);
250
+ }
251
+ }
252
+ function reportError(error, io) {
253
+ if (error instanceof UsageError) {
254
+ io.stderr(PREFIX + clean(error.message) + "\n");
255
+ return 2;
256
+ }
257
+ if (error instanceof HttpError) {
258
+ const requestId = typeof error.body["request_id"] === "string" ? ` (request ${clean(error.body["request_id"])})` : "";
259
+ const approval = approvalUrl(error.body);
260
+ io.stderr(
261
+ `${PREFIX}${clean(error.message)}${requestId}
262
+ ` + (approval === null ? "" : `Approve it at ${clean(approval)}, then run the command again.
263
+ `)
264
+ );
265
+ return 1;
266
+ }
267
+ if (error instanceof TransportError) {
268
+ io.stderr(`${PREFIX}cannot reach the API: ${clean(error.message)}
269
+ `);
270
+ return 3;
271
+ }
272
+ if (error instanceof LocalError || error instanceof CommandError) {
273
+ io.stderr(PREFIX + clean(error.message) + "\n");
274
+ return 1;
275
+ }
276
+ throw error;
277
+ }
278
+ function approvalUrl(body) {
279
+ const pending = body["confirmation"];
280
+ const url = typeof pending === "object" && pending !== null ? pending["approval_url"] : body["approval_url"];
281
+ return typeof url === "string" && url !== "" ? url : null;
282
+ }
283
+ function buildProgram(config, client, loaded, io, state) {
284
+ const program = new Command("infraone").description("Infra One from the terminal: your data rooms, with exactly your app rights").exitOverride().helpCommand(true);
285
+ let target = null;
286
+ const operations = commandSpecs(loaded.spec);
287
+ const context = {
288
+ client,
289
+ signedIn: config.token !== null,
290
+ loginHint: `not signed in to ${config.apiUrl}: run \`infraone login\`, or set INFRAONE_TOKEN for scripts`,
291
+ stdout: io.stdout,
292
+ stderr: io.stderr,
293
+ interactive: io.interactive,
294
+ prompt: io.prompt,
295
+ target: () => target ??= writeTarget(config, client),
296
+ observe: (answer) => {
297
+ if (answer.fingerprint !== null) state.observed = answer.fingerprint;
298
+ },
299
+ operations
300
+ };
301
+ mountCommands(program, operations, (command, invocation) => runOperation(context, command, invocation));
302
+ const general = "General:";
303
+ program.command("login").description("sign in to Infra One in the browser and store the credential").option("--no-browser", "print the sign-in link and paste the token instead").option("--json", "print the result as JSON").helpGroup(general).action(async (options) => {
304
+ finish(await login(config, { noBrowser: !options.browser, fetchImpl: io.fetchImpl, openBrowser: io.openBrowser, notify: (line) => io.stderr(line + "\n"), prompt: () => io.prompt("Token: "), interactive: io.interactive, env: io.env }), options.json === true, io);
305
+ });
306
+ program.command("logout").description("revoke and forget the stored credential").option("--json", "print the result as JSON").helpGroup(general).action(async (options) => {
307
+ finish(await logout(config, { fetchImpl: io.fetchImpl, env: io.env }), options.json === true, io);
308
+ });
309
+ program.command("whoami").description("show the API, the credential and who it signs in as").option("--json", "print the result as JSON").helpGroup(general).action(async (options) => {
310
+ finish(await whoami(config, client), options.json === true, io);
311
+ });
312
+ program.command("version").description("show the CLI version and the spec its commands come from").option("--json", "print the result as JSON").helpGroup(general).action((options) => {
313
+ const apiVersion = loaded.spec.info?.version ?? null;
314
+ const spec = { api_version: apiVersion, source: loaded.source, fingerprint: loaded.fingerprint };
315
+ io.stdout(
316
+ options.json === true ? JSON.stringify({ cli: CLI_VERSION, api: config.apiUrl, spec }) + "\n" : `infraone ${CLI_VERSION} (API ${clean(apiVersion ?? "unknown")} at ${config.apiUrl}, spec ${loaded.source})
317
+ `
318
+ );
319
+ });
320
+ program.command("mcp").description("serve Infra One's tools to an AI client over stdio (MCP), with your stored login").helpGroup(general).action(async () => {
321
+ const { startInfraoneMcp } = await import("./infraone-7TAHY4RN.js");
322
+ await startInfraoneMcp({ env: io.env, ...io.fetchImpl === void 0 ? {} : { fetchImpl: io.fetchImpl } });
323
+ });
324
+ program.command("operator").description("drive Operator: every operator command, with its own login on Harness").helpGroup(general).allowUnknownOption().allowExcessArguments().action(() => {
325
+ throw new UsageError("run `infraone operator \u2026` as its own command line");
326
+ });
327
+ program.commandsGroup(general).helpCommand(true);
328
+ return program;
329
+ }
330
+ async function writeTarget(config, client) {
331
+ const session = await describeSession(client).catch(() => null);
332
+ const org = session?.org;
333
+ const name = clean(org?.["name"] ?? org?.["slug"] ?? "");
334
+ return name === "" ? new URL(config.apiUrl).host : name;
335
+ }
336
+ function finish(result, json, io) {
337
+ if (result.ok) {
338
+ io.stdout(json ? JSON.stringify(result.json) + "\n" : result.human);
339
+ return;
340
+ }
341
+ throw new LocalError(result.message);
342
+ }
343
+ function allCommands(command) {
344
+ return command.commands.flatMap((child) => [child, ...allCommands(child)]);
345
+ }
346
+ function promptLine(question) {
347
+ const readline = createInterface({ input: process.stdin, output: process.stderr });
348
+ return new Promise((resolve) => {
349
+ readline.question(question, (answer) => {
350
+ readline.close();
351
+ resolve(answer);
352
+ });
353
+ });
354
+ }
355
+ async function main(argv = process.argv.slice(2)) {
356
+ if (argv[0] === "operator") return runOperatorMount(argv.slice(1));
357
+ if (argv[0] === "help" && argv[1] === "operator") return runOperatorMount(["--help", ...argv.slice(2)]);
358
+ guardBrokenPipes();
359
+ const code = await run(argv, {
360
+ env: process.env,
361
+ stdout: (text) => process.stdout.write(text),
362
+ stderr: (text) => process.stderr.write(text),
363
+ interactive: process.stdin.isTTY === true && process.stderr.isTTY === true,
364
+ prompt: promptLine
365
+ });
366
+ const drain = (stream) => new Promise((resolve) => stream.write("", () => resolve()));
367
+ await Promise.all([drain(process.stdout), drain(process.stderr)]);
368
+ process.exit(code);
369
+ }
370
+
371
+ // src/infraone/main.ts
372
+ await main();