@agfpd/iapeer 0.2.94 → 0.2.96
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/package.json +1 -1
- package/src/cli/index.ts +14 -2
- package/src/init/index.ts +6 -0
- package/src/onboard/risk.ts +110 -0
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -946,8 +946,8 @@ const USAGE = `usage: iapeer <verb> [args]
|
|
|
946
946
|
version | --version | -v print the installed binary's version
|
|
947
947
|
help | --help | -h print this usage (works appended to any verb; executes nothing)
|
|
948
948
|
daemon [--install-plist] run the host-wide HTTP-MCP router (launchd-held)
|
|
949
|
-
onboard [--dry-run] [--no-notifier] [--no-telegram] [--telegram-human <p>] [--telegram-user-id <id>] [--no-memory] [--memory <pkg>] [--infra <csv>]
|
|
950
|
-
backbone host-phase: marketplace → notifier → telegram (human peer) → memory (all default YES)
|
|
949
|
+
onboard [--accept-risk] [--dry-run] [--no-notifier] [--no-telegram] [--telegram-human <p>] [--telegram-user-id <id>] [--no-memory] [--memory <pkg>] [--infra <csv>]
|
|
950
|
+
backbone host-phase: marketplace → notifier → telegram (human peer) → memory (all default YES). --accept-risk (or IAPEER_ACCEPT_RISK=1) accepts the security warning non-interactively
|
|
951
951
|
status host snapshot: version, daemon health, memory slot (<provider> | none)
|
|
952
952
|
install-runtime <runtime> [--package pkg] [--npx] npx-install a runtime package + deploy its declared peer-set
|
|
953
953
|
update-runtime <runtime> | --all [--force] version-gate → re-install + re-provision declared set → restart the runtime's peers
|
|
@@ -1002,6 +1002,18 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
|
|
|
1002
1002
|
try {
|
|
1003
1003
|
switch (verb) {
|
|
1004
1004
|
case 'onboard': {
|
|
1005
|
+
// SECURITY GATE (pre-release): the operator must consciously accept the risk of
|
|
1006
|
+
// beta infra with live agents BEFORE onboard mutates the host. --accept-risk /
|
|
1007
|
+
// IAPEER_ACCEPT_RISK accepts non-interactively (one-liner installers / CI); a
|
|
1008
|
+
// TTY is prompted; a non-TTY without the flag is REFUSED with how-to (never a
|
|
1009
|
+
// silent proceed / hang). SKIPPED for --dry-run (a read-only preview mutates
|
|
1010
|
+
// nothing, so there is no risk to accept yet). Runs BEFORE daemon/peer steps.
|
|
1011
|
+
if (flags['dry-run'] !== true) {
|
|
1012
|
+
const { confirmOnboardRisk } = await import('../onboard/risk.ts')
|
|
1013
|
+
const gate = await confirmOnboardRisk({ accept: flags['accept-risk'] === true, env, out, errOut })
|
|
1014
|
+
if (gate === 'refused-non-tty') return 2 // explicit refusal: how-to printed
|
|
1015
|
+
if (gate === 'declined') return 1 // operator said no
|
|
1016
|
+
}
|
|
1005
1017
|
// Host-phase: register OUR marketplace in claude + codex (IDEMPOTENT — detect
|
|
1006
1018
|
// → skip when present; an already-configured host is a no-op). --dry-run
|
|
1007
1019
|
// reports the would-be actions without touching anything. --infra <csv> ALSO
|
package/src/init/index.ts
CHANGED
|
@@ -298,6 +298,12 @@ export function resolvePrimaryRuntime(
|
|
|
298
298
|
isInstalled: (rt: Runtime) => boolean = rt => isRuntimeInstalled(rt),
|
|
299
299
|
): Runtime {
|
|
300
300
|
if (explicit) {
|
|
301
|
+
// Infra runtimes (notifier/telegram) are NOT agentic binaries — their presence is
|
|
302
|
+
// the npx runtime package + the launchd plist, not a claude/codex install. The
|
|
303
|
+
// install-check below is agentic-only, so an explicit infra runtime must pass
|
|
304
|
+
// straight through; otherwise re-deploying an infra peer (install-runtime /
|
|
305
|
+
// update-runtime → createPeer → initPeer) wrongly fails "not installed".
|
|
306
|
+
if (isInfraRuntime(explicit)) return explicit
|
|
301
307
|
if (!isInstalled(explicit)) {
|
|
302
308
|
throw new IapError(
|
|
303
309
|
`runtime "${explicit}" is not installed on this host — install it (Claude Code / Codex CLI) or choose an installed runtime`,
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Onboard security gate (pre-release): the operator must consciously accept the
|
|
2
|
+
// risk of running beta infra with live agents BEFORE onboard mutates the host
|
|
3
|
+
// (daemon start + peer registration). The warning text is the owner's verbatim
|
|
4
|
+
// disclaimer — do NOT reword it.
|
|
5
|
+
//
|
|
6
|
+
// BOUNDARY: this must never break a NON-INTERACTIVE onboard (a one-liner installer /
|
|
7
|
+
// automation calls onboard without a TTY). Acceptance paths:
|
|
8
|
+
// • --accept-risk (CLI flag) or IAPEER_ACCEPT_RISK=1 (env) → accept, no prompt
|
|
9
|
+
// (the explicit non-interactive channel);
|
|
10
|
+
// • a real TTY → show the warning and require a y/yes answer;
|
|
11
|
+
// • a NON-TTY without the flag → REFUSE with how-to (never a silent proceed, never
|
|
12
|
+
// a hang waiting on stdin that no one will type into).
|
|
13
|
+
|
|
14
|
+
/** The owner's verbatim security disclaimer. Shown at the top of an interactive
|
|
15
|
+
* onboard and in the non-interactive refusal. Word-for-word — do not edit. */
|
|
16
|
+
export const ONBOARD_SECURITY_WARNING = `Security warning — please read.
|
|
17
|
+
|
|
18
|
+
iapeer is beta infrastructure for live AI agents on your machine. Expect sharp edges.
|
|
19
|
+
|
|
20
|
+
It starts a local daemon, registers peers, and routes messages between Claude Code,
|
|
21
|
+
Codex CLI, Telegram humans, and services. If tools, memory, or project files are
|
|
22
|
+
enabled, a bad prompt can make an agent do unsafe things.
|
|
23
|
+
|
|
24
|
+
iapeer is not a hostile multi-user security boundary.
|
|
25
|
+
Do not expose it to untrusted users, untrusted Telegram chats, or the public internet.
|
|
26
|
+
|
|
27
|
+
State, profiles, logs, memory, and runtime configuration live under ~/.iapeer and
|
|
28
|
+
peer-local .iapeer folders. Treat that data as sensitive.
|
|
29
|
+
|
|
30
|
+
If you are not comfortable with local daemons, filesystem access, Telegram bot tokens,
|
|
31
|
+
and agent tool permissions, do not continue.
|
|
32
|
+
|
|
33
|
+
Recommended baseline:
|
|
34
|
+
- localhost only;
|
|
35
|
+
- private Telegram bots/chats only;
|
|
36
|
+
- least-privilege project folders and tools;
|
|
37
|
+
- no secrets in reachable files;
|
|
38
|
+
- review peers, memory, logs, and configs after onboarding.
|
|
39
|
+
|
|
40
|
+
I understand this is powerful and inherently risky. Continue?`
|
|
41
|
+
|
|
42
|
+
/** How the onboard verb should react after the gate. */
|
|
43
|
+
export type RiskGateResult =
|
|
44
|
+
| 'accepted-flag' // --accept-risk / IAPEER_ACCEPT_RISK → non-interactive accept (no prompt)
|
|
45
|
+
| 'accepted-prompt' // interactive TTY answered y/yes
|
|
46
|
+
| 'declined' // interactive TTY answered no → abort
|
|
47
|
+
| 'refused-non-tty' // no TTY and no accept flag → refuse with how-to (never proceed)
|
|
48
|
+
|
|
49
|
+
export interface RiskGateOptions {
|
|
50
|
+
/** The --accept-risk CLI flag. */
|
|
51
|
+
accept?: boolean
|
|
52
|
+
env?: NodeJS.ProcessEnv
|
|
53
|
+
/** Override TTY detection (tests). Default: stdin AND stdout are TTYs. */
|
|
54
|
+
isTty?: boolean
|
|
55
|
+
/** Injectable prompt (tests). Default: a readline question on the live TTY. */
|
|
56
|
+
ask?: () => Promise<string>
|
|
57
|
+
/** stdout sink (default process.stdout). */
|
|
58
|
+
out?: (s: string) => void
|
|
59
|
+
/** stderr sink (default process.stderr). */
|
|
60
|
+
errOut?: (s: string) => void
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const RISK_ENV = 'IAPEER_ACCEPT_RISK'
|
|
64
|
+
|
|
65
|
+
function envAccepts(env: NodeJS.ProcessEnv): boolean {
|
|
66
|
+
return /^(1|true|yes)$/i.test((env[RISK_ENV] ?? '').trim())
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function defaultAsk(): Promise<string> {
|
|
70
|
+
const { createInterface } = await import('node:readline/promises')
|
|
71
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
72
|
+
try {
|
|
73
|
+
return (await rl.question('')).trim()
|
|
74
|
+
} finally {
|
|
75
|
+
rl.close()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Confirm the operator accepts the onboard risk. Returns the gate outcome; the
|
|
81
|
+
* caller maps a non-accept outcome to a non-zero exit. Never throws, never hangs a
|
|
82
|
+
* non-interactive run: the flag/env path is silent-accept, a TTY is prompted, a
|
|
83
|
+
* non-TTY without the flag is refused with the exact how-to.
|
|
84
|
+
*/
|
|
85
|
+
export async function confirmOnboardRisk(opts: RiskGateOptions = {}): Promise<RiskGateResult> {
|
|
86
|
+
const env = opts.env ?? process.env
|
|
87
|
+
const out = opts.out ?? ((s: string) => void process.stdout.write(s))
|
|
88
|
+
const errOut = opts.errOut ?? ((s: string) => void process.stderr.write(s))
|
|
89
|
+
|
|
90
|
+
// Non-interactive accept channel (one-liner installers / automation).
|
|
91
|
+
if (opts.accept === true || envAccepts(env)) return 'accepted-flag'
|
|
92
|
+
|
|
93
|
+
const tty = opts.isTty ?? (process.stdin.isTTY === true && process.stdout.isTTY === true)
|
|
94
|
+
if (!tty) {
|
|
95
|
+
// No one to answer the prompt — refuse LOUD with how-to, never a silent proceed.
|
|
96
|
+
errOut(
|
|
97
|
+
`${ONBOARD_SECURITY_WARNING}\n\n` +
|
|
98
|
+
`Non-interactive onboard: no TTY to confirm. Re-run with --accept-risk ` +
|
|
99
|
+
`(or set ${RISK_ENV}=1) to accept this risk explicitly.\n`,
|
|
100
|
+
)
|
|
101
|
+
return 'refused-non-tty'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
out(`${ONBOARD_SECURITY_WARNING} `)
|
|
105
|
+
const ask = opts.ask ?? defaultAsk
|
|
106
|
+
const answer = (await ask()).trim().toLowerCase()
|
|
107
|
+
if (answer === 'y' || answer === 'yes') return 'accepted-prompt'
|
|
108
|
+
out('onboard aborted — risk not accepted.\n')
|
|
109
|
+
return 'declined'
|
|
110
|
+
}
|