UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

187 lines • 7.44 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { concat } from "../../../../utils/bytes.js"; import { NotionAccessor } from "../../../../accessor/notion.js"; import { HttpNotionTransport } from "../../../../core/notion/client.js"; import { IOResult } from "../../../../io/types.js"; import { FlagView } from "../../../spec/flag_view.js"; import { asObject, stringOf } from "../../../../utils/records.js"; import { compareCodePoints } from "../../../../utils/sort.js"; const ENC = new TextEncoder(); const CHECKED = '✓'; function httpTransport({ apiKey, baseUrl, apiVersion, timeout, }) { return new HttpNotionTransport({ apiKey, ...(baseUrl !== undefined && baseUrl !== '' ? { baseUrl } : {}), ...(apiVersion !== undefined && apiVersion !== '' ? { apiVersion } : {}), ...(timeout !== undefined ? { timeout } : {}), }); } // The install's accessor, built once from its validated config. export function ntnAccessor(config) { const cfg = config; return new NotionAccessor(httpTransport(cfg), cfg); } // The install's config with this line's --notion-version applied (the // executor fills it from NOTION_API_VERSION); an empty flag keeps the pin. export function notionConfig(accessor, bag) { if (accessor.config === undefined) throw new Error('ntn: the accessor carries no account config'); const version = new FlagView(bag).asStr('notion_version'); if (version === undefined || version === '') return accessor.config; return { ...accessor.config, apiVersion: version }; } // The transport for this line; every verb comes through here so the version // override reaches all of them. export function notionTransport(accessor, bag) { const version = new FlagView(bag).asStr('notion_version'); if (version === undefined || version === '') return accessor.transport; return httpTransport(notionConfig(accessor, bag)); } function sortDeep(value) { if (Array.isArray(value)) return value.map(sortDeep); if (value === null || typeof value !== 'object') return value; const source = value; const out = {}; for (const key of Object.keys(source).sort(compareCodePoints)) out[key] = sortDeep(source[key]); return out; } // Two spaces of indent and keys in sorted order, matching the upstream // binary's serializer, so a golden recorded from the real CLI compares byte // for byte. JSON.stringify keeps insertion order, so the sort is explicit. export function prettyJson(value) { return ENC.encode(`${JSON.stringify(sortDeep(value), null, 2)}\n`); } // What `ntn api` prints: one line, no spaces, same sorted keys. export function compactJson(value) { return ENC.encode(`${JSON.stringify(sortDeep(value))}\n`); } // Quote a string the way Rust's `{:?}` renders one. `ntn` interpolates the // offending token into its parse errors with the Debug formatter, so a token // carrying a quote, a backslash or a tab comes back escaped. Non-ASCII is // left alone, which is why an accented character appears verbatim in the real // binary's message. export function rustDebug(text) { let out = '"'; for (const char of text) { const code = char.codePointAt(0) ?? 0; if (char === '"' || char === '\\') out += `\\${char}`; else if (char === '\n') out += '\\n'; else if (char === '\r') out += '\\r'; else if (char === '\t') out += '\\t'; else if (code < 0x20 || code === 0x7f) out += `\\u{${code.toString(16)}}`; else out += char; } return `${out}"`; } export function firstText(texts, what) { const head = texts[0]; if (head === undefined || head === '') throw new Error(`${what} is required`); return head; } // The upstream CLI's third content source is $EDITOR, which a virtualized CLI // has no terminal for, so the two non-interactive sources are the whole // surface here. export async function contentOrStdin(inline, stdin) { if (inline !== undefined) return inline; if (stdin === null || stdin === undefined) { throw new Error('provide Markdown with --content or on stdin'); } if (stdin instanceof Uint8Array) return new TextDecoder().decode(stdin); const chunks = []; for await (const chunk of stdin) chunks.push(chunk); const joined = concat(chunks); return new TextDecoder().decode(joined); } export function plainTextOf(fragments) { if (!Array.isArray(fragments)) return ''; let out = ''; for (const fragment of fragments) { const text = asObject(fragment).plain_text; if (typeof text === 'string') out += text; } return out; } function namesOf(value) { if (!Array.isArray(value)) return ''; return value.map((one) => stringOf(asObject(one), 'name')).join(', '); } // The formats are pinned against the real binary: a checked box is a check // mark and an unchecked one is empty, a date shows its start, a multi-select // joins with a comma and a space, and anything unset is the empty string. export function propertyCell(prop) { const record = asObject(prop); const kind = record.type; if (typeof kind !== 'string') return ''; const value = record[kind]; if (kind === 'title' || kind === 'rich_text') return plainTextOf(value); if (kind === 'checkbox') return value === true ? CHECKED : ''; if (kind === 'number') return typeof value === 'number' ? String(value) : ''; if (kind === 'select' || kind === 'status') { return value === null ? '' : stringOf(asObject(value), 'name'); } if (kind === 'multi_select' || kind === 'people') return namesOf(value); if (kind === 'date') return value === null ? '' : stringOf(asObject(value), 'start'); if (typeof value === 'string') return value; if (typeof value === 'number') return String(value); return ''; } export function parseJsonText(text, flag) { if (text === '') return {}; let parsed; try { parsed = JSON.parse(text); } catch { // One wording in both languages: the engines' own parse messages // ("Expecting value" vs "Unexpected token") can never agree. throw new Error(`${flag} must be valid JSON`); } if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) { throw new Error(`${flag} must be a JSON object`); } return parsed; } export function usageError(err) { const msg = err instanceof Error ? err.message : String(err); return [null, new IOResult({ exitCode: 2, stderr: ENC.encode(`${msg}\n`) })]; } //# sourceMappingURL=util.js.map