@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agfpd/iapeer",
3
- "version": "0.2.94",
3
+ "version": "0.2.96",
4
4
  "description": "Foundation core for the iapeer multi-agent ecosystem: identity, registry, storage, codec.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "agfpd",
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
+ }