@airfone/connector 0.0.0-stage → 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AirFone
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,44 @@
1
- # Temporary Holding Version
1
+ # @airfone/connector
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Types and the `airfone` command line tool for building, testing, packing and publishing AirFone
4
+ connectors. Actions run locally in the same QuickJS sandbox as AirFone, so what passes here passes
5
+ in review.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install --save-dev @airfone/connector
11
+ npx airfone --help
12
+ ```
13
+
14
+ ## Commands
15
+
16
+ ```
17
+ airfone connector dev <action> run one action against the real API, in the AirFone sandbox
18
+ airfone connector test validate manifest.json and run the review checks and fixtures
19
+ airfone connector pack write dist/<id>-<version>.tar and print its sha256
20
+ airfone connector sign [FILE] print the signature for a packed bundle
21
+ airfone connector keygen create your Ed25519 signing key
22
+ airfone connector publish test, pack, sign and upload (needs AIRFONE_TOKEN)
23
+ airfone knowledge push|delete|sources
24
+ ```
25
+
26
+ Limits match AirFone: 64 MB memory, 2 s CPU and 10 s wall time per action.
27
+
28
+ ## Types
29
+
30
+ ```ts
31
+ import { defineActions } from '@airfone/connector';
32
+
33
+ export const actions = defineActions({
34
+ async lookup(ctx, input) {
35
+ return { ok: true };
36
+ },
37
+ });
38
+ ```
39
+
40
+ Full guide: https://developers.airfone.app/docs/connectors/
41
+
42
+ ## License
43
+
44
+ MIT
@@ -0,0 +1,20 @@
1
+ import { type KeyObject } from 'node:crypto';
2
+ export declare const MAX_BUNDLE_BYTES: number;
3
+ export declare const MAX_JS_BYTES: number;
4
+ export declare const sha256Hex: (b: Uint8Array) => string;
5
+ export declare const signingMessage: (id: string, version: string, sha256: string) => Buffer<ArrayBuffer>;
6
+ /** Packs files into a deterministic tar: manifest.json, index.js, then ui/ sorted. */
7
+ export declare function pack(manifestJson: Uint8Array, js: string | null, ui: Map<string, Uint8Array>): Buffer;
8
+ /** Reads a connector directory: manifest.json, index.js (if present) and every file under ui/. */
9
+ export declare function readProject(dir: string): {
10
+ manifestText: string;
11
+ js: string | null;
12
+ ui: Map<string, Uint8Array<ArrayBufferLike>>;
13
+ };
14
+ export declare function defaultKeyPath(): string;
15
+ /** Creates an Ed25519 key in PKCS#8 PEM, readable only by you. */
16
+ export declare function keygen(path: string, force?: boolean): string;
17
+ export declare function loadKey(path: string): KeyObject;
18
+ /** The raw 32-byte public key, base64. This is what you register in the console. */
19
+ export declare function publicKeyBase64(key: KeyObject): string;
20
+ export declare function signBundle(key: KeyObject, id: string, version: string, sha256: string): string;
package/dist/bundle.js ADDED
@@ -0,0 +1,101 @@
1
+ // Bundles and signatures, as bundle.rs defines them.
2
+ //
3
+ // A bundle is a tar of manifest.json, an optional index.js and optional ui/
4
+ // files, addressed by the sha256 of the tar bytes. The developer signs
5
+ // "airfone-connector-v1\n{id}\n{version}\n{sha256}" with Ed25519.
6
+ import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, sign as edSign } from 'node:crypto';
7
+ import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
8
+ import { homedir } from 'node:os';
9
+ import { dirname, join, relative, sep } from 'node:path';
10
+ export const MAX_BUNDLE_BYTES = 5 * 1024 * 1024;
11
+ export const MAX_JS_BYTES = 1024 * 1024;
12
+ export const sha256Hex = (b) => createHash('sha256').update(b).digest('hex');
13
+ export const signingMessage = (id, version, sha256) => Buffer.from(`airfone-connector-v1\n${id}\n${version}\n${sha256}`);
14
+ function octal(n, width) { return n.toString(8).padStart(width - 1, '0') + '\0'; }
15
+ /** One GNU tar header, as the tar crate's Header::new_gnu writes it. */
16
+ function header(path, size) {
17
+ const h = Buffer.alloc(512, 0);
18
+ if (Buffer.byteLength(path) > 99)
19
+ throw new Error(`path too long for the bundle: ${path}`);
20
+ h.write(path, 0, 'utf8');
21
+ h.write(octal(0o644, 8), 100, 'ascii');
22
+ h.write(octal(0, 8), 108, 'ascii');
23
+ h.write(octal(0, 8), 116, 'ascii');
24
+ h.write(octal(size, 12), 124, 'ascii');
25
+ h.write(octal(0, 12), 136, 'ascii');
26
+ h.write(' ', 148, 'ascii');
27
+ h.write('0', 156, 'ascii');
28
+ h.write('ustar ', 257, 'ascii');
29
+ h.write(' \0', 263, 'ascii');
30
+ let sum = 0;
31
+ for (const b of h)
32
+ sum += b;
33
+ h.write(sum.toString(8).padStart(6, '0') + '\0 ', 148, 'ascii');
34
+ return h;
35
+ }
36
+ /** Packs files into a deterministic tar: manifest.json, index.js, then ui/ sorted. */
37
+ export function pack(manifestJson, js, ui) {
38
+ const parts = [];
39
+ const add = (path, data) => {
40
+ parts.push(header(path, data.length), Buffer.from(data));
41
+ const pad = (512 - (data.length % 512)) % 512;
42
+ if (pad)
43
+ parts.push(Buffer.alloc(pad, 0));
44
+ };
45
+ add('manifest.json', manifestJson);
46
+ if (js !== null)
47
+ add('index.js', Buffer.from(js, 'utf8'));
48
+ for (const p of [...ui.keys()].sort((a, b) => (Buffer.from(a) < Buffer.from(b) ? -1 : a === b ? 0 : 1)))
49
+ add(p, ui.get(p));
50
+ parts.push(Buffer.alloc(1024, 0));
51
+ return Buffer.concat(parts);
52
+ }
53
+ /** Reads a connector directory: manifest.json, index.js (if present) and every file under ui/. */
54
+ export function readProject(dir) {
55
+ const manifestPath = join(dir, 'manifest.json');
56
+ if (!existsSync(manifestPath))
57
+ throw new Error(`no manifest.json in ${dir}`);
58
+ const manifestText = readFileSync(manifestPath, 'utf8');
59
+ const jsPath = join(dir, 'index.js');
60
+ const js = existsSync(jsPath) ? readFileSync(jsPath, 'utf8') : null;
61
+ const ui = new Map();
62
+ const uiDir = join(dir, 'ui');
63
+ const walk = (d) => {
64
+ for (const name of readdirSync(d)) {
65
+ const full = join(d, name);
66
+ if (statSync(full).isDirectory())
67
+ walk(full);
68
+ else
69
+ ui.set(relative(dir, full).split(sep).join('/'), readFileSync(full));
70
+ }
71
+ };
72
+ if (existsSync(uiDir))
73
+ walk(uiDir);
74
+ return { manifestText, js, ui };
75
+ }
76
+ export function defaultKeyPath() {
77
+ return process.env.AIRFONE_SIGNING_KEY ?? join(homedir(), '.airfone', 'connector-signing-key.pem');
78
+ }
79
+ /** Creates an Ed25519 key in PKCS#8 PEM, readable only by you. */
80
+ export function keygen(path, force = false) {
81
+ if (existsSync(path) && !force)
82
+ throw new Error(`${path} already exists. Pass --force to replace it.`);
83
+ const { privateKey } = generateKeyPairSync('ed25519');
84
+ mkdirSync(dirname(path), { recursive: true });
85
+ writeFileSync(path, privateKey.export({ type: 'pkcs8', format: 'pem' }), { mode: 0o600 });
86
+ chmodSync(path, 0o600);
87
+ return publicKeyBase64(privateKey);
88
+ }
89
+ export function loadKey(path) {
90
+ if (!existsSync(path))
91
+ throw new Error(`no signing key at ${path}. Run: airfone connector keygen`);
92
+ return createPrivateKey(readFileSync(path));
93
+ }
94
+ /** The raw 32-byte public key, base64. This is what you register in the console. */
95
+ export function publicKeyBase64(key) {
96
+ const der = createPublicKey(key).export({ type: 'spki', format: 'der' });
97
+ return Buffer.from(der.subarray(der.length - 32)).toString('base64');
98
+ }
99
+ export function signBundle(key, id, version, sha256) {
100
+ return edSign(null, signingMessage(id, version, sha256), key).toString('base64');
101
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { type Canonical } from './manifest.js';
3
+ export type { Canonical };
package/dist/cli.js ADDED
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env node
2
+ // airfone connector dev|test|pack|sign|keygen|publish
3
+ // airfone knowledge push|delete|sources (see ./knowledge.ts)
4
+ import { existsSync, mkdirSync, readFileSync, watch, writeFileSync } from 'node:fs';
5
+ import { basename, join, resolve } from 'node:path';
6
+ import { defaultKeyPath, keygen, loadKey, MAX_BUNDLE_BYTES, pack, publicKeyBase64, readProject, sha256Hex, signBundle } from './bundle.js';
7
+ import { LocalHost } from './host.js';
8
+ import { runHttp } from './http.js';
9
+ import { checkSchema, parseManifest, withConfigDefaults } from './manifest.js';
10
+ import { fixtures, review } from './review.js';
11
+ import { runAction, stopHttpWorker } from './runner.js';
12
+ import { knowledge } from './knowledge.js';
13
+ const HELP = `airfone connector <command> [options]
14
+
15
+ Commands
16
+ dev <action> Run one action against the real API, in the same sandbox as AirFone.
17
+ --input '{"q":"rice"}' or --input @input.json
18
+ --config FILE install settings (default .airfone/config.json)
19
+ --secrets FILE secrets by name (default .airfone/secrets.json)
20
+ --allow-local allow http:// and private addresses, for a mock server
21
+ --watch run again when files change
22
+ test Validate manifest.json and run AirFone's review checks and every fixture
23
+ (manifest fixtures and tests/*.json). No network, as in review.
24
+ pack Write dist/<id>-<version>.tar and print its sha256.
25
+ sign [FILE] Print the signature for a packed bundle, for uploading in the console.
26
+ keygen Create your Ed25519 signing key and print the public key to register.
27
+ publish test, pack, sign and upload. Needs AIRFONE_TOKEN (an access token for your
28
+ AirFone account) and AIRFONE_API (default https://app.airfone.app).
29
+
30
+ Options for every command
31
+ --dir DIR the connector folder (default: current folder)
32
+ --key FILE signing key (default ~/.airfone/connector-signing-key.pem or AIRFONE_SIGNING_KEY)
33
+
34
+ Limits match AirFone: 64 MB memory, 2 s CPU and 10 s wall time per action.`;
35
+ function parseArgs(argv) {
36
+ const pos = [];
37
+ const flags = {};
38
+ for (let i = 0; i < argv.length; i++) {
39
+ const a = argv[i];
40
+ if (a.startsWith('--')) {
41
+ const [k, v] = a.slice(2).split('=', 2);
42
+ if (v !== undefined)
43
+ flags[k] = v;
44
+ else if (argv[i + 1] !== undefined && !argv[i + 1].startsWith('--') && !['watch', 'allow-local', 'force', 'help'].includes(k))
45
+ flags[k] = argv[++i];
46
+ else
47
+ flags[k] = true;
48
+ }
49
+ else
50
+ pos.push(a);
51
+ }
52
+ return { pos, flags };
53
+ }
54
+ const out = (s) => process.stdout.write(s + '\n');
55
+ const err = (s) => process.stderr.write(s + '\n');
56
+ function readJson(path, fallback) {
57
+ if (!existsSync(path))
58
+ return fallback;
59
+ try {
60
+ return JSON.parse(readFileSync(path, 'utf8'));
61
+ }
62
+ catch (e) {
63
+ throw new Error(`${path}: ${e.message}`);
64
+ }
65
+ }
66
+ function load(dir) {
67
+ const p = readProject(dir);
68
+ const m = parseManifest(p.manifestText);
69
+ if (m.actions.some((a) => a.kind === 'js') && p.js === null)
70
+ throw new Error('the manifest has JS actions but there is no index.js');
71
+ return { ...p, m };
72
+ }
73
+ function printFindings(findings) {
74
+ if (findings.length === 0) {
75
+ out('All checks passed.');
76
+ return;
77
+ }
78
+ for (const f of findings)
79
+ out(`${f.level === 'error' ? 'Failed ' : 'Warning'} ${f.message}`);
80
+ }
81
+ async function dev(dir, pos, flags) {
82
+ const once = async () => {
83
+ const { m, js } = load(dir);
84
+ const name = pos[0];
85
+ if (!name) {
86
+ out('Actions:');
87
+ for (const a of m.actions)
88
+ out(` ${a.name} (${a.kind}, ${a.risk}) ${a.description}`);
89
+ return true;
90
+ }
91
+ const action = m.actions.find((a) => a.name === name);
92
+ if (!action)
93
+ throw new Error(`no action named ${name}. Run without an action to list them.`);
94
+ const raw = typeof flags.input === 'string' ? flags.input : '{}';
95
+ const input = raw.startsWith('@') ? JSON.parse(readFileSync(resolve(dir, raw.slice(1)), 'utf8')) : JSON.parse(raw);
96
+ const config = withConfigDefaults(m.config, readJson(resolve(dir, typeof flags.config === 'string' ? flags.config : '.airfone/config.json'), {}));
97
+ const secrets = readJson(resolve(dir, typeof flags.secrets === 'string' ? flags.secrets : '.airfone/secrets.json'), {});
98
+ checkSchema('config', m.config, config);
99
+ checkSchema('input', action.input, input);
100
+ const egress = { allowLocal: flags['allow-local'] === true };
101
+ const started = Date.now();
102
+ let result;
103
+ if (action.kind === 'http')
104
+ result = await runHttp(m, action, input, config, secrets, egress);
105
+ else
106
+ result = await runAction(js, action.js ?? action.name, input, config, new LocalHost(m, config, secrets, join(dir, '.airfone', 'store.json'), egress, (l) => err(` ${l}`)));
107
+ out(JSON.stringify(result, null, 2));
108
+ try {
109
+ checkSchema('output', action.output, result);
110
+ }
111
+ catch (e) {
112
+ err(`Warning ${e.message}`);
113
+ }
114
+ err(`done in ${Date.now() - started} ms`);
115
+ return true;
116
+ };
117
+ const guarded = async () => { try {
118
+ await once();
119
+ return true;
120
+ }
121
+ catch (e) {
122
+ err(`Error ${e.message}`);
123
+ return false;
124
+ } };
125
+ const ok = await guarded();
126
+ if (flags.watch !== true) {
127
+ stopHttpWorker();
128
+ return ok;
129
+ }
130
+ err('Watching for changes. Press Ctrl+C to stop.');
131
+ let timer;
132
+ watch(dir, { recursive: true }, (_e, file) => {
133
+ if (!file || String(file).startsWith('.airfone') || String(file).startsWith('dist') || String(file).includes('node_modules'))
134
+ return;
135
+ clearTimeout(timer);
136
+ timer = setTimeout(() => { err(`\n${file} changed`); void guarded(); }, 150);
137
+ });
138
+ return new Promise(() => { });
139
+ }
140
+ async function test(dir) {
141
+ const { m, js } = load(dir);
142
+ const list = fixtures(m, dir);
143
+ out(`${m.id}@${m.version}: ${m.actions.length} action(s), ${list.length} fixture(s)`);
144
+ const findings = await review(m, js, list);
145
+ printFindings(findings);
146
+ return !findings.some((f) => f.level === 'error');
147
+ }
148
+ function packTo(dir, flags) {
149
+ const { m, js, ui, manifestText } = load(dir);
150
+ const tar = pack(Buffer.from(manifestText), js, ui);
151
+ if (tar.length > MAX_BUNDLE_BYTES)
152
+ throw new Error(`the bundle is ${tar.length} bytes; the limit is ${MAX_BUNDLE_BYTES}`);
153
+ const outDir = resolve(dir, typeof flags.out === 'string' ? flags.out : 'dist');
154
+ mkdirSync(outDir, { recursive: true });
155
+ const file = join(outDir, `${m.id}-${m.version}.tar`);
156
+ writeFileSync(file, tar);
157
+ return { m, tar, file, sha256: sha256Hex(tar) };
158
+ }
159
+ async function publish(dir, flags) {
160
+ const token = process.env.AIRFONE_TOKEN;
161
+ if (!token)
162
+ throw new Error('set AIRFONE_TOKEN to an access token for your AirFone account');
163
+ const api = (process.env.AIRFONE_API ?? 'https://app.airfone.app').replace(/\/$/, '');
164
+ if (!(await test(dir)))
165
+ throw new Error('fix the failed checks first; AirFone would reject this version');
166
+ const { m, tar, file, sha256 } = packTo(dir, flags);
167
+ const key = loadKey(typeof flags.key === 'string' ? flags.key : defaultKeyPath());
168
+ const signature = signBundle(key, m.id, m.version, sha256);
169
+ out(`Uploading ${basename(file)} (${tar.length} bytes, sha256 ${sha256})`);
170
+ const form = new FormData();
171
+ form.append('bundle', new Blob([tar], { type: 'application/x-tar' }), basename(file));
172
+ form.append('signature', signature);
173
+ const r = await fetch(`${api}/api/developer/connectors/${encodeURIComponent(m.id)}/versions`, {
174
+ method: 'POST', headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }, body: form,
175
+ });
176
+ const text = await r.text();
177
+ let body = null;
178
+ try {
179
+ body = JSON.parse(text);
180
+ }
181
+ catch { /* not JSON */ }
182
+ if (!r.ok)
183
+ throw new Error(`upload failed (${r.status}): ${body?.error?.message ?? body?.error ?? body?.message ?? text.slice(0, 300)}`);
184
+ out(`Version ${body?.version ?? m.version}: ${body?.state ?? 'submitted'}`);
185
+ if (Array.isArray(body?.permission_diff) && body.permission_diff.length)
186
+ out(`New permissions: ${body.permission_diff.join(', ')}`);
187
+ if (Array.isArray(body?.findings))
188
+ printFindings(body.findings);
189
+ return body?.state !== 'rejected';
190
+ }
191
+ async function main() {
192
+ let argv = process.argv.slice(2);
193
+ if (argv[0] === 'knowledge')
194
+ return knowledge(parseArgs(argv.slice(1)).pos, out, err);
195
+ if (argv[0] === 'connector')
196
+ argv = argv.slice(1);
197
+ const { pos, flags } = parseArgs(argv);
198
+ const cmd = pos.shift();
199
+ const dir = resolve(typeof flags.dir === 'string' ? flags.dir : '.');
200
+ switch (cmd) {
201
+ case 'dev': return dev(dir, pos, flags);
202
+ case 'test': return test(dir);
203
+ case 'pack': {
204
+ const { file, sha256, tar, m } = packTo(dir, flags);
205
+ out(`${file}\n${m.id}@${m.version} ${tar.length} bytes sha256 ${sha256}`);
206
+ return true;
207
+ }
208
+ case 'sign': {
209
+ const target = pos[0] ? resolve(pos[0]) : packTo(dir, flags).file;
210
+ const tar = readFileSync(target);
211
+ const sha = sha256Hex(tar);
212
+ const { m } = load(dir);
213
+ const key = loadKey(typeof flags.key === 'string' ? flags.key : defaultKeyPath());
214
+ err(`${basename(target)} ${m.id}@${m.version} sha256 ${sha}`);
215
+ out(signBundle(key, m.id, m.version, sha));
216
+ return true;
217
+ }
218
+ case 'keygen': {
219
+ const path = typeof flags.key === 'string' ? flags.key : defaultKeyPath();
220
+ if (flags.show === true || (existsSync(path) && flags.force !== true)) {
221
+ out(publicKeyBase64(loadKey(path)));
222
+ err(`Existing key at ${path}. Pass --force to replace it.`);
223
+ return true;
224
+ }
225
+ const pub = keygen(path, flags.force === true);
226
+ err(`Saved your signing key to ${path}. Keep it private and backed up.`);
227
+ err('Register this public key in the console under Connectors, Signing keys:');
228
+ out(pub);
229
+ return true;
230
+ }
231
+ case 'publish': return publish(dir, flags);
232
+ case undefined:
233
+ case 'help':
234
+ out(HELP);
235
+ return true;
236
+ default:
237
+ err(`Unknown command ${cmd}.\n`);
238
+ out(HELP);
239
+ return false;
240
+ }
241
+ }
242
+ main().then((ok) => { stopHttpWorker(); process.exitCode = ok ? 0 : 1; }, (e) => { stopHttpWorker(); err(`Error ${e.message}`); process.exitCode = 1; });
package/dist/host.d.ts ADDED
@@ -0,0 +1,81 @@
1
+ import { type Canonical } from './manifest.js';
2
+ import type { EgressOptions } from './net.js';
3
+ import { type HostBridge } from './runner.js';
4
+ export interface HostEvent {
5
+ op: string;
6
+ detail: string;
7
+ }
8
+ declare abstract class BaseHost implements HostBridge {
9
+ protected m: Canonical;
10
+ protected config: unknown;
11
+ protected granted: string[];
12
+ readonly events: HostEvent[];
13
+ constructor(m: Canonical, config: unknown, granted: string[]);
14
+ protected abstract storeGet(key: string): unknown;
15
+ protected abstract storeSet(key: string, value: unknown): void;
16
+ protected abstract storeDelete(key: string): void;
17
+ protected abstract fetch(args: any, deadline: number): unknown;
18
+ protected abstract upsert(records: unknown[]): unknown;
19
+ /** ctx.knowledge.*: printed locally, accepted in review. */
20
+ protected abstract knowledge(kind: 'upsert' | 'delete', items: unknown[]): unknown;
21
+ protected abstract log(level: string, message: string): void;
22
+ /** The install secret `name`, or undefined. */
23
+ protected abstract secret(name: string): string | undefined;
24
+ call(op: string, args: any, deadline: number): unknown;
25
+ }
26
+ /** crypto.rs fill_secrets: each `{{secrets.x}}` becomes that declared secret. */
27
+ export declare function fillSecrets(text: string, secret: (name: string) => string | undefined): string;
28
+ /** crypto.rs host_op: `{{secrets.x}}` in the message is filled in, keys never leave the host. */
29
+ export declare function cryptoOp(op: string, args: any, secret: (name: string) => string | undefined): string;
30
+ export declare class LocalHost extends BaseHost {
31
+ private secrets;
32
+ private storePath;
33
+ private egress;
34
+ private out;
35
+ private store;
36
+ constructor(m: Canonical, config: unknown, secrets: Record<string, string>, storePath: string, egress: EgressOptions, out: (line: string) => void);
37
+ private save;
38
+ protected storeGet(key: string): unknown;
39
+ protected storeSet(key: string, value: unknown): void;
40
+ protected storeDelete(key: string): void;
41
+ protected fetch(args: any, deadline: number): {
42
+ status: number;
43
+ headers: Record<string, string>;
44
+ body: string;
45
+ };
46
+ protected upsert(records: unknown[]): {
47
+ local: boolean;
48
+ count: number;
49
+ };
50
+ protected knowledge(kind: 'upsert' | 'delete', items: unknown[]): {
51
+ local: boolean;
52
+ deleted: boolean;
53
+ results?: undefined;
54
+ } | {
55
+ local: boolean;
56
+ results: {
57
+ result: string;
58
+ }[];
59
+ deleted?: undefined;
60
+ };
61
+ protected secret(name: string): string | undefined;
62
+ protected log(level: string, message: string): void;
63
+ }
64
+ export declare class ReviewHost extends BaseHost {
65
+ private store;
66
+ constructor(m: Canonical, config: unknown);
67
+ protected storeGet(key: string): unknown;
68
+ protected storeSet(key: string, value: unknown): void;
69
+ protected storeDelete(key: string): void;
70
+ protected fetch(): never;
71
+ protected upsert(): {
72
+ review: boolean;
73
+ };
74
+ protected knowledge(): {
75
+ review: boolean;
76
+ };
77
+ protected log(): void;
78
+ protected secret(): string;
79
+ call(op: string, args: any, deadline: number): unknown;
80
+ }
81
+ export {};