@tealbrick/kit 0.3.0-rc.7 → 0.3.0-rc.9
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/BOOTSTRAP.md +9 -10
- package/BUZZ.md +135 -0
- package/NATIVE.md +3 -3
- package/README.md +133 -7
- package/RUNTIME.md +1 -1
- package/assets/buzz/buzz-backend-provider.py +99 -0
- package/assets/buzz/buzz-plist.py +22 -0
- package/assets/buzz/buzz-run.sh +39 -0
- package/assets/buzz/tb-provider-apply.py +97 -0
- package/dist/buzz.d.ts +63 -0
- package/dist/buzz.js +262 -0
- package/dist/cli.js +8 -3
- package/dist/index.d.ts +14 -14
- package/dist/native-acp.d.ts +40 -0
- package/dist/native-acp.js +221 -0
- package/dist/native-app-capabilities.d.ts +4 -0
- package/dist/native-app-capabilities.js +2 -1
- package/dist/native-buzz.d.ts +128 -0
- package/dist/native-buzz.js +510 -0
- package/dist/native-claude.d.ts +34 -1
- package/dist/native-claude.js +218 -23
- package/dist/native-serve-config.d.ts +65 -0
- package/dist/native-serve-config.js +70 -6
- package/dist/native-serve.d.ts +3 -0
- package/dist/native-serve.js +7 -2
- package/dist/native-standing-grants.d.ts +105 -0
- package/dist/native-standing-grants.js +226 -0
- package/dist/native.d.ts +1 -1
- package/dist/native.js +14 -4
- package/dist/onboarding.d.ts +3 -0
- package/dist/onboarding.js +8 -1
- package/dist/runtime-onboarding.js +2 -2
- package/package.json +9 -7
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# tealbrick-buzz-host: installed by `tealbrick buzz host setup`; rewritten on every setup run.
|
|
3
|
+
"""Host side of the Teal Brick Buzz Desktop provider. Reads ONE JSON request on stdin (it carries the agent
|
|
4
|
+
key; never argv/env), materialises ~/agents/buzz/<label>/, (re)loads its launchd unit and reports health
|
|
5
|
+
as one JSON line. Idempotent: a redeploy rewrites config and restarts the unit. Host paths come from
|
|
6
|
+
~/agents/buzz/host.json (written by `tealbrick buzz host setup`)."""
|
|
7
|
+
import json, os, re, subprocess, sys, time
|
|
8
|
+
H = os.path.expanduser('~'); BUZZ = os.path.join(H, 'agents', 'buzz')
|
|
9
|
+
HEX = re.compile(r'^[0-9a-f]{64}$')
|
|
10
|
+
LAUNCHCTL = '/bin/launchctl'
|
|
11
|
+
def out(o, code=0): print(json.dumps(o)); sys.exit(code)
|
|
12
|
+
def redact(text): return re.sub(r'nsec1[0-9a-z]+|[0-9a-fA-F]{64}', '<redacted>', text)
|
|
13
|
+
def fail(msg): out({'ok': False, 'error': redact(msg)}, 1)
|
|
14
|
+
def write(path, text, mode=0o600):
|
|
15
|
+
tmp = path + '.tmp'; fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode)
|
|
16
|
+
with os.fdopen(fd, 'w') as f: f.write(text)
|
|
17
|
+
os.chmod(tmp, mode); os.replace(tmp, path)
|
|
18
|
+
SAFE = re.compile(r'^[A-Za-z0-9,._:-]*$')
|
|
19
|
+
ALLOWED_POLICY = {'BUZZ_ACP_SESSION_POLICY', 'BUZZ_ACP_IDLE_TIMEOUT', 'BUZZ_ACP_MAX_TURN_DURATION',
|
|
20
|
+
'BUZZ_ACP_TURN_TIMEOUT', 'BUZZ_ACP_HEARTBEAT_INTERVAL', 'BUZZ_ACP_DEDUP',
|
|
21
|
+
'BUZZ_ACP_MULTIPLE_EVENT_HANDLING', 'BUZZ_ACP_SUBSCRIBE', 'BUZZ_ACP_NO_MENTION_FILTER',
|
|
22
|
+
'BUZZ_ACP_CHANNELS', 'BUZZ_ACP_KINDS', 'BUZZ_ACP_RELAY_OBSERVER', 'BUZZ_ACP_LAZY_POOL', 'BUZZ_ACP_AGENTS'}
|
|
23
|
+
def host_config():
|
|
24
|
+
try:
|
|
25
|
+
with open(os.path.join(BUZZ, 'host.json')) as f: cfg = json.load(f)
|
|
26
|
+
except FileNotFoundError: fail('host is not set up: run `tealbrick buzz host setup` on the host')
|
|
27
|
+
if not isinstance(cfg, dict) or not isinstance(cfg.get('ownersDir'), str) or not os.path.isabs(cfg['ownersDir']):
|
|
28
|
+
fail('host.json has no absolute ownersDir: re-run `tealbrick buzz host setup`')
|
|
29
|
+
return cfg
|
|
30
|
+
def main():
|
|
31
|
+
req = json.loads(sys.stdin.read())
|
|
32
|
+
if not isinstance(req, dict): fail('bad request')
|
|
33
|
+
label, owner, relay = str(req.get('label') or ''), str(req.get('owner') or ''), str(req.get('relay_url') or '')
|
|
34
|
+
if not re.fullmatch(r'[a-z0-9][a-z0-9-]{2,62}', label): fail('bad label')
|
|
35
|
+
if not re.fullmatch(r'[a-z0-9-]{2,32}', owner) or not label.startswith(owner + '-'): fail('bad owner')
|
|
36
|
+
owners_dir = host_config()['ownersDir']
|
|
37
|
+
if not os.path.isfile(os.path.join(owners_dir, owner, '.tealbrick', 'native-serve.json')):
|
|
38
|
+
fail(f'unknown or unenrolled kit owner {owner!r} on this host')
|
|
39
|
+
if not re.fullmatch(r'wss?://[A-Za-z0-9.-]+(:\d+)?/?', relay): fail('bad relay_url')
|
|
40
|
+
owner_pub = str(req.get('owner_pubkey') or ''); agent_pub = str(req.get('agent_pubkey') or '')
|
|
41
|
+
if not (HEX.match(owner_pub) and HEX.match(agent_pub)): fail('bad pubkeys')
|
|
42
|
+
try: tag = json.loads(req.get('auth_tag') or '')
|
|
43
|
+
except (TypeError, ValueError): tag = None
|
|
44
|
+
if not (isinstance(tag, list) and len(tag) == 4 and tag[0] == 'auth' and tag[1] == owner_pub and all(isinstance(x, str) for x in tag)):
|
|
45
|
+
fail('bad auth_tag')
|
|
46
|
+
desktop_rt = req.get('respond_to') or 'owner-only'
|
|
47
|
+
if desktop_rt not in ('owner-only', 'allowlist', 'anyone', 'nobody'): fail('bad respond_to')
|
|
48
|
+
# Fleet default (~/agents/buzz/fleet-defaults.json, e.g. {"respond_to": "anyone"}) replaces Desktop's
|
|
49
|
+
# *default* owner-only on a membership-only relay; a stricter explicit choice (nobody/allowlist) wins.
|
|
50
|
+
try:
|
|
51
|
+
with open(os.path.join(BUZZ, 'fleet-defaults.json')) as f: fleet = json.load(f)
|
|
52
|
+
except FileNotFoundError: fleet = {}
|
|
53
|
+
if not isinstance(fleet, dict): fleet = {}
|
|
54
|
+
rt = fleet.get('respond_to') if desktop_rt == 'owner-only' and fleet.get('respond_to') in ('anyone', 'owner-only') else desktop_rt
|
|
55
|
+
req['respond_to_effective'] = rt; req['respond_to_desktop'] = desktop_rt
|
|
56
|
+
allow = [p for p in (req.get('respond_to_allowlist') or []) if isinstance(p, str) and HEX.match(p)]
|
|
57
|
+
sk = str(req.get('private_key') or '')
|
|
58
|
+
if not (HEX.match(sk) or re.fullmatch(r'nsec1[0-9a-z]{58}', sk)): fail('bad private key')
|
|
59
|
+
rules = req.get('rules_toml')
|
|
60
|
+
if not isinstance(rules, str) or len(rules) > 4096: fail('bad rules_toml')
|
|
61
|
+
D = os.path.join(BUZZ, label); os.makedirs(os.path.join(D, 'logs'), mode=0o700, exist_ok=True); os.chmod(D, 0o700)
|
|
62
|
+
write(f'{D}/nostr.key', sk + '\n'); write(f'{D}/nostr.pub', agent_pub + '\n', 0o644)
|
|
63
|
+
write(f'{D}/auth-tag.json', json.dumps(tag)); write(f'{D}/owner', owner + '\n'); write(f'{D}/relay-url', relay + '\n')
|
|
64
|
+
env = {'BUZZ_ACP_SUBSCRIBE': 'config', 'BUZZ_ACP_RESPOND_TO': rt}
|
|
65
|
+
if rt == 'allowlist': env['BUZZ_ACP_RESPOND_TO_ALLOWLIST'] = ','.join(allow)
|
|
66
|
+
policy = req.get('policy_env') or {}
|
|
67
|
+
for k, v in (policy.items() if isinstance(policy, dict) else []):
|
|
68
|
+
if k in ALLOWED_POLICY and SAFE.match(str(v)) and k != 'BUZZ_ACP_SUBSCRIBE': env[k] = str(v)
|
|
69
|
+
if 'BUZZ_ACP_AGENTS' in env: # Desktop parallelism, capped: each slot is one kit native acp process
|
|
70
|
+
env['BUZZ_ACP_AGENTS'] = str(min(4, max(1, int(env['BUZZ_ACP_AGENTS']) if env['BUZZ_ACP_AGENTS'].isdigit() else 1)))
|
|
71
|
+
write(f'{D}/acp.env', ''.join(f'{k}={v}\n' for k, v in sorted(env.items())))
|
|
72
|
+
write(f'{D}/buzz-acp.toml', rules)
|
|
73
|
+
write(f'{D}/deploy.json', json.dumps({k: req.get(k) for k in ('label', 'owner', 'relay_url', 'agent_pubkey', 'owner_pubkey', 'respond_to_desktop', 'respond_to_effective', 'name')}, indent=1))
|
|
74
|
+
subprocess.run([sys.executable, os.path.join(BUZZ, 'bin', 'buzz-plist.py'), label], check=True, capture_output=True)
|
|
75
|
+
uid = os.getuid(); svc = f'gui/{uid}/com.tealbrick.buzz-acp.{label}'
|
|
76
|
+
plist = os.path.join(H, 'Library', 'LaunchAgents', f'com.tealbrick.buzz-acp.{label}.plist')
|
|
77
|
+
log = f'{D}/logs/buzz-acp.err.log'; start = os.path.getsize(log) if os.path.exists(log) else 0
|
|
78
|
+
loaded = subprocess.run([LAUNCHCTL, 'print', svc], capture_output=True).returncode == 0
|
|
79
|
+
if loaded: subprocess.run([LAUNCHCTL, 'kickstart', '-k', svc], check=True, capture_output=True)
|
|
80
|
+
else: subprocess.run([LAUNCHCTL, 'bootstrap', f'gui/{uid}', plist], check=True, capture_output=True)
|
|
81
|
+
ansi = re.compile(r'\x1b\[[0-9;]*m'); tail = ''
|
|
82
|
+
for _ in range(40):
|
|
83
|
+
time.sleep(1)
|
|
84
|
+
if not os.path.exists(log): continue
|
|
85
|
+
with open(log, errors='replace') as f: f.seek(start); tail = ansi.sub('', f.read())
|
|
86
|
+
if 'subscribed to membership notifications' in tail or 'subscribed to channel' in tail:
|
|
87
|
+
out({'ok': True, 'label': label, 'service': svc})
|
|
88
|
+
bad = re.search(r'(?i)(relay_membership_required|auth[^\n]*(fail|denied|invalid)|ERROR[^\n]*)', tail)
|
|
89
|
+
if bad:
|
|
90
|
+
subprocess.run([LAUNCHCTL, 'bootout', svc], capture_output=True) # no crash-looping unit on a failed deploy
|
|
91
|
+
fail('harness failed: ' + bad.group(0)[:300])
|
|
92
|
+
subprocess.run([LAUNCHCTL, 'bootout', svc], capture_output=True)
|
|
93
|
+
fail('harness did not subscribe within 40s')
|
|
94
|
+
if __name__ == '__main__':
|
|
95
|
+
try: main()
|
|
96
|
+
except SystemExit: raise
|
|
97
|
+
except Exception as e: fail(f'{type(e).__name__}: {str(e)[:200]}')
|
package/dist/buzz.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export declare const PROVIDER_MARKER = "tealbrick-buzz-provider";
|
|
2
|
+
export declare const buzzUsage = "Usage: tealbrick buzz provider install [--name NAME] [--bin-dir DIRECTORY] [--force]\n tealbrick buzz host setup [--owners-dir DIRECTORY] [--buzz-app PATH] [--node PATH] [--kit-cli PATH] [--respond-to-default owner-only|anyone]\n tealbrick buzz host remove <label>";
|
|
3
|
+
export declare function installProvider(options?: {
|
|
4
|
+
name?: string;
|
|
5
|
+
binDir?: string;
|
|
6
|
+
force?: boolean;
|
|
7
|
+
home?: string;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
path: string;
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
updated: boolean;
|
|
13
|
+
discoverable: boolean;
|
|
14
|
+
python3: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
export type HostSetupOptions = {
|
|
17
|
+
home?: string;
|
|
18
|
+
ownersDir?: string;
|
|
19
|
+
buzzApp?: string;
|
|
20
|
+
node?: string;
|
|
21
|
+
kitCli?: string;
|
|
22
|
+
respondToDefault?: 'owner-only' | 'anyone';
|
|
23
|
+
/** Installs @tealbrick/kit@version under prefix and returns the installed dist/cli.js (tests inject this). */
|
|
24
|
+
installKit?: (prefix: string, version: string) => Promise<string>;
|
|
25
|
+
};
|
|
26
|
+
export declare function setupBuzzHost(options?: HostSetupOptions): Promise<{
|
|
27
|
+
scripts: string[];
|
|
28
|
+
fleetDefaults: "owner-only" | "anyone" | null;
|
|
29
|
+
owners: string[];
|
|
30
|
+
warnings: string[];
|
|
31
|
+
version: number;
|
|
32
|
+
kitVersion: string;
|
|
33
|
+
node: string;
|
|
34
|
+
kitCli: string;
|
|
35
|
+
ownersDir: string;
|
|
36
|
+
buzzBin: string;
|
|
37
|
+
buzzAcpBin: string;
|
|
38
|
+
buzzAppVersion: string;
|
|
39
|
+
buzzSha256: {
|
|
40
|
+
buzz: string;
|
|
41
|
+
'buzz-acp': string;
|
|
42
|
+
};
|
|
43
|
+
hostJson: string;
|
|
44
|
+
}>;
|
|
45
|
+
export type LaunchctlRun = (args: string[]) => Promise<number>;
|
|
46
|
+
/**
|
|
47
|
+
* Remove one provider-managed agent from this host (Buzz Desktop has no undeploy operation): boot out its
|
|
48
|
+
* launchd unit, delete the plist and move ~/agents/buzz/<label> to ~/agents/buzz/.removed/<label>-<ts>
|
|
49
|
+
* (0700; it still holds the agent key until you delete it).
|
|
50
|
+
*/
|
|
51
|
+
export declare function removeBuzzAgent(label: string, options?: {
|
|
52
|
+
home?: string;
|
|
53
|
+
uid?: number;
|
|
54
|
+
launchctl?: LaunchctlRun;
|
|
55
|
+
now?: () => Date;
|
|
56
|
+
}): Promise<{
|
|
57
|
+
label: string;
|
|
58
|
+
service: string;
|
|
59
|
+
bootedOut: boolean;
|
|
60
|
+
plistRemoved: boolean;
|
|
61
|
+
movedTo: string | undefined;
|
|
62
|
+
}>;
|
|
63
|
+
export declare function buzzCommand(args: string[]): Promise<void>;
|
package/dist/buzz.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { chmod, copyFile, lstat, mkdir, readFile, readdir, rename, stat, unlink, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { execFile } from 'node:child_process';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { delimiter, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
/** Buzz Desktop managed-agent provider (client Mac) and its agent host (Mac running buzz-acp + kit native acp). */
|
|
9
|
+
const run = promisify(execFile);
|
|
10
|
+
const assets = fileURLToPath(new URL('../assets/buzz/', import.meta.url));
|
|
11
|
+
export const PROVIDER_MARKER = 'tealbrick-buzz-provider';
|
|
12
|
+
export const buzzUsage = `Usage: tealbrick buzz provider install [--name NAME] [--bin-dir DIRECTORY] [--force]
|
|
13
|
+
tealbrick buzz host setup [--owners-dir DIRECTORY] [--buzz-app PATH] [--node PATH] [--kit-cli PATH] [--respond-to-default owner-only|anyone]
|
|
14
|
+
tealbrick buzz host remove <label>`;
|
|
15
|
+
async function kitVersion() { return JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')).version; }
|
|
16
|
+
function flagsOf(args, values, switches) {
|
|
17
|
+
const flags = {};
|
|
18
|
+
for (let i = 0; i < args.length; i++) {
|
|
19
|
+
const name = /^--([a-z-]+)$/.exec(args[i])?.[1];
|
|
20
|
+
if (!name || flags[name] !== undefined)
|
|
21
|
+
throw Error('kit_buzz_invalid_arguments');
|
|
22
|
+
if (switches.includes(name)) {
|
|
23
|
+
flags[name] = true;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (!values.includes(name) || !args[i + 1] || args[i + 1].startsWith('--'))
|
|
27
|
+
throw Error('kit_buzz_invalid_arguments');
|
|
28
|
+
flags[name] = args[++i];
|
|
29
|
+
}
|
|
30
|
+
return flags;
|
|
31
|
+
}
|
|
32
|
+
function expand(path, home) { return resolve(path === '~' ? home : path.startsWith('~/') ? join(home, path.slice(2)) : path); }
|
|
33
|
+
async function exists(path) { try {
|
|
34
|
+
await stat(path);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
} }
|
|
40
|
+
async function writeAtomic(path, body, mode) {
|
|
41
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
42
|
+
await writeFile(tmp, body, { mode });
|
|
43
|
+
await chmod(tmp, mode);
|
|
44
|
+
await rename(tmp, path);
|
|
45
|
+
}
|
|
46
|
+
/** Paths end up in a shell script and in buzz-acp's comma-separated BUZZ_ACP_AGENT_ARGS. */
|
|
47
|
+
function hostPath(name, path) {
|
|
48
|
+
if (!isAbsolute(path) || /[,\x00-\x1f\x7f]/.test(path))
|
|
49
|
+
throw Error(`kit_buzz_${name}_path_invalid`);
|
|
50
|
+
return path;
|
|
51
|
+
}
|
|
52
|
+
function shellQuote(value) { return `'${value.replace(/'/g, `'\\''`)}'`; }
|
|
53
|
+
async function python3Ok() { try {
|
|
54
|
+
await run('/usr/bin/python3', ['-c', 'import json, plistlib']);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
} }
|
|
60
|
+
export async function installProvider(options = {}) {
|
|
61
|
+
const home = options.home ?? homedir(), name = options.name ?? 'tealbrick-mac';
|
|
62
|
+
if (!/^[a-z0-9-]{3,40}$/.test(name))
|
|
63
|
+
throw Error('kit_buzz_provider_name_invalid');
|
|
64
|
+
const binDir = expand(options.binDir ?? '~/.local/bin', home), path = join(binDir, `buzz-backend-${name}`);
|
|
65
|
+
let existing;
|
|
66
|
+
try {
|
|
67
|
+
existing = await readFile(path, 'utf8');
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error?.code !== 'ENOENT')
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
if (existing !== undefined && !existing.split('\n', 3).some(line => line.includes(PROVIDER_MARKER)) && !options.force)
|
|
74
|
+
throw Error('kit_buzz_provider_foreign_file');
|
|
75
|
+
const version = await kitVersion();
|
|
76
|
+
const body = (await readFile(join(assets, 'buzz-backend-provider.py'), 'utf8')).replaceAll('__TB_PROVIDER_ID__', name).replaceAll('__TB_KIT_VERSION__', version);
|
|
77
|
+
await mkdir(binDir, { recursive: true });
|
|
78
|
+
await writeAtomic(path, body, 0o755);
|
|
79
|
+
const onPath = (process.env.PATH ?? '').split(delimiter).some(entry => entry && resolve(entry) === binDir);
|
|
80
|
+
return { path, name, version, updated: existing !== undefined, discoverable: onPath || binDir === join(home, '.local', 'bin'), python3: await python3Ok() };
|
|
81
|
+
}
|
|
82
|
+
async function npmInstallKit(prefix, version) {
|
|
83
|
+
const local = join(dirname(process.execPath), 'npm');
|
|
84
|
+
const npm = await exists(local) ? local : 'npm';
|
|
85
|
+
try {
|
|
86
|
+
await run(npm, ['install', '--prefix', prefix, '--no-audit', '--no-fund', '--save-exact', `@tealbrick/kit@${version}`], { maxBuffer: 16 * 1024 * 1024 });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
throw Error('kit_buzz_kit_install_failed');
|
|
90
|
+
}
|
|
91
|
+
return join(prefix, 'node_modules', '@tealbrick', 'kit', 'dist', 'cli.js');
|
|
92
|
+
}
|
|
93
|
+
async function bundleVersion(app) {
|
|
94
|
+
const plist = join(app, 'Contents', 'Info.plist');
|
|
95
|
+
try {
|
|
96
|
+
const { stdout } = await run('/usr/bin/plutil', ['-extract', 'CFBundleShortVersionString', 'raw', '-o', '-', plist]);
|
|
97
|
+
if (stdout.trim())
|
|
98
|
+
return stdout.trim();
|
|
99
|
+
}
|
|
100
|
+
catch { }
|
|
101
|
+
try {
|
|
102
|
+
const text = await readFile(plist, 'utf8');
|
|
103
|
+
return /<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/.exec(text)?.[1] ?? 'unknown';
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return 'unknown';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function sha256(path) { return createHash('sha256').update(await readFile(path)).digest('hex'); }
|
|
110
|
+
export async function setupBuzzHost(options = {}) {
|
|
111
|
+
const home = options.home ?? homedir(), version = await kitVersion();
|
|
112
|
+
if (options.respondToDefault !== undefined && !['owner-only', 'anyone'].includes(options.respondToDefault))
|
|
113
|
+
throw Error('kit_buzz_respond_to_default_invalid');
|
|
114
|
+
const ownersDir = hostPath('owners_dir', expand(options.ownersDir ?? '~/agents/tealbrick-connections', home));
|
|
115
|
+
const app = expand(options.buzzApp ?? '/Applications/Buzz.app', home);
|
|
116
|
+
const node = hostPath('node', expand(options.node ?? process.execPath, home));
|
|
117
|
+
if (!(await stat(node).catch(() => undefined))?.isFile())
|
|
118
|
+
throw Error('kit_buzz_node_missing');
|
|
119
|
+
const sources = { buzz: join(app, 'Contents', 'MacOS', 'buzz'), 'buzz-acp': join(app, 'Contents', 'MacOS', 'buzz-acp') };
|
|
120
|
+
for (const source of Object.values(sources))
|
|
121
|
+
if (!(await stat(source).catch(() => undefined))?.isFile())
|
|
122
|
+
throw Error('kit_buzz_app_binaries_missing');
|
|
123
|
+
const root = join(home, 'agents', 'buzz'), bin = join(root, 'bin'), runtime = join(root, 'runtime');
|
|
124
|
+
for (const dir of [root, bin, runtime]) {
|
|
125
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
126
|
+
await chmod(dir, 0o700);
|
|
127
|
+
}
|
|
128
|
+
const buzzBin = hostPath('buzz_bin', join(bin, 'buzz')), buzzAcpBin = hostPath('buzz_acp_bin', join(bin, 'buzz-acp'));
|
|
129
|
+
// Copy then rename, so a running buzz-acp keeps its inode and a Buzz.app update never changes agents silently.
|
|
130
|
+
for (const [source, target] of [[sources.buzz, buzzBin], [sources['buzz-acp'], buzzAcpBin]]) {
|
|
131
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
132
|
+
await copyFile(source, tmp);
|
|
133
|
+
await chmod(tmp, 0o755);
|
|
134
|
+
await rename(tmp, target);
|
|
135
|
+
}
|
|
136
|
+
const kitCli = hostPath('kit_cli', options.kitCli ? expand(options.kitCli, home) : await (options.installKit ?? npmInstallKit)(runtime, version));
|
|
137
|
+
if (!(await stat(kitCli).catch(() => undefined))?.isFile())
|
|
138
|
+
throw Error('kit_buzz_kit_cli_missing');
|
|
139
|
+
const host = { version: 1, kitVersion: version, node, kitCli, ownersDir, buzzBin, buzzAcpBin, buzzAppVersion: await bundleVersion(app),
|
|
140
|
+
buzzSha256: { buzz: await sha256(buzzBin), 'buzz-acp': await sha256(buzzAcpBin) } };
|
|
141
|
+
await writeAtomic(join(root, 'host.json'), JSON.stringify(host, null, 1) + '\n', 0o600);
|
|
142
|
+
const values = { NODE: node, KIT_CLI: kitCli, OWNERS_DIR: ownersDir, BUZZ_BIN: buzzBin, BUZZ_ACP_BIN: buzzAcpBin };
|
|
143
|
+
const runner = (await readFile(join(assets, 'buzz-run.sh'), 'utf8')).replace(/@@([A-Z_]+)@@/g, (_, key) => { if (!Object.hasOwn(values, key))
|
|
144
|
+
throw Error('kit_buzz_asset_placeholder_unknown'); return shellQuote(values[key]); });
|
|
145
|
+
await writeAtomic(join(bin, 'buzz-run.sh'), runner, 0o700);
|
|
146
|
+
for (const script of ['buzz-plist.py', 'tb-provider-apply.py'])
|
|
147
|
+
await writeAtomic(join(bin, script), await readFile(join(assets, script)), 0o700);
|
|
148
|
+
if (options.respondToDefault)
|
|
149
|
+
await writeAtomic(join(root, 'fleet-defaults.json'), JSON.stringify({ respond_to: options.respondToDefault }) + '\n', 0o600);
|
|
150
|
+
const owners = [];
|
|
151
|
+
try {
|
|
152
|
+
for (const entry of await readdir(ownersDir, { withFileTypes: true }))
|
|
153
|
+
if (entry.isDirectory() && /^[a-z0-9-]{2,32}$/.test(entry.name) && await exists(join(ownersDir, entry.name, '.tealbrick', 'native-serve.json')))
|
|
154
|
+
owners.push(entry.name);
|
|
155
|
+
}
|
|
156
|
+
catch { }
|
|
157
|
+
const warnings = [];
|
|
158
|
+
if (!await python3Ok())
|
|
159
|
+
warnings.push('/usr/bin/python3 is not usable; install the Xcode Command Line Tools (xcode-select --install).');
|
|
160
|
+
if (!owners.length)
|
|
161
|
+
warnings.push(`No enrolled kit owners under ${ownersDir}; run \`tealbrick native setup\` and \`tealbrick native enroll\` in ${ownersDir}/<owner>.`);
|
|
162
|
+
return { hostJson: join(root, 'host.json'), ...host, scripts: ['buzz-run.sh', 'buzz-plist.py', 'tb-provider-apply.py'].map(name => join(bin, name)),
|
|
163
|
+
fleetDefaults: options.respondToDefault ?? null, owners, warnings };
|
|
164
|
+
}
|
|
165
|
+
const launchctl = args => run('/bin/launchctl', args).then(() => 0, (e) => typeof e?.code === 'number' ? e.code : 1);
|
|
166
|
+
const AGENT_LABEL = /^[a-z0-9][a-z0-9-]{2,62}$/;
|
|
167
|
+
/** Names under ~/agents/buzz that are host files, never agents. */
|
|
168
|
+
const RESERVED_LABELS = ['bin', 'runtime', 'logs'];
|
|
169
|
+
/**
|
|
170
|
+
* Remove one provider-managed agent from this host (Buzz Desktop has no undeploy operation): boot out its
|
|
171
|
+
* launchd unit, delete the plist and move ~/agents/buzz/<label> to ~/agents/buzz/.removed/<label>-<ts>
|
|
172
|
+
* (0700; it still holds the agent key until you delete it).
|
|
173
|
+
*/
|
|
174
|
+
export async function removeBuzzAgent(label, options = {}) {
|
|
175
|
+
if (!AGENT_LABEL.test(label) || RESERVED_LABELS.includes(label))
|
|
176
|
+
throw Error('kit_buzz_label_invalid');
|
|
177
|
+
const home = options.home ?? homedir(), uid = options.uid ?? process.getuid?.() ?? 0, unit = `com.tealbrick.buzz-acp.${label}`, service = `gui/${uid}/${unit}`;
|
|
178
|
+
const root = join(home, 'agents', 'buzz'), dir = join(root, label), plist = join(home, 'Library', 'LaunchAgents', `${unit}.plist`);
|
|
179
|
+
const exec = options.launchctl ?? launchctl;
|
|
180
|
+
const loaded = await exec(['print', service]) === 0;
|
|
181
|
+
if (loaded && await exec(['bootout', service]) !== 0)
|
|
182
|
+
throw Error('kit_buzz_bootout_failed');
|
|
183
|
+
const plistStat = await lstat(plist).catch(() => undefined);
|
|
184
|
+
if (plistStat) {
|
|
185
|
+
if (!plistStat.isFile() || plistStat.isSymbolicLink())
|
|
186
|
+
throw Error('kit_buzz_plist_unsafe');
|
|
187
|
+
await unlink(plist);
|
|
188
|
+
}
|
|
189
|
+
const dirStat = await lstat(dir).catch(() => undefined);
|
|
190
|
+
let movedTo;
|
|
191
|
+
if (dirStat) {
|
|
192
|
+
if (!dirStat.isDirectory() || dirStat.isSymbolicLink())
|
|
193
|
+
throw Error('kit_buzz_agent_dir_unsafe');
|
|
194
|
+
const removed = join(root, '.removed');
|
|
195
|
+
await mkdir(removed, { recursive: true, mode: 0o700 });
|
|
196
|
+
const removedStat = await lstat(removed);
|
|
197
|
+
if (!removedStat.isDirectory() || removedStat.isSymbolicLink())
|
|
198
|
+
throw Error('kit_buzz_removed_dir_unsafe');
|
|
199
|
+
await chmod(removed, 0o700);
|
|
200
|
+
movedTo = join(removed, `${label}-${(options.now?.() ?? new Date()).toISOString().replace(/[:.]/g, '-')}`);
|
|
201
|
+
await rename(dir, movedTo);
|
|
202
|
+
await chmod(movedTo, 0o700);
|
|
203
|
+
}
|
|
204
|
+
if (!loaded && !plistStat && !dirStat)
|
|
205
|
+
throw Error('kit_buzz_agent_not_found');
|
|
206
|
+
return { label, service, bootedOut: loaded, plistRemoved: !!plistStat, movedTo };
|
|
207
|
+
}
|
|
208
|
+
export async function buzzCommand(args) {
|
|
209
|
+
const [group, action, ...rest] = args;
|
|
210
|
+
if (!group || group === 'help' || group === '--help' || group === '-h') {
|
|
211
|
+
console.log(buzzUsage);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (group === 'provider' && action === 'install') {
|
|
215
|
+
const flags = flagsOf(rest, ['name', 'bin-dir'], ['force']);
|
|
216
|
+
const result = await installProvider({ ...(typeof flags.name === 'string' ? { name: flags.name } : {}), ...(typeof flags['bin-dir'] === 'string' ? { binDir: flags['bin-dir'] } : {}), force: flags.force === true });
|
|
217
|
+
console.log(`${result.updated ? 'Updated' : 'Installed'} Buzz Desktop provider ${result.name} (kit ${result.version}):\n ${result.path}`);
|
|
218
|
+
if (!result.discoverable)
|
|
219
|
+
console.log(`Warning: ${dirname(result.path)} is not on PATH; Buzz Desktop looks in ~/.local/bin and on PATH.`);
|
|
220
|
+
if (!result.python3)
|
|
221
|
+
console.log('Warning: /usr/bin/python3 is not usable; install the Xcode Command Line Tools (xcode-select --install).');
|
|
222
|
+
console.log(`Next, in Buzz Desktop:
|
|
223
|
+
1. Restart Buzz Desktop so it finds the new provider.
|
|
224
|
+
2. Open Agents > New agent.
|
|
225
|
+
3. Set "Run on" to ${result.name} (Teal Brick · ${result.name}).
|
|
226
|
+
4. Enter owner (the kit owner directory on the host) and host (the ssh alias or user@host of the agent host).
|
|
227
|
+
5. Create the agent. Desktop deploys it over ssh; the host must have run \`tealbrick buzz host setup\`.`);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (group === 'host' && action === 'setup') {
|
|
231
|
+
const flags = flagsOf(rest, ['owners-dir', 'buzz-app', 'node', 'kit-cli', 'respond-to-default'], []);
|
|
232
|
+
const str = (name) => typeof flags[name] === 'string' ? flags[name] : undefined;
|
|
233
|
+
const respond = str('respond-to-default');
|
|
234
|
+
if (respond !== undefined && respond !== 'owner-only' && respond !== 'anyone')
|
|
235
|
+
throw Error('kit_buzz_respond_to_default_invalid');
|
|
236
|
+
const result = await setupBuzzHost({ ownersDir: str('owners-dir'), buzzApp: str('buzz-app'), node: str('node'), kitCli: str('kit-cli'), respondToDefault: respond });
|
|
237
|
+
console.log(`Buzz agent host ready (kit ${result.kitVersion}, Buzz ${result.buzzAppVersion}).
|
|
238
|
+
host.json: ${result.hostJson}
|
|
239
|
+
node: ${result.node}
|
|
240
|
+
kit cli: ${result.kitCli}
|
|
241
|
+
owners dir: ${result.ownersDir}${result.owners.length ? ` (enrolled: ${result.owners.join(', ')})` : ''}
|
|
242
|
+
buzz: ${result.buzzBin}
|
|
243
|
+
buzz-acp: ${result.buzzAcpBin}
|
|
244
|
+
fleet respond_to default: ${result.fleetDefaults ?? '(none; Desktop choice applies)'}`);
|
|
245
|
+
for (const warning of result.warnings)
|
|
246
|
+
console.log(`Warning: ${warning}`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (group === 'host' && action === 'remove') {
|
|
250
|
+
if (rest.length !== 1)
|
|
251
|
+
throw Error('kit_buzz_invalid_arguments');
|
|
252
|
+
const result = await removeBuzzAgent(rest[0]);
|
|
253
|
+
console.log(`Removed Buzz agent ${result.label}:
|
|
254
|
+
launchd unit: ${result.bootedOut ? `booted out (${result.service})` : 'was not loaded'}
|
|
255
|
+
plist: ${result.plistRemoved ? 'deleted' : 'not present'}
|
|
256
|
+
agent dir: ${result.movedTo ? `moved to ${result.movedTo} (still holds the agent key; delete it when no longer needed)` : 'not present'}
|
|
257
|
+
Also delete the agent in Buzz Desktop so it is not deployed again.`);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
console.error(buzzUsage);
|
|
261
|
+
process.exitCode = 2;
|
|
262
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { checkbox, input, password, select, confirm } from '@inquirer/prompts';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
|
-
import { loginPortal, registerPortalCard, portalOrigin, agentOrigin } from './onboarding.js';
|
|
4
|
+
import { deviceLabel, loginPortal, registerPortalCard, portalOrigin, agentOrigin } from './onboarding.js';
|
|
5
5
|
import { collectSetup } from '@tealbrick/deliver/setup';
|
|
6
6
|
import { applyKit, planKit, readKit } from './index.js';
|
|
7
7
|
import { nativeCommand } from './native.js';
|
|
@@ -20,6 +20,11 @@ async function main() {
|
|
|
20
20
|
await nativeCommand(process.argv.slice(3), root);
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
|
+
if (command === 'buzz') {
|
|
24
|
+
const { buzzCommand } = await import('./buzz.js');
|
|
25
|
+
await buzzCommand(process.argv.slice(3));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
23
28
|
if (command === 'connect') {
|
|
24
29
|
await runtimeOnboarding('connect', process.argv.slice(3), root);
|
|
25
30
|
return;
|
|
@@ -41,7 +46,7 @@ async function main() {
|
|
|
41
46
|
return;
|
|
42
47
|
}
|
|
43
48
|
if (command !== 'setup') {
|
|
44
|
-
console.log('Usage: tealbrick setup | status | apply <config.json>\n tealbrick connect --workspace ID --node ID --agent ID [--issuer URL]\n tealbrick app claim --workspace ID --node ID --binding REF --endpoint HTTPS_ORIGIN --company PARTITION [--issuer URL]\nInstalled dependencies expose nothing until activated. Rebuild/restart Eve after changing selection.');
|
|
49
|
+
console.log('Usage: tealbrick setup | status | apply <config.json>\n tealbrick connect --workspace ID --node ID --agent ID [--issuer URL]\n tealbrick app claim --workspace ID --node ID --binding REF --endpoint HTTPS_ORIGIN --company PARTITION [--issuer URL]\n tealbrick buzz provider install | buzz host setup (Buzz Desktop agents on a Mac host; see BUZZ.md)\nInstalled dependencies expose nothing until activated. Rebuild/restart Eve after changing selection.');
|
|
45
50
|
return;
|
|
46
51
|
}
|
|
47
52
|
console.log('Setup requires Portal sign-in and saves an agent card in your chosen workspace. Other capabilities are optional.');
|
|
@@ -56,7 +61,7 @@ async function main() {
|
|
|
56
61
|
] });
|
|
57
62
|
const config = { version: 1 }, credentials = {};
|
|
58
63
|
const issuer = portalOrigin(await input({ message: 'Portal issuer URL', default: old.portal?.issuer ?? 'https://portal.tealbrick.com' }));
|
|
59
|
-
const login = await loginPortal(issuer, (url, code) => console.log(`Sign in and approve this setup in your browser: ${url}\nVerification code: ${code}`));
|
|
64
|
+
const login = await loginPortal(issuer, (url, code) => console.log(`Sign in and approve this setup in your browser: ${url}\nVerification code: ${code}`), { label: deviceLabel('Eve setup', root) });
|
|
60
65
|
if (!login.workspaces.length)
|
|
61
66
|
throw Error('kit_portal_create_workspace_then_retry');
|
|
62
67
|
const workspaceId = await select({ message: 'Portal workspace for this agent card', choices: login.workspaces.map(w => ({ name: w.name, value: w.id })) });
|
package/dist/index.d.ts
CHANGED
|
@@ -67,7 +67,7 @@ declare const schema: z.ZodObject<{
|
|
|
67
67
|
}[] | undefined;
|
|
68
68
|
spawns?: {
|
|
69
69
|
worker: string;
|
|
70
|
-
profile: "
|
|
70
|
+
profile: "small" | "light" | "standard" | "browser";
|
|
71
71
|
mode: "ephemeral" | "durable";
|
|
72
72
|
harnesses?: string[] | undefined;
|
|
73
73
|
bundles?: string[] | undefined;
|
|
@@ -150,7 +150,7 @@ declare const schema: z.ZodObject<{
|
|
|
150
150
|
kind: "user";
|
|
151
151
|
id: string;
|
|
152
152
|
};
|
|
153
|
-
allow: ("capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "
|
|
153
|
+
allow: ("logs" | "capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "events" | "runs" | "assignments" | "leases" | "resident" | "list" | "inventory" | "monitor" | "roles" | "ingress")[];
|
|
154
154
|
} | {
|
|
155
155
|
operation: "shareRevoke";
|
|
156
156
|
worker: string;
|
|
@@ -194,7 +194,7 @@ declare const schema: z.ZodObject<{
|
|
|
194
194
|
}[] | undefined;
|
|
195
195
|
spawns?: {
|
|
196
196
|
worker: string;
|
|
197
|
-
profile: "
|
|
197
|
+
profile: "small" | "light" | "standard" | "browser";
|
|
198
198
|
mode: "ephemeral" | "durable";
|
|
199
199
|
harnesses?: string[] | undefined;
|
|
200
200
|
bundles?: string[] | undefined;
|
|
@@ -277,7 +277,7 @@ declare const schema: z.ZodObject<{
|
|
|
277
277
|
kind: "user";
|
|
278
278
|
id: string;
|
|
279
279
|
};
|
|
280
|
-
allow: ("capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "
|
|
280
|
+
allow: ("logs" | "capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "events" | "runs" | "assignments" | "leases" | "resident" | "list" | "inventory" | "monitor" | "roles" | "ingress")[];
|
|
281
281
|
} | {
|
|
282
282
|
operation: "shareRevoke";
|
|
283
283
|
worker: string;
|
|
@@ -320,7 +320,7 @@ declare const schema: z.ZodObject<{
|
|
|
320
320
|
}[] | undefined;
|
|
321
321
|
spawns?: {
|
|
322
322
|
worker: string;
|
|
323
|
-
profile: "
|
|
323
|
+
profile: "small" | "light" | "standard" | "browser";
|
|
324
324
|
mode: "ephemeral" | "durable";
|
|
325
325
|
harnesses?: string[] | undefined;
|
|
326
326
|
bundles?: string[] | undefined;
|
|
@@ -403,7 +403,7 @@ declare const schema: z.ZodObject<{
|
|
|
403
403
|
kind: "user";
|
|
404
404
|
id: string;
|
|
405
405
|
};
|
|
406
|
-
allow: ("capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "
|
|
406
|
+
allow: ("logs" | "capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "events" | "runs" | "assignments" | "leases" | "resident" | "list" | "inventory" | "monitor" | "roles" | "ingress")[];
|
|
407
407
|
} | {
|
|
408
408
|
operation: "shareRevoke";
|
|
409
409
|
worker: string;
|
|
@@ -447,7 +447,7 @@ declare const schema: z.ZodObject<{
|
|
|
447
447
|
}[] | undefined;
|
|
448
448
|
spawns?: {
|
|
449
449
|
worker: string;
|
|
450
|
-
profile: "
|
|
450
|
+
profile: "small" | "light" | "standard" | "browser";
|
|
451
451
|
mode: "ephemeral" | "durable";
|
|
452
452
|
harnesses?: string[] | undefined;
|
|
453
453
|
bundles?: string[] | undefined;
|
|
@@ -530,7 +530,7 @@ declare const schema: z.ZodObject<{
|
|
|
530
530
|
kind: "user";
|
|
531
531
|
id: string;
|
|
532
532
|
};
|
|
533
|
-
allow: ("capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "
|
|
533
|
+
allow: ("logs" | "capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "events" | "runs" | "assignments" | "leases" | "resident" | "list" | "inventory" | "monitor" | "roles" | "ingress")[];
|
|
534
534
|
} | {
|
|
535
535
|
operation: "shareRevoke";
|
|
536
536
|
worker: string;
|
|
@@ -575,7 +575,7 @@ declare const schema: z.ZodObject<{
|
|
|
575
575
|
}[] | undefined;
|
|
576
576
|
spawns?: {
|
|
577
577
|
worker: string;
|
|
578
|
-
profile: "
|
|
578
|
+
profile: "small" | "light" | "standard" | "browser";
|
|
579
579
|
mode: "ephemeral" | "durable";
|
|
580
580
|
harnesses?: string[] | undefined;
|
|
581
581
|
bundles?: string[] | undefined;
|
|
@@ -658,7 +658,7 @@ declare const schema: z.ZodObject<{
|
|
|
658
658
|
kind: "user";
|
|
659
659
|
id: string;
|
|
660
660
|
};
|
|
661
|
-
allow: ("capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "
|
|
661
|
+
allow: ("logs" | "capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "events" | "runs" | "assignments" | "leases" | "resident" | "list" | "inventory" | "monitor" | "roles" | "ingress")[];
|
|
662
662
|
} | {
|
|
663
663
|
operation: "shareRevoke";
|
|
664
664
|
worker: string;
|
|
@@ -704,7 +704,7 @@ declare const schema: z.ZodObject<{
|
|
|
704
704
|
}[] | undefined;
|
|
705
705
|
spawns?: {
|
|
706
706
|
worker: string;
|
|
707
|
-
profile: "
|
|
707
|
+
profile: "small" | "light" | "standard" | "browser";
|
|
708
708
|
mode: "ephemeral" | "durable";
|
|
709
709
|
harnesses?: string[] | undefined;
|
|
710
710
|
bundles?: string[] | undefined;
|
|
@@ -787,7 +787,7 @@ declare const schema: z.ZodObject<{
|
|
|
787
787
|
kind: "user";
|
|
788
788
|
id: string;
|
|
789
789
|
};
|
|
790
|
-
allow: ("capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "
|
|
790
|
+
allow: ("logs" | "capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "events" | "runs" | "assignments" | "leases" | "resident" | "list" | "inventory" | "monitor" | "roles" | "ingress")[];
|
|
791
791
|
} | {
|
|
792
792
|
operation: "shareRevoke";
|
|
793
793
|
worker: string;
|
|
@@ -831,7 +831,7 @@ declare const schema: z.ZodObject<{
|
|
|
831
831
|
}[] | undefined;
|
|
832
832
|
spawns?: {
|
|
833
833
|
worker: string;
|
|
834
|
-
profile: "
|
|
834
|
+
profile: "small" | "light" | "standard" | "browser";
|
|
835
835
|
mode: "ephemeral" | "durable";
|
|
836
836
|
harnesses?: string[] | undefined;
|
|
837
837
|
bundles?: string[] | undefined;
|
|
@@ -914,7 +914,7 @@ declare const schema: z.ZodObject<{
|
|
|
914
914
|
kind: "user";
|
|
915
915
|
id: string;
|
|
916
916
|
};
|
|
917
|
-
allow: ("capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "
|
|
917
|
+
allow: ("logs" | "capabilities" | "auth" | "start" | "stop" | "wake" | "schedule" | "cool" | "status" | "exec" | "ssh" | "events" | "runs" | "assignments" | "leases" | "resident" | "list" | "inventory" | "monitor" | "roles" | "ingress")[];
|
|
918
918
|
} | {
|
|
919
919
|
operation: "shareRevoke";
|
|
920
920
|
worker: string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type ClaudeSdk } from './native-claude.js';
|
|
2
|
+
import { type GrantStoreOptions } from './native-standing-grants.js';
|
|
3
|
+
import { type BuzzDownload, type BuzzRun } from './native-buzz.js';
|
|
4
|
+
/**
|
|
5
|
+
* Agent Client Protocol (ACP, JSON-RPC 2.0 over stdio) agent for this kit root, for chat harnesses
|
|
6
|
+
* such as Buzz's `buzz-acp`. It runs the SAME owner harness as `native serve` (profile, expert
|
|
7
|
+
* contracts, Portal grants, PreToolUse gate, traces) in-process; no HTTP, no extra credential.
|
|
8
|
+
* Messages are treated as untrusted. Payload approvals have no surface here: they are declined and
|
|
9
|
+
* the owner is told to continue in TBD, so a chat message can never approve an outward write. Calls
|
|
10
|
+
* covered by an owner standing grant (config, enforced by the kit hook) proceed without a prompt here too.
|
|
11
|
+
* With a Buzz CLI, the owner session also gets the in-process `buzz` MCP tools (agent↔agent
|
|
12
|
+
* messaging) and each final answer is auto-posted with request/answer mention rules and a
|
|
13
|
+
* per-thread loop breaker. ACP `session/new` mcpServers are never attached (they could grant a shell).
|
|
14
|
+
*/
|
|
15
|
+
export { buzzRoute, buzzTrigger } from './native-buzz.js';
|
|
16
|
+
export interface AcpDependencies {
|
|
17
|
+
claudeSdk?: ClaudeSdk;
|
|
18
|
+
log?: (event: Record<string, unknown>) => void;
|
|
19
|
+
env?: NodeJS.ProcessEnv;
|
|
20
|
+
input?: NodeJS.ReadableStream;
|
|
21
|
+
output?: NodeJS.WritableStream;
|
|
22
|
+
runtimeSync?: false;
|
|
23
|
+
/** Buzz CLI path: enables the buzz tools and posts each turn's final answer (identity comes from buzz-acp's injected BUZZ_* env). */
|
|
24
|
+
buzzCli?: string;
|
|
25
|
+
/** Injectable Buzz CLI runner (tests); behaves like buzzCli. */
|
|
26
|
+
buzz?: BuzzRun;
|
|
27
|
+
/** Injectable media downloader (tests); default `buzz media get` through buzzCli. */
|
|
28
|
+
download?: BuzzDownload;
|
|
29
|
+
/** Relay base URL for attachment URLs (default BUZZ_RELAY_URL). */
|
|
30
|
+
relayUrl?: string;
|
|
31
|
+
/** Overrides the auto-post transport only (legacy hook). */
|
|
32
|
+
send?: (args: string[], text: string) => Promise<boolean>;
|
|
33
|
+
/** Identity overrides (tests). Defaults: owner from BUZZ_AUTH_TAG, self derived from BUZZ_PRIVATE_KEY. */
|
|
34
|
+
ownerPubkey?: string;
|
|
35
|
+
selfPubkey?: string;
|
|
36
|
+
now?: () => number;
|
|
37
|
+
/** Test seams for the standing-grant usage store (clock, lock timing). */
|
|
38
|
+
standingGrants?: GrantStoreOptions;
|
|
39
|
+
}
|
|
40
|
+
export declare function serveAcp(root: string, deps?: AcpDependencies): Promise<void>;
|