@tealbrick/kit 0.3.0-rc.1 → 0.3.0-rc.10
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 +146 -0
- package/NATIVE.md +10 -4
- package/README.md +263 -2
- 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 +9 -4
- package/dist/index.d.ts +15 -14
- package/dist/index.js +1 -1
- package/dist/native-acp.d.ts +40 -0
- package/dist/native-acp.js +224 -0
- package/dist/native-app-capabilities.d.ts +33 -0
- package/dist/native-app-capabilities.js +37 -0
- package/dist/native-buzz.d.ts +161 -0
- package/dist/native-buzz.js +649 -0
- package/dist/native-child-guard.js +7 -2
- package/dist/native-claude.d.ts +99 -0
- package/dist/native-claude.js +584 -0
- package/dist/native-enroll.d.ts +22 -0
- package/dist/native-enroll.js +85 -0
- package/dist/native-runtime-sync.d.ts +49 -0
- package/dist/native-runtime-sync.js +101 -0
- package/dist/native-selection.js +19 -4
- package/dist/native-serve-config.d.ts +286 -0
- package/dist/native-serve-config.js +296 -0
- package/dist/native-serve.d.ts +76 -0
- package/dist/native-serve.js +145 -0
- package/dist/native-setup.js +38 -15
- package/dist/native-standing-grants.d.ts +105 -0
- package/dist/native-standing-grants.js +226 -0
- package/dist/native.d.ts +3 -0
- package/dist/native.js +94 -5
- package/dist/onboarding.d.ts +8 -0
- package/dist/onboarding.js +9 -2
- package/dist/runtime-onboarding.js +2 -2
- package/dist/secrets.d.ts +2 -1
- package/dist/secrets.js +4 -2
- package/package.json +9 -7
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# tealbrick-buzz-host: installed by `tealbrick buzz host setup`; rewritten on every setup run.
|
|
3
|
+
"""Writes ~/Library/LaunchAgents/com.tealbrick.buzz-acp.<label>.plist for one provider-managed agent.
|
|
4
|
+
The unit runs ~/agents/buzz/bin/buzz-run.sh <label>; logs stay in the agent directory (0700)."""
|
|
5
|
+
import os, plistlib, re, sys
|
|
6
|
+
def main():
|
|
7
|
+
if len(sys.argv) != 2 or not re.fullmatch(r'[a-z0-9][a-z0-9-]{2,62}', sys.argv[1]):
|
|
8
|
+
print('usage: buzz-plist.py <label>', file=sys.stderr); return 2
|
|
9
|
+
label = sys.argv[1]; H = os.path.expanduser('~'); BUZZ = os.path.join(H, 'agents', 'buzz')
|
|
10
|
+
D = os.path.join(BUZZ, label); logs = os.path.join(D, 'logs')
|
|
11
|
+
os.makedirs(logs, mode=0o700, exist_ok=True); os.chmod(logs, 0o700)
|
|
12
|
+
agents = os.path.join(H, 'Library', 'LaunchAgents'); os.makedirs(agents, exist_ok=True)
|
|
13
|
+
unit = f'com.tealbrick.buzz-acp.{label}'
|
|
14
|
+
body = {'Label': unit, 'ProgramArguments': ['/bin/bash', os.path.join(BUZZ, 'bin', 'buzz-run.sh'), label],
|
|
15
|
+
'WorkingDirectory': D, 'RunAtLoad': True, 'KeepAlive': {'SuccessfulExit': False},
|
|
16
|
+
'ThrottleInterval': 30, 'ProcessType': 'Background', 'Umask': 63,
|
|
17
|
+
'StandardOutPath': os.path.join(logs, 'buzz-acp.out.log'), 'StandardErrorPath': os.path.join(logs, 'buzz-acp.err.log')}
|
|
18
|
+
path = os.path.join(agents, unit + '.plist'); tmp = path + '.tmp'
|
|
19
|
+
fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
20
|
+
with os.fdopen(fd, 'wb') as f: plistlib.dump(body, f)
|
|
21
|
+
os.chmod(tmp, 0o600); os.replace(tmp, path); print(path); return 0
|
|
22
|
+
if __name__ == '__main__': sys.exit(main())
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tealbrick-buzz-host: installed by `tealbrick buzz host setup`; rewritten on every setup run.
|
|
3
|
+
# Runs one provider-managed agent: buzz-acp with Teal Brick kit `native acp` as its ACP agent.
|
|
4
|
+
# Usage (from its launchd unit): buzz-run.sh <label>
|
|
5
|
+
# Each agent gets an isolated environment (env -i): nothing from the login session leaks into the
|
|
6
|
+
# agent, and one agent's keys never reach another (block/buzz#3142). The key and auth tag are read
|
|
7
|
+
# from their files by the final shell and never appear in any process argv (only their paths do).
|
|
8
|
+
# A missing precondition logs one line and exits 0: the unit uses KeepAlive SuccessfulExit=false, so
|
|
9
|
+
# launchd does not restart-loop an agent that cannot start. Fix the cause, then
|
|
10
|
+
# `launchctl kickstart -k gui/$(id -u)/com.tealbrick.buzz-acp.<label>`.
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
NODE=@@NODE@@
|
|
13
|
+
KIT_CLI=@@KIT_CLI@@
|
|
14
|
+
OWNERS_DIR=@@OWNERS_DIR@@
|
|
15
|
+
BUZZ=@@BUZZ_BIN@@
|
|
16
|
+
BUZZ_ACP=@@BUZZ_ACP_BIN@@
|
|
17
|
+
a=${1:-}
|
|
18
|
+
stop() { echo "buzz-run: ${a:-<no label>}: $1; not starting (exit 0, launchd will not restart it)" >&2; exit 0; }
|
|
19
|
+
[[ "$a" =~ ^[a-z0-9][a-z0-9-]{2,62}$ ]] || stop "bad or missing label"
|
|
20
|
+
D="$HOME/agents/buzz/$a"
|
|
21
|
+
[ -d "$D" ] || stop "agent directory $D is missing"
|
|
22
|
+
O=$(cat "$D/owner" 2>/dev/null || echo "$a")
|
|
23
|
+
[[ "$O" =~ ^[a-z0-9-]{2,32}$ ]] || stop "bad owner in $D/owner"
|
|
24
|
+
R="$OWNERS_DIR/$O"
|
|
25
|
+
[ -f "$R/.tealbrick/native-serve.json" ] || stop "kit owner '$O' is not set up under $OWNERS_DIR"
|
|
26
|
+
RELAY=$(cat "$D/relay-url" 2>/dev/null || cat "$HOME/agents/buzz/relay-url" 2>/dev/null || true)
|
|
27
|
+
[ -n "$RELAY" ] || stop "no relay URL ($D/relay-url)"
|
|
28
|
+
[ -s "$D/auth-tag.json" ] || stop "no auth tag ($D/auth-tag.json)"
|
|
29
|
+
[ -s "$D/nostr.key" ] || stop "no agent key ($D/nostr.key)"
|
|
30
|
+
exec /usr/bin/env -i HOME="$HOME" USER="${USER:-$(id -un)}" LOGNAME="${LOGNAME:-$(id -un)}" \
|
|
31
|
+
PATH="$(dirname "$NODE"):/usr/bin:/bin:/usr/sbin:/sbin" LANG=en_US.UTF-8 \
|
|
32
|
+
BUZZ_RELAY_URL="$RELAY" \
|
|
33
|
+
BUZZ_ACP_AGENT_COMMAND="$NODE" BUZZ_ACP_AGENT_ARGS="$KIT_CLI,native,acp,--root,$R,--buzz-cli,$BUZZ" \
|
|
34
|
+
BUZZ_ACP_RESPOND_TO=owner-only BUZZ_ACP_SESSION_POLICY=thread BUZZ_ACP_PERMISSION_MODE=default \
|
|
35
|
+
BUZZ_ACP_MULTIPLE_EVENT_HANDLING=queue BUZZ_ACP_IDLE_TIMEOUT=1800 BUZZ_ACP_NO_BASE_PROMPT=true \
|
|
36
|
+
BUZZ_ACP_NO_MEMORY=true BUZZ_ACP_CONFIG="$D/buzz-acp.toml" RUST_LOG=info \
|
|
37
|
+
$(grep -E "^BUZZ_ACP_(SUBSCRIBE|NO_MENTION_FILTER|CHANNELS|KINDS|RESPOND_TO|RESPOND_TO_ALLOWLIST|SESSION_POLICY|IDLE_TIMEOUT|MAX_TURN_DURATION|TURN_TIMEOUT|AGENTS|HEARTBEAT_INTERVAL|DEDUP|MULTIPLE_EVENT_HANDLING|RELAY_OBSERVER|LAZY_POOL)=[A-Za-z0-9,._:-]*$" "$D/acp.env" 2>/dev/null || true) \
|
|
38
|
+
/bin/bash -c 'set -eu; BUZZ_PRIVATE_KEY=$(cat "$1"); BUZZ_AUTH_TAG=$(cat "$2"); export BUZZ_PRIVATE_KEY BUZZ_AUTH_TAG; exec "$3"' \
|
|
39
|
+
buzz-run "$D/nostr.key" "$D/auth-tag.json" "$BUZZ_ACP"
|
|
@@ -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 })) });
|
|
@@ -127,4 +132,4 @@ async function main() {
|
|
|
127
132
|
console.log('Portal agent card saved and confirmed in workspace discovery. Rebuild/restart Eve to connect.');
|
|
128
133
|
console.log('Local credentials stay in .tealbrick/kit.credentials.json (owner-only). Supply them as runtime secrets when deploying. Provider connectivity has not been tested.');
|
|
129
134
|
}
|
|
130
|
-
main().catch(error => { const message = error instanceof Error ? error.message : ''; console.error(/^kit_[a-z_]+(?::agent\/[a-zA-Z0-9_./-]+)
|
|
135
|
+
main().catch(error => { const message = error instanceof Error ? error.message : ''; console.error(/^(?:kit_[a-z_]+(?::agent\/[a-zA-Z0-9_./-]+|:[a-zA-Z0-9_.,]+)?|runtime_product_inactive)$/.test(message) ? message : 'Kit setup failed; check configuration and permissions. Credentials omitted.'); process.exitCode = 1; });
|