@agfpd/iapeer 0.1.0

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.
Files changed (63) hide show
  1. package/bin/iapeer +25 -0
  2. package/package.json +37 -0
  3. package/src/cli/cli.test.ts +130 -0
  4. package/src/cli/index.ts +608 -0
  5. package/src/cli/listTui.test.ts +70 -0
  6. package/src/cli/listTui.ts +165 -0
  7. package/src/codec/codec.test.ts +271 -0
  8. package/src/codec/index.ts +217 -0
  9. package/src/core/constants.test.ts +21 -0
  10. package/src/core/constants.ts +180 -0
  11. package/src/core/errors.ts +20 -0
  12. package/src/core/index.ts +3 -0
  13. package/src/core/normalize.test.ts +98 -0
  14. package/src/core/normalize.ts +89 -0
  15. package/src/core/socket.ts +63 -0
  16. package/src/create/create.test.ts +143 -0
  17. package/src/create/index.ts +178 -0
  18. package/src/daemon/daemon-http.test.ts +114 -0
  19. package/src/daemon/daemon.test.ts +103 -0
  20. package/src/daemon/index.ts +439 -0
  21. package/src/daemon/main.test.ts +194 -0
  22. package/src/daemon/main.ts +230 -0
  23. package/src/enable/enable.test.ts +92 -0
  24. package/src/enable/index.ts +381 -0
  25. package/src/identity/identity.test.ts +262 -0
  26. package/src/identity/index.ts +603 -0
  27. package/src/index.ts +27 -0
  28. package/src/init/index.ts +408 -0
  29. package/src/init/init.test.ts +171 -0
  30. package/src/init/runtime-resolve.test.ts +49 -0
  31. package/src/install/index.ts +84 -0
  32. package/src/install/install.test.ts +31 -0
  33. package/src/launch/adapters/claude.ts +250 -0
  34. package/src/launch/adapters/codex.ts +329 -0
  35. package/src/launch/adapters/notifier.ts +90 -0
  36. package/src/launch/adapters/telegram.ts +130 -0
  37. package/src/launch/bootstrap.test.ts +56 -0
  38. package/src/launch/composeSystemPrompt.layers.test.ts +319 -0
  39. package/src/launch/composeSystemPrompt.test.ts +98 -0
  40. package/src/launch/composeSystemPrompt.ts +261 -0
  41. package/src/launch/index.ts +253 -0
  42. package/src/launch/launch.test.ts +233 -0
  43. package/src/launch/launchd.test.ts +363 -0
  44. package/src/launch/launchd.ts +375 -0
  45. package/src/launch/launchdRun.ts +168 -0
  46. package/src/launch/sockdir.test.ts +70 -0
  47. package/src/launch/types.ts +300 -0
  48. package/src/lifecycle/index.ts +840 -0
  49. package/src/lifecycle/lifecycle.test.ts +496 -0
  50. package/src/onboard/index.ts +135 -0
  51. package/src/onboard/onboard.test.ts +39 -0
  52. package/src/provision/index.ts +170 -0
  53. package/src/provision/provision.test.ts +104 -0
  54. package/src/registry/index.ts +453 -0
  55. package/src/registry/registry.test.ts +400 -0
  56. package/src/runtime/deploy.ts +230 -0
  57. package/src/runtime/index.ts +191 -0
  58. package/src/runtime/runtime.test.ts +226 -0
  59. package/src/storage/index.ts +331 -0
  60. package/src/storage/peers-home.test.ts +34 -0
  61. package/src/storage/storage.test.ts +65 -0
  62. package/src/transport/index.ts +522 -0
  63. package/tsconfig.json +17 -0
