@agfpd/iapeer 0.4.28 → 0.4.29

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.4.28",
3
+ "version": "0.4.29",
4
4
  "description": "Run Claude Code and Codex CLI as local background agents that message each other, wake on demand, and share one memory.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "agfpd",
package/src/cli/index.ts CHANGED
@@ -1291,6 +1291,32 @@ export function renderUsage(width: number = helpWidth()): string {
1291
1291
  return lines.join('\n') + '\n'
1292
1292
  }
1293
1293
 
1294
+ /** Focused help for ONE verb: the VERBS entry whose signature's first token equals `verb`,
1295
+ * rendered as its own `usage: iapeer <sig>` + wrapped description — or null when `verb` is
1296
+ * not a known verb (the caller then falls back to the full renderUsage). This is why
1297
+ * `iapeer connect telegram --help` prints connect's OWN usage instead of the whole verb list
1298
+ * (the prior behavior: `--help` anywhere dumped the general usage, burying the subcommand the
1299
+ * user asked about). First-token match is unambiguous — no two VERBS share a leading token. */
1300
+ export function renderVerbHelp(verb: string, width: number = helpWidth()): string | null {
1301
+ const entry = VERBS.find(v => v.sig.split(/\s+/)[0] === verb)
1302
+ if (!entry) return null
1303
+ const W = Math.max(40, width)
1304
+ const lines = [`usage: iapeer ${entry.sig}`, ...wrapText(entry.desc, W - 2).map(d => ` ${d}`)]
1305
+ return lines.join('\n') + '\n'
1306
+ }
1307
+
1308
+ /** Which verb's help an explicit help request targets, or null for the GENERAL usage:
1309
+ * - `<verb> --help` / `<verb> -h` → the verb (argv[0], when it is not itself a help token)
1310
+ * - `help <verb>` → argv[1]
1311
+ * - bare `help` / `--help` / `-h` → null (whole verb list)
1312
+ * An unknown target falls through to the general usage via renderVerbHelp returning null. */
1313
+ export function helpTargetVerb(argv: string[]): string | null {
1314
+ const first = argv[0]
1315
+ if (first && first !== 'help' && first !== '--help' && first !== '-h') return first
1316
+ if (first === 'help' && argv[1]) return argv[1]!
1317
+ return null
1318
+ }
1319
+
1294
1320
  export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.env): Promise<number> {
1295
1321
  const [verb, ...rest] = argv
1296
1322
  // CLI hygiene (design «Onboard костяка» §CLI-гигиена): an explicit help request —
@@ -1301,7 +1327,10 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
1301
1327
  // match is safe: the look-ahead parser never consumes a `--`-token as a value, so
1302
1328
  // a LITERAL "--help" value is only expressible as `--key=--help` (not intercepted).
1303
1329
  if (verb === 'help' || argv.includes('--help') || argv.includes('-h')) {
1304
- process.stdout.write(renderUsage())
1330
+ // Per-verb help when a known verb is named (`connect telegram --help` → connect's own usage,
1331
+ // `help connect` → same); the general verb list only for a bare/unknown help request.
1332
+ const target = helpTargetVerb(argv)
1333
+ process.stdout.write((target && renderVerbHelp(target)) || renderUsage())
1305
1334
  return 0
1306
1335
  }
1307
1336
  const { positionals, flags } = parseArgs(rest)
package/src/init/index.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  // persistent-peer install-gate class is gone). A cold-start claude session
10
10
  // (--dangerously-skip-permissions, as the launch primitive runs it) finds the
11
11
  // `.mcp.json` http server and send_to_peer is callable on its FIRST turn. The server's
12
- // project-approval is pre-granted by ensureClaudeProjectMcpEnabled (the
12
+ // project-approval is pre-granted by ensureClaudeProjectSettings (the
13
13
  // enableAllProjectMcpServers flag below); on a VIRGIN config without it the launch's
14
14
  // bootDialogKeys clears the one-time "new MCP servers found" dialog as a backstop.
15
15
  //
@@ -121,27 +121,33 @@ export function writeClaudeMcpConfig(cwd: string, personality: string, daemonUrl
121
121
  }
122
122
 
123
123
  /**
124
- * Idempotently enable project-scope MCP servers for a claude peer by merging
125
- * `"enableAllProjectMcpServers": true` into `<cwd>/.claude/settings.json`.
124
+ * Idempotently merge the project-LOCAL claude settings that pre-empt BLOCKING startup/
125
+ * mid-session modals for a headless peer, into `<cwd>/.claude/settings.json`:
126
+ *
127
+ * - `"enableAllProjectMcpServers": true` — on a VIRGIN claude config, the FIRST launch
128
+ * in a cwd carrying a `.mcp.json` (writeClaudeMcpConfig above) shows a BLOCKING "N new
129
+ * MCP servers found in this project" approval dialog BEFORE the input prompt; a headless
130
+ * peer has no one to approve it, so the wake stalls. This flag pre-approves the cwd's MCP
131
+ * servers so the dialog never appears (verified live, claude 2.1.183). Set unconditionally.
132
+ * - `"tui": "default"` — pins the CLASSIC renderer so claude never pops its one-time
133
+ * "Try the new fullscreen renderer?" upsell modal (a BLOCKING mid-session nag that froze
134
+ * live fleet peers; empirically an explicit tui choice suppresses it — verified live). This
135
+ * is the PREVENTION belt to the supervisor nag-watcher: init keeps the modal from appearing
136
+ * for a new peer at all, the watcher dismisses the class on every peer mid-session. Set ONLY
137
+ * when ABSENT — never override an explicit operator/peer tui choice already in the file.
126
138
  *
127
- * Why: on a VIRGIN claude config, the FIRST launch in a cwd carrying a `.mcp.json`
128
- * (writeClaudeMcpConfig above) shows a BLOCKING "N new MCP servers found in this
129
- * project" approval dialog BEFORE the input prompt — a headless peer has no one to
130
- * approve it, so the wake stalls. This project-LOCAL flag pre-approves the cwd's MCP
131
- * servers so the dialog never appears (verified live, claude 2.1.183). It is
132
139
  * PROJECT-scoped (this peer's cwd only) — it does NOT touch the user's GLOBAL
133
- * ~/.claude/settings.json, so it never alters the user's own claude environment (the
134
- * same constraint that forbids a global skipDangerousModePermissionPrompt). The launch
135
- * adapter's bootDialogKeys also Enter-confirms this dialog as a belt-and-suspenders
136
- * backstop, so a peer whose cwd predates this flag still boots.
140
+ * ~/.claude/settings.json, so it never alters the user's own claude environment (the same
141
+ * constraint that forbids a global skipDangerousModePermissionPrompt). Both modals also have
142
+ * runtime backstops (bootDialogKeys Enter-confirms the MCP dialog; the supervisor nag-watcher
143
+ * declines the fullscreen modal), so a peer whose cwd predates these keys still boots safely.
137
144
  *
138
- * NO-CLOBBER merge (the file may carry foreign blocks — plugin enables, statusline
139
- * wrappers, the native-memory lever): parse/patch + atomic write; a non-object
140
- * settings.json is REFUSED rather than overwritten. Returns the written path, or null
141
- * when already set / on a refusal / on any error (best-effort, never throws — the
142
- * bootDialogKeys backstop covers the dialog regardless).
145
+ * NO-CLOBBER merge (the file may carry foreign blocks — plugin enables, statusline wrappers,
146
+ * the native-memory lever): parse/patch + atomic write; a non-object settings.json is REFUSED
147
+ * rather than overwritten. Returns the written path, or null when nothing changed / on a refusal
148
+ * / on any error (best-effort, never throws — the runtime backstops cover the modals regardless).
143
149
  */
144
- export function ensureClaudeProjectMcpEnabled(cwd: string): string | null {
150
+ export function ensureClaudeProjectSettings(cwd: string): string | null {
145
151
  const path = claudeSettingsPath(cwd)
146
152
  try {
147
153
  let obj: Record<string, unknown> = {}
@@ -150,13 +156,21 @@ export function ensureClaudeProjectMcpEnabled(cwd: string): string | null {
150
156
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null // refuse to clobber a non-object
151
157
  obj = raw as Record<string, unknown>
152
158
  }
153
- if (obj.enableAllProjectMcpServers === true) return path // already enabled
154
- obj.enableAllProjectMcpServers = true
159
+ let changed = false
160
+ if (obj.enableAllProjectMcpServers !== true) {
161
+ obj.enableAllProjectMcpServers = true // pre-approve the cwd's MCP servers (no boot approval dialog)
162
+ changed = true
163
+ }
164
+ if (obj.tui === undefined) {
165
+ obj.tui = 'default' // classic renderer → no fullscreen-upsell modal; ABSENT-only (no override of an explicit choice)
166
+ changed = true
167
+ }
168
+ if (!changed) return path // both already in place — idempotent, no rewrite
155
169
  mkdirSync(dirname(path), { recursive: true })
156
170
  writeFileAtomic(path, `${JSON.stringify(obj, null, 2)}\n`, 0o644)
157
171
  return path
158
172
  } catch {
159
- return null // best-effort; the bootDialogKeys backstop still clears the dialog
173
+ return null // best-effort; the bootDialogKeys / nag-watcher backstops still clear the modals
160
174
  }
161
175
  }
162
176
 
@@ -476,7 +490,7 @@ export async function initPeer(opts: InitPeerOptions): Promise<InitPeerResult> {
476
490
  // Pre-approve the cwd's project MCP servers so the FIRST virgin-config launch does
477
491
  // not stall on the blocking "N new MCP servers found" dialog (project-local, never
478
492
  // touches the user's global settings; bootDialogKeys is the backstop).
479
- ensureClaudeProjectMcpEnabled(provisioned.cwd)
493
+ ensureClaudeProjectSettings(provisioned.cwd)
480
494
  }
481
495
  // codex: write the token-free host-wide config.toml block (dummy bearer flips codex's
482
496
  // auth gate; the OPEN daemon authenticates by the identity header). send_to_peer works