@meetopenbot/pi 0.0.1
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/README.md +53 -0
- package/dist/config.js +34 -0
- package/dist/format.js +120 -0
- package/dist/index.js +95 -0
- package/dist/session.js +68 -0
- package/dist/state.js +19 -0
- package/dist/stream.js +58 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @meetopenbot/pi
|
|
2
|
+
|
|
3
|
+
OpenBot agent plugin powered by the [Pi coding agent SDK](https://pi.dev/docs/latest/sdk).
|
|
4
|
+
|
|
5
|
+
Pi provides a full agent runtime with built-in tools (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`), session management, and model/provider support. This plugin wraps Pi as an OpenBot **agent runtime** — each `agent:invoke` runs a Pi turn and streams progress back via `agent:output`.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @meetopenbot/pi
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Add the plugin to your agent in `AGENT.md`:
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
plugins:
|
|
17
|
+
- id: '@meetopenbot/pi'
|
|
18
|
+
config:
|
|
19
|
+
provider: anthropic
|
|
20
|
+
model: claude-opus-4-5
|
|
21
|
+
thinkingLevel: medium
|
|
22
|
+
tools: read,bash,edit,write,grep,find,ls
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
| Option | Description |
|
|
28
|
+
|--------|-------------|
|
|
29
|
+
| `cwd` | Working directory for Pi tools and resource discovery. Defaults to the OpenBot channel `cwd`, then `process.cwd()`. |
|
|
30
|
+
| `agentDir` | Pi config directory (credentials, settings, sessions). Default: `~/.pi/agent`. |
|
|
31
|
+
| `provider` | Model provider (e.g. `anthropic`, `openai`). |
|
|
32
|
+
| `model` | Model id (e.g. `claude-opus-4-5`). |
|
|
33
|
+
| `thinkingLevel` | Extended thinking: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`. |
|
|
34
|
+
| `tools` | Comma-separated built-in tools to enable. |
|
|
35
|
+
| `excludeTools` | Comma-separated tool names to disable. |
|
|
36
|
+
| `noTools` | `all` or `builtin` to disable tools. |
|
|
37
|
+
| `systemPrompt` | Override Pi's system prompt for this agent. |
|
|
38
|
+
|
|
39
|
+
## API Keys
|
|
40
|
+
|
|
41
|
+
Pi resolves credentials via `AuthStorage` (see [Pi SDK docs](https://pi.dev/docs/latest/sdk)):
|
|
42
|
+
|
|
43
|
+
1. Runtime overrides
|
|
44
|
+
2. `~/.pi/agent/auth.json`
|
|
45
|
+
3. Environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)
|
|
46
|
+
|
|
47
|
+
## Sessions
|
|
48
|
+
|
|
49
|
+
Each OpenBot thread gets its own Pi session. The session file path is stored in thread state so conversations continue across turns.
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const THINKING_LEVELS = new Set([
|
|
2
|
+
'off',
|
|
3
|
+
'minimal',
|
|
4
|
+
'low',
|
|
5
|
+
'medium',
|
|
6
|
+
'high',
|
|
7
|
+
'xhigh',
|
|
8
|
+
]);
|
|
9
|
+
export const resolveConfig = (context, channelCwd) => {
|
|
10
|
+
const config = context.config;
|
|
11
|
+
const thinkingLevel = config.thinkingLevel && THINKING_LEVELS.has(config.thinkingLevel)
|
|
12
|
+
? config.thinkingLevel
|
|
13
|
+
: undefined;
|
|
14
|
+
return {
|
|
15
|
+
cwd: channelCwd || config.cwd,
|
|
16
|
+
agentDir: config.agentDir,
|
|
17
|
+
provider: config.provider?.trim() || undefined,
|
|
18
|
+
model: config.model?.trim() || undefined,
|
|
19
|
+
thinkingLevel,
|
|
20
|
+
tools: config.tools?.trim() || undefined,
|
|
21
|
+
excludeTools: config.excludeTools?.trim() || undefined,
|
|
22
|
+
noTools: config.noTools,
|
|
23
|
+
systemPrompt: config.systemPrompt?.trim() || undefined,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export const parseToolList = (value) => {
|
|
27
|
+
if (!value)
|
|
28
|
+
return undefined;
|
|
29
|
+
const tools = value
|
|
30
|
+
.split(',')
|
|
31
|
+
.map((tool) => tool.trim())
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
return tools.length > 0 ? tools : undefined;
|
|
34
|
+
};
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const isAssistantMessage = (message) => message.role === 'assistant';
|
|
2
|
+
export const createStreamState = () => ({
|
|
3
|
+
assistantText: '',
|
|
4
|
+
lastYieldedText: '',
|
|
5
|
+
});
|
|
6
|
+
const strArg = (args, ...keys) => {
|
|
7
|
+
if (!args || typeof args !== 'object')
|
|
8
|
+
return undefined;
|
|
9
|
+
const record = args;
|
|
10
|
+
for (const key of keys) {
|
|
11
|
+
const value = record[key];
|
|
12
|
+
if (typeof value === 'string' && value.trim())
|
|
13
|
+
return value.trim();
|
|
14
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
15
|
+
return String(value);
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
};
|
|
19
|
+
const truncate = (text, max = 80) => text.length > max ? `${text.slice(0, max - 3)}…` : text;
|
|
20
|
+
const quoteDetail = (detail) => detail.includes('`') ? `"${detail}"` : `\`${detail}\``;
|
|
21
|
+
const formatToolDetail = (toolName, args) => {
|
|
22
|
+
switch (toolName) {
|
|
23
|
+
case 'bash': {
|
|
24
|
+
const command = strArg(args, 'command');
|
|
25
|
+
return command ? truncate(command.replace(/\s+/g, ' ')) : undefined;
|
|
26
|
+
}
|
|
27
|
+
case 'read':
|
|
28
|
+
case 'edit':
|
|
29
|
+
case 'write': {
|
|
30
|
+
const path = strArg(args, 'path', 'file_path');
|
|
31
|
+
if (!path)
|
|
32
|
+
return undefined;
|
|
33
|
+
let detail = truncate(path, 120);
|
|
34
|
+
if (toolName === 'read') {
|
|
35
|
+
const offset = strArg(args, 'offset');
|
|
36
|
+
const limit = strArg(args, 'limit');
|
|
37
|
+
const parts = [offset && `offset=${offset}`, limit && `limit=${limit}`].filter(Boolean);
|
|
38
|
+
if (parts.length)
|
|
39
|
+
detail += ` (${parts.join(', ')})`;
|
|
40
|
+
}
|
|
41
|
+
return detail;
|
|
42
|
+
}
|
|
43
|
+
case 'grep': {
|
|
44
|
+
const pattern = strArg(args, 'pattern');
|
|
45
|
+
const path = strArg(args, 'path') ?? '.';
|
|
46
|
+
if (!pattern)
|
|
47
|
+
return undefined;
|
|
48
|
+
return `${truncate(pattern)} in ${truncate(path, 60)}`;
|
|
49
|
+
}
|
|
50
|
+
case 'find': {
|
|
51
|
+
const pattern = strArg(args, 'pattern');
|
|
52
|
+
const path = strArg(args, 'path') ?? '.';
|
|
53
|
+
if (!pattern)
|
|
54
|
+
return undefined;
|
|
55
|
+
return `${truncate(pattern)} in ${truncate(path, 60)}`;
|
|
56
|
+
}
|
|
57
|
+
case 'ls': {
|
|
58
|
+
const path = strArg(args, 'path') ?? '.';
|
|
59
|
+
return truncate(path, 120);
|
|
60
|
+
}
|
|
61
|
+
default:
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const formatToolStart = (toolName, args) => {
|
|
66
|
+
const detail = formatToolDetail(toolName, args);
|
|
67
|
+
if (detail)
|
|
68
|
+
return `Running **${toolName}** (${quoteDetail(detail)})…`;
|
|
69
|
+
return `Running **${toolName}**…`;
|
|
70
|
+
};
|
|
71
|
+
/** Map Pi session events to user-visible output chunks. */
|
|
72
|
+
export const formatPiEvent = (event, state) => {
|
|
73
|
+
switch (event.type) {
|
|
74
|
+
case 'message_update': {
|
|
75
|
+
const assistantEvent = event.assistantMessageEvent;
|
|
76
|
+
if (assistantEvent.type !== 'text_delta')
|
|
77
|
+
return undefined;
|
|
78
|
+
state.assistantText += assistantEvent.delta;
|
|
79
|
+
if (state.assistantText === state.lastYieldedText)
|
|
80
|
+
return undefined;
|
|
81
|
+
state.lastYieldedText = state.assistantText;
|
|
82
|
+
return state.assistantText;
|
|
83
|
+
}
|
|
84
|
+
case 'tool_execution_start': {
|
|
85
|
+
state.statusLine = formatToolStart(event.toolName, event.args);
|
|
86
|
+
return state.statusLine;
|
|
87
|
+
}
|
|
88
|
+
case 'tool_execution_end': {
|
|
89
|
+
if (!event.isError)
|
|
90
|
+
return undefined;
|
|
91
|
+
state.statusLine = `Tool **${event.toolName}** failed.`;
|
|
92
|
+
return state.statusLine;
|
|
93
|
+
}
|
|
94
|
+
case 'auto_retry_start': {
|
|
95
|
+
state.statusLine = `Retrying (${event.attempt}/${event.maxAttempts})…`;
|
|
96
|
+
return state.statusLine;
|
|
97
|
+
}
|
|
98
|
+
case 'compaction_start': {
|
|
99
|
+
state.statusLine = 'Compacting conversation context…';
|
|
100
|
+
return state.statusLine;
|
|
101
|
+
}
|
|
102
|
+
case 'agent_end': {
|
|
103
|
+
const lastAssistant = [...event.messages].reverse().find(isAssistantMessage);
|
|
104
|
+
if (lastAssistant?.stopReason === 'error' && lastAssistant.errorMessage?.trim()) {
|
|
105
|
+
return `**Pi error:** ${lastAssistant.errorMessage.trim()}`;
|
|
106
|
+
}
|
|
107
|
+
if (state.assistantText.trim()) {
|
|
108
|
+
return state.assistantText;
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
default:
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
export const formatPiError = (error) => {
|
|
117
|
+
if (error instanceof Error)
|
|
118
|
+
return error.message;
|
|
119
|
+
return String(error);
|
|
120
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { agentOutput, definePlugin, shouldHandleInvoke, } from '@meetopenbot/plugin-sdk';
|
|
2
|
+
import { resolveConfig } from './config.js';
|
|
3
|
+
import { formatPiError } from './format.js';
|
|
4
|
+
import { getOrCreatePiSession } from './session.js';
|
|
5
|
+
import { streamPiPrompt } from './stream.js';
|
|
6
|
+
export default definePlugin({
|
|
7
|
+
id: 'pi',
|
|
8
|
+
name: 'Pi',
|
|
9
|
+
description: 'Pi coding agent — read, edit, and run code in your workspace.',
|
|
10
|
+
configSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
cwd: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'Working directory for Pi tools and resource discovery.',
|
|
16
|
+
},
|
|
17
|
+
agentDir: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Pi config directory (default: ~/.pi/agent).',
|
|
20
|
+
},
|
|
21
|
+
provider: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Model provider (e.g. anthropic, openai).',
|
|
24
|
+
},
|
|
25
|
+
model: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Model id (e.g. claude-opus-4-5).',
|
|
28
|
+
},
|
|
29
|
+
thinkingLevel: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'Extended thinking level.',
|
|
32
|
+
enum: ['off', 'minimal', 'low', 'medium', 'high', 'xhigh'],
|
|
33
|
+
default: 'off',
|
|
34
|
+
},
|
|
35
|
+
tools: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'Comma-separated built-in tools to enable (e.g. read,bash,edit,write,grep,find,ls).',
|
|
38
|
+
},
|
|
39
|
+
excludeTools: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: 'Comma-separated tool names to disable.',
|
|
42
|
+
},
|
|
43
|
+
noTools: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
description: 'Disable tools: "all" or "builtin".',
|
|
46
|
+
enum: ['all', 'builtin'],
|
|
47
|
+
},
|
|
48
|
+
systemPrompt: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: 'Override Pi system prompt for this agent.',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
factory: (context) => (builder) => {
|
|
55
|
+
builder.on('agent:invoke', async function* (event, handlerCtx) {
|
|
56
|
+
if (!shouldHandleInvoke(event, context.agentId))
|
|
57
|
+
return;
|
|
58
|
+
const threadId = event.meta?.threadId ?? handlerCtx.state.threadId;
|
|
59
|
+
const prompt = (event.data.content ?? '').trim();
|
|
60
|
+
if (!prompt) {
|
|
61
|
+
yield agentOutput({
|
|
62
|
+
agentId: context.agentId,
|
|
63
|
+
content: 'Send a message to run Pi in this workspace — for example, "List the files here" or "Fix the failing test in src/foo.test.ts".',
|
|
64
|
+
threadId,
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const channelCwd = handlerCtx.state.channelDetails?.cwd;
|
|
69
|
+
const config = resolveConfig(context, channelCwd);
|
|
70
|
+
try {
|
|
71
|
+
const { session } = await getOrCreatePiSession({
|
|
72
|
+
config,
|
|
73
|
+
state: handlerCtx.state,
|
|
74
|
+
storage: context.storage,
|
|
75
|
+
});
|
|
76
|
+
for await (const chunk of streamPiPrompt(session, prompt, {
|
|
77
|
+
streaming: session.isStreaming,
|
|
78
|
+
})) {
|
|
79
|
+
yield agentOutput({
|
|
80
|
+
agentId: context.agentId,
|
|
81
|
+
content: chunk,
|
|
82
|
+
threadId,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
yield agentOutput({
|
|
88
|
+
agentId: context.agentId,
|
|
89
|
+
content: `**Pi error:** ${formatPiError(error)}`,
|
|
90
|
+
threadId,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
});
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { AuthStorage, createAgentSession, DefaultResourceLoader, getAgentDir, ModelRegistry, SessionManager, SettingsManager, } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { parseToolList } from './config.js';
|
|
3
|
+
import { persistPiState, readPersistedState } from './state.js';
|
|
4
|
+
const sessionCache = new Map();
|
|
5
|
+
const buildSessionKey = (state) => state.threadId ? `${state.channelId}:${state.threadId}` : state.channelId;
|
|
6
|
+
const resolveModel = async (config, modelRegistry) => {
|
|
7
|
+
if (config.provider && config.model) {
|
|
8
|
+
return modelRegistry.find(config.provider, config.model) ?? undefined;
|
|
9
|
+
}
|
|
10
|
+
const available = await modelRegistry.getAvailable();
|
|
11
|
+
return available[0];
|
|
12
|
+
};
|
|
13
|
+
export const getOrCreatePiSession = async (args) => {
|
|
14
|
+
const { config, state, storage } = args;
|
|
15
|
+
const sessionKey = buildSessionKey(state);
|
|
16
|
+
const cached = sessionCache.get(sessionKey);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
const cwd = config.cwd || process.cwd();
|
|
20
|
+
const agentDir = config.agentDir || getAgentDir();
|
|
21
|
+
const persisted = readPersistedState(state);
|
|
22
|
+
const authStorage = AuthStorage.create(`${agentDir}/auth.json`);
|
|
23
|
+
const modelRegistry = ModelRegistry.create(authStorage, `${agentDir}/models.json`);
|
|
24
|
+
const model = await resolveModel(config, modelRegistry);
|
|
25
|
+
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
26
|
+
const loader = new DefaultResourceLoader({
|
|
27
|
+
cwd,
|
|
28
|
+
agentDir,
|
|
29
|
+
settingsManager,
|
|
30
|
+
...(config.systemPrompt
|
|
31
|
+
? { systemPromptOverride: () => config.systemPrompt }
|
|
32
|
+
: {}),
|
|
33
|
+
});
|
|
34
|
+
await loader.reload();
|
|
35
|
+
const sessionManager = persisted.sessionFile
|
|
36
|
+
? SessionManager.open(persisted.sessionFile)
|
|
37
|
+
: SessionManager.create(cwd);
|
|
38
|
+
const { session, modelFallbackMessage } = await createAgentSession({
|
|
39
|
+
cwd,
|
|
40
|
+
agentDir,
|
|
41
|
+
authStorage,
|
|
42
|
+
modelRegistry,
|
|
43
|
+
model,
|
|
44
|
+
thinkingLevel: config.thinkingLevel,
|
|
45
|
+
tools: parseToolList(config.tools),
|
|
46
|
+
excludeTools: parseToolList(config.excludeTools),
|
|
47
|
+
noTools: config.noTools,
|
|
48
|
+
resourceLoader: loader,
|
|
49
|
+
sessionManager,
|
|
50
|
+
settingsManager,
|
|
51
|
+
});
|
|
52
|
+
if (modelFallbackMessage) {
|
|
53
|
+
console.warn(`[pi-plugin] ${modelFallbackMessage}`);
|
|
54
|
+
}
|
|
55
|
+
if (session.sessionFile && session.sessionFile !== persisted.sessionFile) {
|
|
56
|
+
await persistPiState(state, storage, { sessionFile: session.sessionFile });
|
|
57
|
+
}
|
|
58
|
+
const handle = { session, sessionKey };
|
|
59
|
+
sessionCache.set(sessionKey, handle);
|
|
60
|
+
return handle;
|
|
61
|
+
};
|
|
62
|
+
export const disposePiSession = (sessionKey) => {
|
|
63
|
+
const cached = sessionCache.get(sessionKey);
|
|
64
|
+
if (!cached)
|
|
65
|
+
return;
|
|
66
|
+
cached.session.dispose();
|
|
67
|
+
sessionCache.delete(sessionKey);
|
|
68
|
+
};
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const asRecord = (value) => value && typeof value === 'object' && !Array.isArray(value)
|
|
2
|
+
? value
|
|
3
|
+
: {};
|
|
4
|
+
export const readPersistedState = (state) => {
|
|
5
|
+
const source = state.threadDetails?.state ?? state.channelDetails?.state;
|
|
6
|
+
const record = asRecord(source);
|
|
7
|
+
return typeof record.sessionFile === 'string' ? { sessionFile: record.sessionFile } : {};
|
|
8
|
+
};
|
|
9
|
+
export const persistPiState = async (state, storage, patch) => {
|
|
10
|
+
if (state.threadId) {
|
|
11
|
+
await storage.patchThreadState({
|
|
12
|
+
channelId: state.channelId,
|
|
13
|
+
threadId: state.threadId,
|
|
14
|
+
state: patch,
|
|
15
|
+
});
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
await storage.patchChannelState({ channelId: state.channelId, state: patch });
|
|
19
|
+
};
|
package/dist/stream.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createStreamState, formatPiEvent } from './format.js';
|
|
2
|
+
/** Bridge Pi session events into an async generator of output chunks. */
|
|
3
|
+
export async function* streamPiPrompt(session, prompt, options) {
|
|
4
|
+
const state = createStreamState();
|
|
5
|
+
const pending = [];
|
|
6
|
+
let wake;
|
|
7
|
+
let finished = false;
|
|
8
|
+
let error;
|
|
9
|
+
const wakeUp = () => {
|
|
10
|
+
wake?.();
|
|
11
|
+
wake = undefined;
|
|
12
|
+
};
|
|
13
|
+
const unsubscribe = session.subscribe((event) => {
|
|
14
|
+
const chunk = formatPiEvent(event, state);
|
|
15
|
+
if (chunk) {
|
|
16
|
+
pending.push(chunk);
|
|
17
|
+
wakeUp();
|
|
18
|
+
}
|
|
19
|
+
if (event.type === 'agent_end') {
|
|
20
|
+
finished = true;
|
|
21
|
+
wakeUp();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
const run = (async () => {
|
|
25
|
+
try {
|
|
26
|
+
if (options?.streaming && session.isStreaming) {
|
|
27
|
+
await session.followUp(prompt);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
await session.prompt(prompt);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
finished = true;
|
|
38
|
+
wakeUp();
|
|
39
|
+
}
|
|
40
|
+
})();
|
|
41
|
+
try {
|
|
42
|
+
while (!finished || pending.length > 0) {
|
|
43
|
+
if (pending.length === 0) {
|
|
44
|
+
await new Promise((resolve) => {
|
|
45
|
+
wake = resolve;
|
|
46
|
+
});
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
yield pending.shift();
|
|
50
|
+
}
|
|
51
|
+
await run;
|
|
52
|
+
if (error)
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
unsubscribe();
|
|
57
|
+
}
|
|
58
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@meetopenbot/pi",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "OpenBot agent plugin powered by the Pi coding agent SDK.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.json",
|
|
18
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@earendil-works/pi-ai": "^0.79.1",
|
|
23
|
+
"@earendil-works/pi-coding-agent": "^0.79.1",
|
|
24
|
+
"@meetopenbot/plugin-sdk": "^0.1.2"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^25.9.2",
|
|
28
|
+
"typescript": "^6.0.3"
|
|
29
|
+
}
|
|
30
|
+
}
|