package/bin/iapeer ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bash
2
+ # npx @agfpd/iapeer bootstrap.
3
+ #
4
+ # Runs the iapeer foundation CLI from the package SOURCE via bun (the foundation is a
5
+ # bun project; there is no precompiled JS shipped). A BARE invocation (`npx
6
+ # @agfpd/iapeer`, no args) runs the `install` verb — compile the stable
7
+ # ~/.local/bin/iapeer binary + the global ~/.iapeer scaffold + the daemon plist —
8
+ # implementing the `npx @agfpd/iapeer` install contract. Any args pass straight through
9
+ # to the CLI (`npx @agfpd/iapeer onboard`, `… create <p>`, …).
10
+ set -euo pipefail
11
+
12
+ PKG_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
13
+ CLI="$PKG_ROOT/src/cli/index.ts"
14
+
15
+ if ! command -v bun >/dev/null 2>&1; then
16
+ echo "iapeer: 'bun' is required on PATH to run the foundation CLI — install it from https://bun.sh" >&2
17
+ exit 1
18
+ fi
19
+
20
+ if [ "$#" -eq 0 ]; then
21
+ # Bare bootstrap → install the foundation (binary + scaffold + daemon plist).
22
+ exec bun "$CLI" install
23
+ fi
24
+
25
+ exec bun "$CLI" "$@"
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@agfpd/iapeer",
3
+ "version": "0.1.0",
4
+ "description": "Foundation core for the IAPeer multi-agent ecosystem: identity, registry, storage, codec.",
5
+ "type": "module",
6
+ "bin": {
7
+ "iapeer": "bin/iapeer"
8
+ },
9
+ "files": [
10
+ "src",
11
+ "bin",
12
+ "tsconfig.json"
13
+ ],
14
+ "exports": {
15
+ ".": "./src/index.ts",
16
+ "./core": "./src/core/index.ts",
17
+ "./storage": "./src/storage/index.ts",
18
+ "./identity": "./src/identity/index.ts",
19
+ "./registry": "./src/registry/index.ts",
20
+ "./codec": "./src/codec/index.ts"
21
+ },
22
+ "scripts": {
23
+ "test": "IAPEER_TEST_SANDBOX=1 bun test",
24
+ "typecheck": "tsc --noEmit"
25
+ },
26
+ "dependencies": {
27
+ "@modelcontextprotocol/sdk": "1.29.0",
28
+ "proper-lockfile": "^4.1.2",
29
+ "transliteration": "^2.6.1"
30
+ },
31
+ "devDependencies": {
32
+ "@types/bun": "latest",
33
+ "@types/node": "^22.0.0",
34
+ "@types/proper-lockfile": "^4.1.4",
35
+ "typescript": "^5.6.0"
36
+ }
37
+ }
@@ -0,0 +1,130 @@
1
+ // iapeer CLI verbs — list / stop / start (C1 + fleet guard) / send validation.
2
+ // No tmux: killSession on a non-existent session is a no-op, the C1 flag is the
3
+ // observable. The FLEET GUARD (H4) is the safety-critical case — a foreign
4
+ // persistent-peer launchd plist must be refused.
5
+
6
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
7
+ import { mkdtempSync, rmSync, writeFileSync } from 'fs'
8
+ import { tmpdir } from 'os'
9
+ import { join } from 'path'
10
+ import { formatListTable, listPeers, parseArgs, sendMessage, startPeer, stopPeer } from './index.ts'
11
+ import { upsertPeer } from '../registry/index.ts'
12
+ import { isStopped, loadLifecycleConfig, setStopped } from '../lifecycle/index.ts'
13
+ import { launchdPlistPath } from '../launch/launchd.ts'
14
+
15
+ let root: string
16
+ let laDir: string
17
+ function env(): NodeJS.ProcessEnv {
18
+ const e: NodeJS.ProcessEnv = {
19
+ ...process.env,
20
+ IAPEER_ROOT: root,
21
+ IAPEER_LAUNCHAGENTS_DIR: laDir,
22
+ IAPEER_SOCK_DIR: join(root, 'socks'),
23
+ }
24
+ delete e.PEER_PERSONALITY
25
+ delete e.PEER_IDENTITY
26
+ delete e.PEER_RUNTIME
27
+ return e
28
+ }
29
+
30
+ beforeEach(() => {
31
+ root = mkdtempSync(join(tmpdir(), 'iapeer-cli-root-'))
32
+ laDir = mkdtempSync(join(tmpdir(), 'iapeer-cli-la-'))
33
+ })
34
+ afterEach(() => {
35
+ rmSync(root, { recursive: true, force: true })
36
+ rmSync(laDir, { recursive: true, force: true })
37
+ })
38
+
39
+ async function register(personality: string, runtime = 'claude', intelligence: 'artificial' | 'natural' | 'absent' = 'artificial'): Promise<void> {
40
+ await upsertPeer({ personality, runtime, cwd: `/tmp/${personality}`, intelligence }, { rootDir: root })
41
+ }
42
+
43
+ describe('list', () => {
44
+ test('lists registered peers with per-runtime liveness (asleep / stopped)', async () => {
45
+ await register('alpha')
46
+ await register('beta')
47
+ const e = env()
48
+ setStopped(loadLifecycleConfig(e), 'claude-beta')
49
+ const rows = listPeers({ env: e })
50
+ const alpha = rows.find(r => r.personality === 'alpha')!
51
+ const beta = rows.find(r => r.personality === 'beta')!
52
+ expect(alpha.runtimes[0]).toEqual({ runtime: 'claude', status: 'asleep' })
53
+ expect(beta.runtimes[0]).toEqual({ runtime: 'claude', status: 'stopped' })
54
+ expect(formatListTable(rows)).toContain('alpha')
55
+ expect(formatListTable(rows)).toContain('✕ claude') // stopped glyph for beta
56
+ })
57
+ test('empty registry → friendly message', () => {
58
+ expect(formatListTable(listPeers({ env: env() }))).toBe('no peers registered\n')
59
+ })
60
+ })
61
+
62
+ describe('stop / start (C1 durable flag, warm runtime)', () => {
63
+ test('stop sets the durable flag; start clears it', async () => {
64
+ await register('gamma')
65
+ const e = env()
66
+ const cfg = loadLifecycleConfig(e)
67
+ expect(isStopped(cfg, 'claude-gamma')).toBe(false)
68
+ expect(stopPeer('gamma', undefined, { env: e })[0].action).toBe('stopped')
69
+ expect(isStopped(cfg, 'claude-gamma')).toBe(true)
70
+ expect(startPeer('gamma', undefined, { env: e })[0].action).toBe('started')
71
+ expect(isStopped(cfg, 'claude-gamma')).toBe(false)
72
+ })
73
+ test('stop an unregistered peer → throws', () => {
74
+ expect(() => stopPeer('nobody', undefined, { env: env() })).toThrow(/not registered/)
75
+ })
76
+ })
77
+
78
+ describe('FLEET GUARD (H4) — foreign persistent-peer launchd plist is off-limits', () => {
79
+ test('stop refuses a peer whose com.iapeer.<p>.plist is NOT foundation-owned', async () => {
80
+ await register('boris')
81
+ // a live PP peer owns com.iapeer.boris.plist WITHOUT the foundation sentinel
82
+ writeFileSync(launchdPlistPath('boris', env()), '<?xml version="1.0"?>\n<plist><dict><key>Label</key><string>com.iapeer.boris</string></dict></plist>\n')
83
+ const e = env()
84
+ const out = stopPeer('boris', undefined, { env: e })
85
+ expect(out[0].action).toBe('refused-foreign-launchd')
86
+ // the durable flag was NOT set — the foundation did not touch the PP peer
87
+ expect(isStopped(loadLifecycleConfig(e), 'claude-boris')).toBe(false)
88
+ // start likewise refuses
89
+ expect(startPeer('boris', undefined, { env: e })[0].action).toBe('refused-foreign-launchd')
90
+ })
91
+ })
92
+
93
+ describe('send validation', () => {
94
+ test('invalid --from identity → throws', async () => {
95
+ await register('alpha')
96
+ await expect(
97
+ sendMessage({ from: 'notanidentity', target: 'alpha', message: 'hi', env: env() }),
98
+ ).rejects.toThrow(/invalid/)
99
+ })
100
+ test('unknown --from caller → throws (not registered)', async () => {
101
+ await register('alpha')
102
+ await expect(
103
+ sendMessage({ from: 'claude-ghost', target: 'alpha', message: 'hi', env: env() }),
104
+ ).rejects.toThrow(/unknown caller|not registered/)
105
+ })
106
+ test('target not in registry → throws before any wake', async () => {
107
+ await register('alpha')
108
+ await expect(
109
+ sendMessage({ from: 'claude-alpha', target: 'nobody', message: 'hi', env: env() }),
110
+ ).rejects.toThrow(/not in the IAPeer peers index|self/)
111
+ })
112
+ })
113
+
114
+ describe('parseArgs (audit #27 — value beginning with --)', () => {
115
+ test('--key=value preserves a value that starts with --', () => {
116
+ expect(parseArgs(['send', 'boris', '--message=--look', '--topic=re: x']).flags).toMatchObject({
117
+ message: '--look',
118
+ topic: 're: x',
119
+ })
120
+ })
121
+ test('look-ahead --key value still works; a following --flag is not consumed as a value', () => {
122
+ const { flags, positionals } = parseArgs(['send', 'boris', '--message', 'hi', '--json'])
123
+ expect(positionals).toEqual(['send', 'boris'])
124
+ expect(flags.message).toBe('hi')
125
+ expect(flags.json).toBe(true)
126
+ })
127
+ test('empty value via = stays a string, not true', () => {
128
+ expect(parseArgs(['x', '--description=']).flags.description).toBe('')
129
+ })
130
+ })