@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.
- package/bin/iapeer +25 -0
- package/package.json +37 -0
- package/src/cli/cli.test.ts +130 -0
- package/src/cli/index.ts +608 -0
- package/src/cli/listTui.test.ts +70 -0
- package/src/cli/listTui.ts +165 -0
- package/src/codec/codec.test.ts +271 -0
- package/src/codec/index.ts +217 -0
- package/src/core/constants.test.ts +21 -0
- package/src/core/constants.ts +180 -0
- package/src/core/errors.ts +20 -0
- package/src/core/index.ts +3 -0
- package/src/core/normalize.test.ts +98 -0
- package/src/core/normalize.ts +89 -0
- package/src/core/socket.ts +63 -0
- package/src/create/create.test.ts +143 -0
- package/src/create/index.ts +178 -0
- package/src/daemon/daemon-http.test.ts +114 -0
- package/src/daemon/daemon.test.ts +103 -0
- package/src/daemon/index.ts +439 -0
- package/src/daemon/main.test.ts +194 -0
- package/src/daemon/main.ts +230 -0
- package/src/enable/enable.test.ts +92 -0
- package/src/enable/index.ts +381 -0
- package/src/identity/identity.test.ts +262 -0
- package/src/identity/index.ts +603 -0
- package/src/index.ts +27 -0
- package/src/init/index.ts +408 -0
- package/src/init/init.test.ts +171 -0
- package/src/init/runtime-resolve.test.ts +49 -0
- package/src/install/index.ts +84 -0
- package/src/install/install.test.ts +31 -0
- package/src/launch/adapters/claude.ts +250 -0
- package/src/launch/adapters/codex.ts +329 -0
- package/src/launch/adapters/notifier.ts +90 -0
- package/src/launch/adapters/telegram.ts +130 -0
- package/src/launch/bootstrap.test.ts +56 -0
- package/src/launch/composeSystemPrompt.layers.test.ts +319 -0
- package/src/launch/composeSystemPrompt.test.ts +98 -0
- package/src/launch/composeSystemPrompt.ts +261 -0
- package/src/launch/index.ts +253 -0
- package/src/launch/launch.test.ts +233 -0
- package/src/launch/launchd.test.ts +363 -0
- package/src/launch/launchd.ts +375 -0
- package/src/launch/launchdRun.ts +168 -0
- package/src/launch/sockdir.test.ts +70 -0
- package/src/launch/types.ts +300 -0
- package/src/lifecycle/index.ts +840 -0
- package/src/lifecycle/lifecycle.test.ts +496 -0
- package/src/onboard/index.ts +135 -0
- package/src/onboard/onboard.test.ts +39 -0
- package/src/provision/index.ts +170 -0
- package/src/provision/provision.test.ts +104 -0
- package/src/registry/index.ts +453 -0
- package/src/registry/registry.test.ts +400 -0
- package/src/runtime/deploy.ts +230 -0
- package/src/runtime/index.ts +191 -0
- package/src/runtime/runtime.test.ts +226 -0
- package/src/storage/index.ts +331 -0
- package/src/storage/peers-home.test.ts +34 -0
- package/src/storage/storage.test.ts +65 -0
- package/src/transport/index.ts +522 -0
- 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
|
+
})
|