@onesi/genesis 0.2.4 → 0.2.5
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/README.md +1 -1
- package/dist/client.d.ts +10 -0
- package/dist/client.js +20 -1
- package/dist/fleets.d.ts +4 -1
- package/dist/fleets.js +10 -2
- package/dist/generated/operations.js +177 -150
- package/dist/generated/schema.d.ts +153 -131
- package/dist/genesis.d.ts +15 -5
- package/dist/genesis.js +2 -0
- package/dist/genesis.openapi.json +182 -154
- package/dist/hives-project.d.ts +18 -0
- package/dist/hives-project.js +51 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/urls.js +36 -14
- package/package.json +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { GenesisError } from './errors.js';
|
|
2
|
+
import type { OperationMeta } from './types.js';
|
|
3
|
+
/** The one description of a Hives `project_id` (spec, CLI help and MCP schemas). */
|
|
4
|
+
export declare const HIVES_PROJECT_ID_DESCRIPTION = "Hives project id (prj_\u2026)";
|
|
5
|
+
/** Appended where an omitted project_id resolves from the selected Genesis context. */
|
|
6
|
+
export declare const HIVES_PROJECT_ID_DEFAULT = "omit it to use the Hives project bound to your selected Genesis context";
|
|
7
|
+
/** A Genesis project id (`pr_…`). A Hives project id is `prj_…`, so the two never collide. */
|
|
8
|
+
export declare const isGenesisProjectId: (value: unknown) => value is string;
|
|
9
|
+
/** Hives operations addressed by a Hives project in their path. */
|
|
10
|
+
export declare const isHivesProjectOperation: (op: OperationMeta | undefined) => boolean;
|
|
11
|
+
/** A Genesis pr_… id given where Hives wants its own prj_… id: refused before any request. */
|
|
12
|
+
export declare function hivesProjectIdRefusal(value: string, operationId?: string): GenesisError;
|
|
13
|
+
/**
|
|
14
|
+
* Map a Hives project argument to a Hives project id. A prj_… (or any non-Genesis) value is returned unchanged.
|
|
15
|
+
* Omitted, or a Genesis pr_… id that is the selected context's project, resolves to that selection's Hives binding
|
|
16
|
+
* read from `hives_get_context_project`; anything else is refused with how to find the prj_… id.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveHivesProjectId(value: unknown, readContext: () => Promise<unknown>): Promise<string>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { GenesisError } from './errors.js';
|
|
2
|
+
/** The one description of a Hives `project_id` (spec, CLI help and MCP schemas). */
|
|
3
|
+
export const HIVES_PROJECT_ID_DESCRIPTION = 'Hives project id (prj_…)';
|
|
4
|
+
/** Appended where an omitted project_id resolves from the selected Genesis context. */
|
|
5
|
+
export const HIVES_PROJECT_ID_DEFAULT = 'omit it to use the Hives project bound to your selected Genesis context';
|
|
6
|
+
/** A Genesis project id (`pr_…`). A Hives project id is `prj_…`, so the two never collide. */
|
|
7
|
+
export const isGenesisProjectId = (value) => typeof value === 'string' && /^pr_[A-Za-z0-9]{1,124}$/.test(value);
|
|
8
|
+
const HIVES_PROJECT = /^prj_[A-Za-z0-9]{1,124}$/;
|
|
9
|
+
/** Hives operations addressed by a Hives project in their path. */
|
|
10
|
+
export const isHivesProjectOperation = (op) => op?.service === 'hives' && op.pathParams.includes('project_id');
|
|
11
|
+
const FIND = 'Find the Hives project bound to your selected Genesis context with `genesis hives context get` (SDK: genesis.hives.resolveProject()), or list Hives projects with `genesis hives projects list`.';
|
|
12
|
+
/** A Genesis pr_… id given where Hives wants its own prj_… id: refused before any request. */
|
|
13
|
+
export function hivesProjectIdRefusal(value, operationId) {
|
|
14
|
+
return new GenesisError({ status: 0, type: 'invalid_request', code: 'HIVES_PROJECT_ID_REQUIRED', operationId,
|
|
15
|
+
message: `${value} is a Genesis project id. Hives operations take a Hives project id (prj_…). ${FIND}` });
|
|
16
|
+
}
|
|
17
|
+
const notBound = (message, from) => new GenesisError({ status: from?.status ?? 0, type: 'invalid_request', code: 'HIVES_PROJECT_NOT_BOUND',
|
|
18
|
+
message, requestId: from?.requestId, operationId: 'hives_get_context_project' });
|
|
19
|
+
/**
|
|
20
|
+
* Map a Hives project argument to a Hives project id. A prj_… (or any non-Genesis) value is returned unchanged.
|
|
21
|
+
* Omitted, or a Genesis pr_… id that is the selected context's project, resolves to that selection's Hives binding
|
|
22
|
+
* read from `hives_get_context_project`; anything else is refused with how to find the prj_… id.
|
|
23
|
+
*/
|
|
24
|
+
export async function resolveHivesProjectId(value, readContext) {
|
|
25
|
+
if (value !== undefined && value !== null && value !== '' && !isGenesisProjectId(value)) {
|
|
26
|
+
if (typeof value !== 'string')
|
|
27
|
+
throw new GenesisError({ status: 0, type: 'invalid_request', code: 'HIVES_PROJECT_ID_REQUIRED', message: `project_id must be a ${HIVES_PROJECT_ID_DESCRIPTION}.` });
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
let context;
|
|
31
|
+
try {
|
|
32
|
+
context = await readContext();
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
if (!(e instanceof GenesisError) || ![404, 409].includes(e.status))
|
|
36
|
+
throw e;
|
|
37
|
+
const named = isGenesisProjectId(value) ? `${value} is a Genesis project id and ` : '';
|
|
38
|
+
throw notBound(e.status === 404
|
|
39
|
+
? `${named}No Hives project is bound to your selected Genesis context. Bind one with \`genesis hives context create <name>\` or \`genesis hives context link <prj_…>\`, or pass a Hives project id (prj_…) from \`genesis hives projects list\`.`
|
|
40
|
+
: `${named}No Genesis project is selected. Select an organization, workspace and project with \`genesis context select\`, or pass a Hives project id (prj_…) from \`genesis hives projects list\`.`, e);
|
|
41
|
+
}
|
|
42
|
+
const row = context && typeof context === 'object' ? context : {};
|
|
43
|
+
const selected = row.selectedContext?.projectId, bound = row.binding?.hivesProjectId;
|
|
44
|
+
if (typeof bound !== 'string' || !HIVES_PROJECT.test(bound) || typeof selected !== 'string') {
|
|
45
|
+
throw new GenesisError({ status: 0, type: 'invalid_response', operationId: 'hives_get_context_project', message: 'Hives returned an invalid context binding.' });
|
|
46
|
+
}
|
|
47
|
+
if (isGenesisProjectId(value) && value !== selected) {
|
|
48
|
+
throw notBound(`${value} is a Genesis project id, and it is not your selected Genesis project (${selected}, bound to Hives project ${bound}). Select it with \`genesis context select\`, or pass its Hives project id (prj_…) from \`genesis hives projects list\`.`);
|
|
49
|
+
}
|
|
50
|
+
return bound;
|
|
51
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type { paths, operations, components } from './generated/schema.js';
|
|
|
11
11
|
export { hivesUploadBody, MAX_WASM_BYTES, type HivesUploadOptions } from './hives-upload.js';
|
|
12
12
|
export { validateHivesInvocation, hivesFunctionResponseHeaders, HivesOperationError, MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_JSON_BYTES, type HivesInvokeRequest, type HivesInvokeOptions, type HivesOperationStatus } from './hives-functions.js';
|
|
13
13
|
export { hivesResponseBytes } from './hives-response.js';
|
|
14
|
+
export { HIVES_PROJECT_ID_DESCRIPTION, HIVES_PROJECT_ID_DEFAULT, isGenesisProjectId, isHivesProjectOperation, hivesProjectIdRefusal } from './hives-project.js';
|
|
14
15
|
export { runnerModule, type RunnerStartInput, type RunnerSnapshot, type RunnerCatalog, type RunnerEvent, type RunnerProviderResult, type RunnerRequestOptions } from './runner.js';
|
|
15
16
|
export type { RunnerSelfExecutionGrantInput, RunnerSelfExecutionGrant } from './runner-grant.js';
|
|
16
17
|
export { beaconsModule, BEACONS_RESOURCE_FAMILIES, BEACONS_CLIENT_PLATFORMS, type BeaconsResourceFamily } from './beacons.js';
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { operationTable, services } from './generated/operations.js';
|
|
|
9
9
|
export { hivesUploadBody, MAX_WASM_BYTES } from './hives-upload.js';
|
|
10
10
|
export { validateHivesInvocation, hivesFunctionResponseHeaders, HivesOperationError, MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_JSON_BYTES } from './hives-functions.js';
|
|
11
11
|
export { hivesResponseBytes } from './hives-response.js';
|
|
12
|
+
export { HIVES_PROJECT_ID_DESCRIPTION, HIVES_PROJECT_ID_DEFAULT, isGenesisProjectId, isHivesProjectOperation, hivesProjectIdRefusal } from './hives-project.js';
|
|
12
13
|
export { runnerModule } from './runner.js';
|
|
13
14
|
export { beaconsModule, BEACONS_RESOURCE_FAMILIES, BEACONS_CLIENT_PLATFORMS } from './beacons.js';
|
|
14
15
|
export { rigsModule, panesModule, shellsModule } from './instances.js';
|
package/dist/urls.js
CHANGED
|
@@ -41,34 +41,54 @@ const ENCODED_USERINFO = /(%(?:25){0,2}2F%(?:25){0,2}2F)(?:(?!%(?:25){0,2}2F)[^\
|
|
|
41
41
|
const BARE_USERINFO = /(^|[\s"'(<=,;[{])[^\s/?#@@:"'<>\\]{1,64}:[^\s/?#"'<>\\]{0,128}[@@](?=[\w[])/g;
|
|
42
42
|
/** Every place a userinfo can end: `@`, fullwidth `@`, or an encoded `%40` / `%2540`. */
|
|
43
43
|
const AT_HITS = /[@@]|%(?:25){0,2}40/gi;
|
|
44
|
+
/** The scheme-less form can only end at an `@` or `@` that is followed by a word character or `[` (see BARE_USERINFO). */
|
|
45
|
+
const BARE_HITS = /[@@](?=[\w[])/g;
|
|
44
46
|
/** The longest URL-form match (slashes and userinfo, encoded) is under 640 characters; the bare form is under 200. */
|
|
45
47
|
const URL_WINDOW = 700;
|
|
46
48
|
const BARE_WINDOW = 200;
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
/** With at least one hit per DENSE characters the windows would cover the text anyway, so it is scanned whole. The regexes are linear on their own; the windows only skip stretches that cannot hold a userinfo. */
|
|
50
|
+
const DENSE = 64;
|
|
51
|
+
/** Start and end of every hit of `re` in `text`, flattened; `null` when they are dense. */
|
|
52
|
+
function hitSpans(text, re) {
|
|
53
|
+
const limit = Math.floor(text.length / DENSE);
|
|
54
|
+
const spans = [];
|
|
55
|
+
re.lastIndex = 0;
|
|
56
|
+
for (let hit = re.exec(text); hit; hit = re.exec(text)) {
|
|
57
|
+
if (spans.length >= 2 * limit)
|
|
58
|
+
return null;
|
|
59
|
+
spans.push(hit.index, hit.index + hit[0].length);
|
|
60
|
+
}
|
|
61
|
+
return spans;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Apply `fn` to the stretches of `text` within `window` characters before a hit, and copy the rest as it is.
|
|
65
|
+
* `spans` of `null` means dense: `fn` runs over the whole text. A stretch without the character `needs` cannot hold a match.
|
|
66
|
+
*/
|
|
67
|
+
function overHits(text, spans, window, fn, needs = '') {
|
|
68
|
+
if (spans === null)
|
|
69
|
+
return fn(text);
|
|
70
|
+
if (spans.length === 0)
|
|
52
71
|
return text;
|
|
53
72
|
let out = '';
|
|
54
73
|
let copied = 0;
|
|
55
|
-
let start = Math.max(0,
|
|
56
|
-
let end =
|
|
74
|
+
let start = Math.max(0, spans[0] - window);
|
|
75
|
+
let end = spans[1] + 1;
|
|
57
76
|
const flush = () => {
|
|
77
|
+
const seg = text.slice(start, end);
|
|
58
78
|
// A sentinel before a window that does not start the text keeps `^` (start of text) from matching mid-text.
|
|
59
|
-
const piece = start > 0 ? fn(`x${
|
|
79
|
+
const piece = needs && !seg.includes(needs) ? seg : start > 0 ? fn(`x${seg}`).slice(1) : fn(seg);
|
|
60
80
|
out += text.slice(copied, start) + piece;
|
|
61
81
|
copied = Math.min(end, text.length);
|
|
62
82
|
};
|
|
63
|
-
for (
|
|
64
|
-
const s = Math.max(0,
|
|
83
|
+
for (let i = 2; i < spans.length; i += 2) {
|
|
84
|
+
const s = Math.max(0, spans[i] - window);
|
|
65
85
|
if (s <= end) {
|
|
66
|
-
end =
|
|
86
|
+
end = spans[i + 1] + 1;
|
|
67
87
|
continue;
|
|
68
88
|
}
|
|
69
89
|
flush();
|
|
70
90
|
start = s;
|
|
71
|
-
end =
|
|
91
|
+
end = spans[i + 1] + 1;
|
|
72
92
|
}
|
|
73
93
|
flush();
|
|
74
94
|
return out + text.slice(copied);
|
|
@@ -83,8 +103,10 @@ export function redactUserinfo(text, mask = '***') {
|
|
|
83
103
|
// Pre-check: no `@`, `@` or `%40` means no userinfo in any form.
|
|
84
104
|
if (!/[@@]|%(?:25){0,2}40/i.test(text))
|
|
85
105
|
return text;
|
|
86
|
-
const urlForms = overHits(text, URL_WINDOW, (t) => t.replace(ENCODED_USERINFO, `$1${mask}%40`).replace(USERINFO, `$1${mask}@`));
|
|
87
|
-
|
|
106
|
+
const urlForms = overHits(text, hitSpans(text, AT_HITS), URL_WINDOW, (t) => t.replace(ENCODED_USERINFO, `$1${mask}%40`).replace(USERINFO, `$1${mask}@`));
|
|
107
|
+
if (!urlForms.includes(':'))
|
|
108
|
+
return urlForms; // the scheme-less form needs its colon
|
|
109
|
+
return overHits(urlForms, hitSpans(urlForms, BARE_HITS), BARE_WINDOW, (t) => t.replace(BARE_USERINFO, `$1${mask}@`), ':');
|
|
88
110
|
}
|
|
89
111
|
/** `url` without its userinfo (`https://u:p@host/x` becomes `https://host/x`); text that is not an absolute URL passes through redactUserinfo. */
|
|
90
112
|
export function stripUserinfo(url) {
|