@koalaauto/agentchannel 0.0.0-stage → 3.0.0-rc.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/LICENSE +202 -0
- package/README.md +154 -2
- package/dist/src/adapters/claude.d.ts +65 -0
- package/dist/src/adapters/claude.js +243 -0
- package/dist/src/adapters/codex.d.ts +74 -0
- package/dist/src/adapters/codex.js +237 -0
- package/dist/src/adapters/fake.d.ts +65 -0
- package/dist/src/adapters/fake.js +105 -0
- package/dist/src/adapters/index.d.ts +11 -0
- package/dist/src/adapters/index.js +26 -0
- package/dist/src/adapters/paths.d.ts +35 -0
- package/dist/src/adapters/paths.js +113 -0
- package/dist/src/adapters/procgroup.d.ts +14 -0
- package/dist/src/adapters/procgroup.js +49 -0
- package/dist/src/adapters/types.d.ts +57 -0
- package/dist/src/adapters/types.js +1 -0
- package/dist/src/args.d.ts +14 -0
- package/dist/src/args.js +257 -0
- package/dist/src/attachments.d.ts +63 -0
- package/dist/src/attachments.js +227 -0
- package/dist/src/attention.d.ts +85 -0
- package/dist/src/attention.js +262 -0
- package/dist/src/avatars.d.ts +40 -0
- package/dist/src/avatars.js +90 -0
- package/dist/src/cli.d.ts +45 -0
- package/dist/src/cli.js +1822 -0
- package/dist/src/client.d.ts +520 -0
- package/dist/src/client.js +449 -0
- package/dist/src/config.d.ts +62 -0
- package/dist/src/config.js +121 -0
- package/dist/src/desknotify.d.ts +154 -0
- package/dist/src/desknotify.js +426 -0
- package/dist/src/doctor.d.ts +75 -0
- package/dist/src/doctor.js +375 -0
- package/dist/src/env.d.ts +31 -0
- package/dist/src/env.js +52 -0
- package/dist/src/import.d.ts +245 -0
- package/dist/src/import.js +1008 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.js +13 -0
- package/dist/src/invite.d.ts +103 -0
- package/dist/src/invite.js +229 -0
- package/dist/src/invite_person.d.ts +16 -0
- package/dist/src/invite_person.js +75 -0
- package/dist/src/keystore.d.ts +55 -0
- package/dist/src/keystore.js +108 -0
- package/dist/src/mcp.d.ts +62 -0
- package/dist/src/mcp.js +503 -0
- package/dist/src/names.d.ts +30 -0
- package/dist/src/names.js +108 -0
- package/dist/src/netproxy.d.ts +20 -0
- package/dist/src/netproxy.js +36 -0
- package/dist/src/onboard.d.ts +105 -0
- package/dist/src/onboard.js +270 -0
- package/dist/src/perm.d.ts +12 -0
- package/dist/src/perm.js +20 -0
- package/dist/src/prompts.d.ts +166 -0
- package/dist/src/prompts.js +409 -0
- package/dist/src/runner/runner.d.ts +212 -0
- package/dist/src/runner/runner.js +1148 -0
- package/dist/src/runner/state.d.ts +68 -0
- package/dist/src/runner/state.js +80 -0
- package/dist/src/runner/workspace.d.ts +70 -0
- package/dist/src/runner/workspace.js +265 -0
- package/dist/src/service.d.ts +29 -0
- package/dist/src/service.js +101 -0
- package/dist/src/version.d.ts +3 -0
- package/dist/src/version.js +3 -0
- package/dist/src/wiki.d.ts +74 -0
- package/dist/src/wiki.js +222 -0
- package/package.json +48 -4
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Client, WikiContext } from './client.js';
|
|
2
|
+
export interface WikiFigure {
|
|
3
|
+
id?: string;
|
|
4
|
+
metric: string;
|
|
5
|
+
value: string;
|
|
6
|
+
unit?: string;
|
|
7
|
+
env?: string;
|
|
8
|
+
as_of: string;
|
|
9
|
+
source: string;
|
|
10
|
+
/** Exempt from staleness (a fact that does not go out of date). */
|
|
11
|
+
stable?: string | boolean;
|
|
12
|
+
/** When the source said it (orders rows of the same as_of date). */
|
|
13
|
+
at?: string;
|
|
14
|
+
/** The id of the row this one corrects (a correction supersedes it directly). */
|
|
15
|
+
corrects?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface WikiFile {
|
|
18
|
+
slug: string;
|
|
19
|
+
title: string;
|
|
20
|
+
channel: string;
|
|
21
|
+
topics: string[];
|
|
22
|
+
version: number;
|
|
23
|
+
figures: WikiFigure[];
|
|
24
|
+
body: string;
|
|
25
|
+
/** Optional per-page change note (front matter `note:`); the push note is the default. */
|
|
26
|
+
note?: string;
|
|
27
|
+
}
|
|
28
|
+
/** Parse one wiki file (front matter + body). `name` is the file name (slug fallback). */
|
|
29
|
+
export declare function parseWikiFile(text: string, name?: string): WikiFile;
|
|
30
|
+
/** The inverse of parseWikiFile (what `ach memory pull` writes for a new page template). */
|
|
31
|
+
export declare function formatWikiFile(f: WikiFile): string;
|
|
32
|
+
/** `ach memory pull <dir>`: every page (or one channel's) as <slug>.md. Returns the files written. */
|
|
33
|
+
export declare function wikiPull(client: Client, dir: string, channel?: string): Promise<string[]>;
|
|
34
|
+
/** Reads <dir>/*.md (not starting with "_") into a push request. New figure rows are those without an id. */
|
|
35
|
+
export declare function readWikiDir(dir: string): {
|
|
36
|
+
pages: Record<string, unknown>[];
|
|
37
|
+
figures: Record<string, unknown>[];
|
|
38
|
+
};
|
|
39
|
+
/** `ach memory push <dir> --note`: only changed pages get a new version (the hub compares). Per-page `note:` in front matter overrides the push note for that page. */
|
|
40
|
+
export declare function wikiPush(client: Client, dir: string, note: string): Promise<{
|
|
41
|
+
held?: boolean;
|
|
42
|
+
id?: string;
|
|
43
|
+
conflicts?: {
|
|
44
|
+
reason: string;
|
|
45
|
+
}[];
|
|
46
|
+
message?: string;
|
|
47
|
+
pages: {
|
|
48
|
+
slug: string;
|
|
49
|
+
version: number;
|
|
50
|
+
note?: string;
|
|
51
|
+
}[];
|
|
52
|
+
figures: unknown[];
|
|
53
|
+
warnings?: {
|
|
54
|
+
slug: string;
|
|
55
|
+
line: number;
|
|
56
|
+
problem: string;
|
|
57
|
+
text: string;
|
|
58
|
+
}[];
|
|
59
|
+
}>;
|
|
60
|
+
/** ~1k tokens. The hub already trims to this; the runner enforces it again. */
|
|
61
|
+
export declare const WIKI_PROMPT_CHARS = 4000;
|
|
62
|
+
/**
|
|
63
|
+
* The Memory part of a worker prompt, layered and trimmed by priority (the hub
|
|
64
|
+
* already trims; the runner enforces it again): 1. decisions confirmed by
|
|
65
|
+
* people are trusted lines ("follow by default"); 2. current figures, 3.
|
|
66
|
+
* lessons, 4. a few lines of the team overview go into the untrusted data
|
|
67
|
+
* block, each with its usage wording (agent-recorded decisions: "verify first";
|
|
68
|
+
* figures: "as of … (env)"; overview: "background only"; old volatile items:
|
|
69
|
+
* "may be outdated, verify").
|
|
70
|
+
*/
|
|
71
|
+
export declare function wikiPromptParts(w: WikiContext | null | undefined, max?: number): {
|
|
72
|
+
trusted: string[];
|
|
73
|
+
data: Record<string, unknown> | null;
|
|
74
|
+
};
|
package/dist/src/wiki.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// Team Memory (formerly "wiki"; spec §21-revised): `ach memory pull/push` as
|
|
2
|
+
// Markdown files with YAML front matter, and the worker prompt's wiki block.
|
|
3
|
+
// The front matter is a small YAML subset (what the hub writes and what an
|
|
4
|
+
// agent is asked to add): scalars, inline lists, and a `figures:` list of maps.
|
|
5
|
+
import { mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { basename, join } from 'node:path';
|
|
7
|
+
const SLUG_RE = /^[a-z0-9][a-z0-9-]{0,79}$/;
|
|
8
|
+
function scalar(v) {
|
|
9
|
+
const s = v.trim();
|
|
10
|
+
if (s.startsWith('"')) {
|
|
11
|
+
try {
|
|
12
|
+
return String(JSON.parse(s));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return s.slice(1, -1);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (s.startsWith("'") && s.endsWith("'") && s.length >= 2)
|
|
19
|
+
return s.slice(1, -1).replace(/''/g, "'");
|
|
20
|
+
return s;
|
|
21
|
+
}
|
|
22
|
+
function inlineList(v) {
|
|
23
|
+
const s = v.trim();
|
|
24
|
+
if (!s.startsWith('['))
|
|
25
|
+
return s ? [scalar(s)] : [];
|
|
26
|
+
try {
|
|
27
|
+
const j = JSON.parse(s);
|
|
28
|
+
if (Array.isArray(j))
|
|
29
|
+
return j.map(String);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
/* plain YAML flow list */
|
|
33
|
+
}
|
|
34
|
+
return s
|
|
35
|
+
.slice(1, -1)
|
|
36
|
+
.split(',')
|
|
37
|
+
.map((x) => scalar(x))
|
|
38
|
+
.filter((x) => x.length);
|
|
39
|
+
}
|
|
40
|
+
/** Parse one wiki file (front matter + body). `name` is the file name (slug fallback). */
|
|
41
|
+
export function parseWikiFile(text, name = '') {
|
|
42
|
+
const src = String(text).replace(/\r\n/g, '\n');
|
|
43
|
+
const out = { slug: basename(name, '.md').toLowerCase(), title: '', channel: '', topics: [], version: 0, figures: [], body: src };
|
|
44
|
+
const m = /^---\n([\s\S]*?)\n---\n?/.exec(src);
|
|
45
|
+
if (!m)
|
|
46
|
+
return { ...out, title: out.slug };
|
|
47
|
+
out.body = src.slice(m[0].length);
|
|
48
|
+
let inFigures = false;
|
|
49
|
+
let cur = null;
|
|
50
|
+
const flush = () => {
|
|
51
|
+
if (cur)
|
|
52
|
+
out.figures.push(cur);
|
|
53
|
+
cur = null;
|
|
54
|
+
};
|
|
55
|
+
for (const line of m[1].split('\n')) {
|
|
56
|
+
if (!line.trim() || line.trim().startsWith('#'))
|
|
57
|
+
continue;
|
|
58
|
+
const item = /^\s+-\s+([A-Za-z_]+):\s*(.*)$/.exec(line);
|
|
59
|
+
const cont = /^\s{3,}([A-Za-z_]+):\s*(.*)$/.exec(line);
|
|
60
|
+
const top = /^([A-Za-z_]+):\s*(.*)$/.exec(line);
|
|
61
|
+
if (inFigures && item) {
|
|
62
|
+
flush();
|
|
63
|
+
cur = { [item[1]]: scalar(item[2]) };
|
|
64
|
+
}
|
|
65
|
+
else if (inFigures && cont && cur) {
|
|
66
|
+
cur[cont[1]] = scalar(cont[2]);
|
|
67
|
+
}
|
|
68
|
+
else if (top) {
|
|
69
|
+
flush();
|
|
70
|
+
inFigures = false;
|
|
71
|
+
const [, k, v] = top;
|
|
72
|
+
if (k === 'figures')
|
|
73
|
+
inFigures = v.trim() !== '[]';
|
|
74
|
+
else if (k === 'topics')
|
|
75
|
+
out.topics = inlineList(v);
|
|
76
|
+
else if (k === 'version')
|
|
77
|
+
out.version = parseInt(scalar(v), 10) || 0;
|
|
78
|
+
else if (k === 'slug')
|
|
79
|
+
out.slug = scalar(v).toLowerCase();
|
|
80
|
+
else if (k === 'title')
|
|
81
|
+
out.title = scalar(v);
|
|
82
|
+
else if (k === 'channel')
|
|
83
|
+
out.channel = scalar(v).replace(/^#/, '');
|
|
84
|
+
else if (k === 'note')
|
|
85
|
+
out.note = scalar(v);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
throw new Error(`${name || 'wiki file'}: cannot read front matter line: ${line}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
flush();
|
|
92
|
+
if (!out.title)
|
|
93
|
+
out.title = out.slug;
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
const q = (s) => JSON.stringify(String(s ?? ''));
|
|
97
|
+
/** The inverse of parseWikiFile (what `ach memory pull` writes for a new page template). */
|
|
98
|
+
export function formatWikiFile(f) {
|
|
99
|
+
const lines = ['---', `slug: ${f.slug}`, `title: ${q(f.title)}`, `channel: ${q(f.channel)}`, `topics: [${f.topics.map(q).join(', ')}]`, `version: ${f.version}`];
|
|
100
|
+
if (f.note !== undefined)
|
|
101
|
+
lines.push(`note: ${q(f.note)}`);
|
|
102
|
+
if (!f.figures.length)
|
|
103
|
+
lines.push('figures: []');
|
|
104
|
+
else {
|
|
105
|
+
lines.push('figures:');
|
|
106
|
+
for (const g of f.figures) {
|
|
107
|
+
const keys = ['id', 'metric', 'value', 'unit', 'env', 'as_of', 'source', 'stable', 'at', 'corrects'];
|
|
108
|
+
let first = true;
|
|
109
|
+
for (const k of keys) {
|
|
110
|
+
if (g[k] === undefined)
|
|
111
|
+
continue;
|
|
112
|
+
lines.push(`${first ? ' - ' : ' '}${k}: ${k === 'id' ? g[k] : q(g[k])}`);
|
|
113
|
+
first = false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
lines.push('---');
|
|
118
|
+
return `${lines.join('\n')}\n${f.body.replace(/^\n+/, '')}`;
|
|
119
|
+
}
|
|
120
|
+
/** `ach memory pull <dir>`: every page (or one channel's) as <slug>.md. Returns the files written. */
|
|
121
|
+
export async function wikiPull(client, dir, channel) {
|
|
122
|
+
const r = await client.wikiExport(channel);
|
|
123
|
+
mkdirSync(dir, { recursive: true });
|
|
124
|
+
const written = [];
|
|
125
|
+
for (const f of r.files ?? []) {
|
|
126
|
+
if (!SLUG_RE.test(String(f.slug)))
|
|
127
|
+
continue;
|
|
128
|
+
const p = join(dir, `${f.slug}.md`);
|
|
129
|
+
writeFileSync(p, f.content);
|
|
130
|
+
written.push(p);
|
|
131
|
+
}
|
|
132
|
+
// a template for a new topic page (files starting with "_" are never pushed)
|
|
133
|
+
writeFileSync(join(dir, '_new-page.md.example'), formatWikiFile({ slug: 'new-topic', title: 'New topic', channel: channel ?? '', topics: [], version: 0, figures: [], body: r.template ?? '' }));
|
|
134
|
+
return written;
|
|
135
|
+
}
|
|
136
|
+
/** Reads <dir>/*.md (not starting with "_") into a push request. New figure rows are those without an id. */
|
|
137
|
+
export function readWikiDir(dir) {
|
|
138
|
+
const pages = [];
|
|
139
|
+
const figures = [];
|
|
140
|
+
for (const name of readdirSync(dir).sort()) {
|
|
141
|
+
if (!name.endsWith('.md') || name.startsWith('_') || name.startsWith('.'))
|
|
142
|
+
continue;
|
|
143
|
+
const f = parseWikiFile(readFileSync(join(dir, name), 'utf8'), name);
|
|
144
|
+
if (!SLUG_RE.test(f.slug))
|
|
145
|
+
throw new Error(`${name}: slug "${f.slug}" must match ^[a-z0-9][a-z0-9-]{0,79}$`);
|
|
146
|
+
pages.push({ slug: f.slug, title: f.title, channel: f.channel, topics: f.topics, body: f.body, base_version: f.version, ...(f.note ? { note: f.note } : {}) });
|
|
147
|
+
for (const g of f.figures) {
|
|
148
|
+
if (g.id)
|
|
149
|
+
continue;
|
|
150
|
+
const stable = g.stable === true || g.stable === 'true';
|
|
151
|
+
figures.push({ topic: f.slug, metric: g.metric, value: g.value, unit: g.unit ?? '', env: g.env ?? '', as_of: g.as_of, source: g.source, ...(stable ? { stable: true } : {}), ...(g.at ? { at: g.at } : {}), ...(g.corrects ? { corrects: g.corrects } : {}) });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return { pages, figures };
|
|
155
|
+
}
|
|
156
|
+
/** `ach memory push <dir> --note`: only changed pages get a new version (the hub compares). Per-page `note:` in front matter overrides the push note for that page. */
|
|
157
|
+
export async function wikiPush(client, dir, note) {
|
|
158
|
+
const req = readWikiDir(dir);
|
|
159
|
+
return client.wikiPush({ ...req, note });
|
|
160
|
+
}
|
|
161
|
+
// ---------------------------------------------------------------- worker prompt
|
|
162
|
+
/** ~1k tokens. The hub already trims to this; the runner enforces it again. */
|
|
163
|
+
export const WIKI_PROMPT_CHARS = 4000;
|
|
164
|
+
/**
|
|
165
|
+
* The Memory part of a worker prompt, layered and trimmed by priority (the hub
|
|
166
|
+
* already trims; the runner enforces it again): 1. decisions confirmed by
|
|
167
|
+
* people are trusted lines ("follow by default"); 2. current figures, 3.
|
|
168
|
+
* lessons, 4. a few lines of the team overview go into the untrusted data
|
|
169
|
+
* block, each with its usage wording (agent-recorded decisions: "verify first";
|
|
170
|
+
* figures: "as of … (env)"; overview: "background only"; old volatile items:
|
|
171
|
+
* "may be outdated, verify").
|
|
172
|
+
*/
|
|
173
|
+
export function wikiPromptParts(w, max = WIKI_PROMPT_CHARS) {
|
|
174
|
+
if (!w)
|
|
175
|
+
return { trusted: [], data: null };
|
|
176
|
+
let left = max;
|
|
177
|
+
// decisions first but at most half the budget, so figures and lessons always get room
|
|
178
|
+
const fit = (list, size, cap = Infinity) => {
|
|
179
|
+
const out = [];
|
|
180
|
+
let used = 0;
|
|
181
|
+
for (const x of Array.isArray(list) ? list : []) {
|
|
182
|
+
const n = size(x);
|
|
183
|
+
if (n > left || used + n > cap)
|
|
184
|
+
continue;
|
|
185
|
+
left -= n;
|
|
186
|
+
used += n;
|
|
187
|
+
out.push(x);
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
};
|
|
191
|
+
const all = Array.isArray(w.decisions) ? w.decisions : [];
|
|
192
|
+
const human = fit(all.filter((d) => d.human_confirmed !== false), (d) => String(d.text).length + 80, max / 2);
|
|
193
|
+
const agentMade = fit(all.filter((d) => d.human_confirmed === false), (d) => String(d.text).length + 80, max / 2 - (max - left));
|
|
194
|
+
const figures = fit(w.figures, (f) => JSON.stringify(f).length, max * 0.3);
|
|
195
|
+
const lessons = fit(w.lessons, (l) => JSON.stringify(l).length);
|
|
196
|
+
const overview = fit(w.overview, (l) => String(l).length + 4);
|
|
197
|
+
const trusted = human.length
|
|
198
|
+
? [
|
|
199
|
+
'Team decisions confirmed by people (team Memory; follow them by default):',
|
|
200
|
+
...human.map((d) => `- ${JSON.stringify(String(d.text))}${d.decider ? ` (confirmed by ${JSON.stringify(String(d.decider))}` : ' ('}${d.as_of ? `, as of ${d.as_of}` : ''})${d.url ? ` ${d.url}` : ''}`),
|
|
201
|
+
'',
|
|
202
|
+
]
|
|
203
|
+
: [];
|
|
204
|
+
const use = {
|
|
205
|
+
decisions_recorded: 'recorded by an agent from evidence, not confirmed by a person: verify first',
|
|
206
|
+
figures: 'current value as of the date shown (env if given); stale: true means it may be outdated, verify',
|
|
207
|
+
lessons: 'agent-written lessons: verify first',
|
|
208
|
+
overview: 'team overview: background only',
|
|
209
|
+
};
|
|
210
|
+
const any = agentMade.length || figures.length || lessons.length || overview.length;
|
|
211
|
+
const data = any
|
|
212
|
+
? {
|
|
213
|
+
use,
|
|
214
|
+
...(agentMade.length ? { decisions_recorded: agentMade.map((d) => ({ text: d.text, as_of: d.as_of, url: d.url })) } : {}),
|
|
215
|
+
figures,
|
|
216
|
+
lessons,
|
|
217
|
+
...(overview.length ? { overview } : {}),
|
|
218
|
+
pages: (w.pages ?? []).map((p) => ({ slug: p.slug, title: p.title })),
|
|
219
|
+
}
|
|
220
|
+
: null;
|
|
221
|
+
return { trusted, data };
|
|
222
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koalaauto/agentchannel",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "3.0.0-rc.1",
|
|
4
|
+
"description": "Hand work between your team's agents (Claude Code, Codex) and follow it in one panel: the ach CLI, MCP server and runner for AgentChannel v2",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agentchannel",
|
|
7
|
+
"mcp",
|
|
8
|
+
"agents",
|
|
9
|
+
"claude-code",
|
|
10
|
+
"codex",
|
|
11
|
+
"handoff"
|
|
12
|
+
],
|
|
13
|
+
"license": "Apache-2.0",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"bin": {
|
|
16
|
+
"ach": "dist/src/cli.js"
|
|
17
|
+
},
|
|
18
|
+
"main": "dist/src/index.js",
|
|
19
|
+
"types": "dist/src/index.d.ts",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/src",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public",
|
|
30
|
+
"tag": "next"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.json",
|
|
34
|
+
"test": "npm run build && node scripts/test-sandbox.mjs node --test dist/test/*.test.js",
|
|
35
|
+
"test:e2e": "npm run build && node scripts/test-sandbox.mjs node --test --test-concurrency=4 test-e2e/*.test.mjs",
|
|
36
|
+
"prepack": "npm run build"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.312",
|
|
40
|
+
"@modelcontextprotocol/sdk": "1.32.2",
|
|
41
|
+
"@napi-rs/keyring": "2.1.2",
|
|
42
|
+
"@openai/codex-sdk": "0.165.0",
|
|
43
|
+
"undici": "7.30.1",
|
|
44
|
+
"zod": "4.6.7"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "22.19.1",
|
|
48
|
+
"typescript": "5.9.3"
|
|
49
|
+
}
|
|
50
|
+
}
|