@agentconnect/cli 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.
@@ -0,0 +1,66 @@
1
+ import path from 'path';
2
+ import { promises as fs } from 'fs';
3
+ import { readJson, writeJson, fileExists } from './fs-utils.js';
4
+ import { hashFile } from './zip.js';
5
+ import { readManifestFromZip } from './manifest.js';
6
+ function compareSemver(a, b) {
7
+ const parse = (v) => v
8
+ .split('-')[0]
9
+ .split('.')
10
+ .map((n) => Number(n));
11
+ const pa = parse(a);
12
+ const pb = parse(b);
13
+ for (let i = 0; i < 3; i += 1) {
14
+ const diff = (pa[i] || 0) - (pb[i] || 0);
15
+ if (diff !== 0)
16
+ return diff;
17
+ }
18
+ return 0;
19
+ }
20
+ export async function publishPackage({ zipPath, signaturePath, registryPath, manifest, }) {
21
+ const resolvedManifest = manifest || (await readManifestFromZip(zipPath));
22
+ const appId = resolvedManifest.id;
23
+ const version = resolvedManifest.version;
24
+ if (!appId || !version) {
25
+ throw new Error('Manifest must include id and version.');
26
+ }
27
+ const entryDir = path.join(registryPath, 'apps', appId, version);
28
+ await fs.mkdir(entryDir, { recursive: true });
29
+ const targetZip = path.join(entryDir, 'app.zip');
30
+ await fs.copyFile(zipPath, targetZip);
31
+ const manifestPath = path.join(entryDir, 'manifest.json');
32
+ await writeJson(manifestPath, resolvedManifest);
33
+ let signatureOut = null;
34
+ if (signaturePath) {
35
+ signatureOut = path.join(entryDir, 'signature.json');
36
+ await fs.copyFile(signaturePath, signatureOut);
37
+ }
38
+ const hash = await hashFile(zipPath);
39
+ const indexPath = path.join(registryPath, 'index.json');
40
+ const index = (await fileExists(indexPath))
41
+ ? await readJson(indexPath)
42
+ : { apps: {} };
43
+ if (!index.apps)
44
+ index.apps = {};
45
+ if (!index.apps[appId]) {
46
+ index.apps[appId] = { latest: version, versions: {} };
47
+ }
48
+ index.apps[appId].versions[version] = {
49
+ path: path.relative(registryPath, targetZip).replace(/\\/g, '/'),
50
+ manifest: resolvedManifest,
51
+ signature: signatureOut
52
+ ? {
53
+ algorithm: 'unknown',
54
+ publicKey: '',
55
+ signature: '',
56
+ }
57
+ : undefined,
58
+ hash,
59
+ };
60
+ const currentLatest = index.apps[appId].latest;
61
+ if (!currentLatest || compareSemver(version, currentLatest) > 0) {
62
+ index.apps[appId].latest = version;
63
+ }
64
+ await writeJson(indexPath, index);
65
+ return { appId, version, hash, targetZip, manifestPath, signaturePath: signatureOut, indexPath };
66
+ }
@@ -0,0 +1,225 @@
1
+ import type { ChildProcess } from 'child_process';
2
+ export type RpcId = string | number;
3
+ export interface RpcRequest {
4
+ jsonrpc: '2.0';
5
+ id: RpcId;
6
+ method: string;
7
+ params?: Record<string, unknown>;
8
+ }
9
+ export interface RpcSuccess {
10
+ jsonrpc: '2.0';
11
+ id: RpcId;
12
+ result: Record<string, unknown>;
13
+ }
14
+ export type RpcErrorCode = 'AC_ERR_UNAUTHORIZED' | 'AC_ERR_NOT_INSTALLED' | 'AC_ERR_INVALID_ARGS' | 'AC_ERR_UNSUPPORTED' | 'AC_ERR_INTERNAL' | 'AC_ERR_FS_READ' | 'AC_ERR_FS_WRITE' | 'AC_ERR_FS_LIST' | 'AC_ERR_FS_STAT' | 'AC_ERR_PROCESS' | 'AC_ERR_NET' | 'AC_ERR_BACKEND';
15
+ export interface RpcError {
16
+ jsonrpc: '2.0';
17
+ id: RpcId;
18
+ error: {
19
+ code: RpcErrorCode;
20
+ message: string;
21
+ data?: Record<string, unknown>;
22
+ };
23
+ }
24
+ export type RpcResponse = RpcSuccess | RpcError;
25
+ export type ProviderId = 'claude' | 'codex' | 'local';
26
+ export interface AppManifestEntry {
27
+ type: 'web';
28
+ path: string;
29
+ devUrl?: string;
30
+ }
31
+ export interface AppManifestBackendHealthcheck {
32
+ type: 'http';
33
+ path: string;
34
+ }
35
+ export interface AppManifestBackend {
36
+ runtime: 'node';
37
+ command: string;
38
+ args?: string[];
39
+ cwd?: string;
40
+ env?: Record<string, string>;
41
+ healthcheck?: AppManifestBackendHealthcheck;
42
+ }
43
+ export interface AppManifestAuthor {
44
+ name?: string;
45
+ email?: string;
46
+ url?: string;
47
+ }
48
+ export interface AppManifest {
49
+ id: string;
50
+ name: string;
51
+ version: string;
52
+ description?: string;
53
+ entry: AppManifestEntry;
54
+ backend?: AppManifestBackend;
55
+ capabilities?: string[];
56
+ providers?: ProviderId[];
57
+ models?: {
58
+ default?: string;
59
+ };
60
+ icon?: string;
61
+ repo?: string;
62
+ homepage?: string;
63
+ license?: string;
64
+ author?: AppManifestAuthor;
65
+ keywords?: string[];
66
+ }
67
+ export interface RegistryAppVersion {
68
+ path: string;
69
+ manifest: AppManifest;
70
+ signature?: {
71
+ algorithm: string;
72
+ publicKey: string;
73
+ signature: string;
74
+ };
75
+ hash?: string;
76
+ }
77
+ export interface RegistryApp {
78
+ versions: Record<string, RegistryAppVersion>;
79
+ latest: string;
80
+ }
81
+ export interface RegistryIndex {
82
+ apps: Record<string, RegistryApp>;
83
+ }
84
+ export interface ProviderStatus {
85
+ installed: boolean;
86
+ loggedIn: boolean;
87
+ version?: string;
88
+ }
89
+ export interface ProviderInfo extends ProviderStatus {
90
+ id: ProviderId;
91
+ name: string;
92
+ }
93
+ export interface ReasoningEffort {
94
+ id: string;
95
+ label: string;
96
+ }
97
+ export interface ModelInfo {
98
+ id: string;
99
+ provider: ProviderId;
100
+ displayName?: string;
101
+ contextWindow?: number;
102
+ maxOutputTokens?: number;
103
+ reasoningEfforts?: ReasoningEffort[];
104
+ defaultReasoningEffort?: string;
105
+ }
106
+ export interface ProviderLoginOptions {
107
+ baseUrl?: string;
108
+ apiKey?: string;
109
+ model?: string;
110
+ models?: string[];
111
+ loginMethod?: 'claudeai' | 'console';
112
+ loginExperience?: 'embedded' | 'terminal';
113
+ }
114
+ export interface SessionEvent {
115
+ type: 'delta' | 'final' | 'usage' | 'status' | 'error' | 'raw_line' | 'provider_event';
116
+ text?: string;
117
+ message?: string;
118
+ line?: string;
119
+ provider?: ProviderId;
120
+ event?: Record<string, unknown>;
121
+ providerSessionId?: string | null;
122
+ inputTokens?: number;
123
+ outputTokens?: number;
124
+ }
125
+ export interface RunPromptOptions {
126
+ prompt: string;
127
+ resumeSessionId?: string | null;
128
+ model?: string;
129
+ reasoningEffort?: string | null;
130
+ repoRoot?: string;
131
+ cwd?: string;
132
+ signal?: AbortSignal;
133
+ onEvent: (event: SessionEvent) => void;
134
+ }
135
+ export interface RunPromptResult {
136
+ sessionId: string | null;
137
+ }
138
+ export type PackageManagerType = 'bun' | 'pnpm' | 'npm' | 'brew' | 'script' | 'unknown';
139
+ export interface InstallResult {
140
+ installed: boolean;
141
+ version?: string;
142
+ packageManager?: PackageManagerType;
143
+ }
144
+ export interface Provider {
145
+ id: ProviderId;
146
+ name: string;
147
+ ensureInstalled(): Promise<InstallResult>;
148
+ status(): Promise<ProviderStatus>;
149
+ login(options?: ProviderLoginOptions): Promise<{
150
+ loggedIn: boolean;
151
+ }>;
152
+ logout(): Promise<void>;
153
+ listModels?(): Promise<ModelInfo[]>;
154
+ runPrompt(options: RunPromptOptions): Promise<RunPromptResult>;
155
+ }
156
+ export interface SessionState {
157
+ id: string;
158
+ providerId: ProviderId;
159
+ model: string;
160
+ providerSessionId: string | null;
161
+ reasoningEffort: string | null;
162
+ cwd?: string;
163
+ repoRoot?: string;
164
+ }
165
+ export interface BackendState {
166
+ status: 'starting' | 'running' | 'stopped' | 'error' | 'disabled';
167
+ pid?: number;
168
+ url?: string;
169
+ }
170
+ export interface ProcessHandle {
171
+ pid: number;
172
+ process: ChildProcess;
173
+ }
174
+ export interface ValidationError {
175
+ path: string;
176
+ message: string;
177
+ }
178
+ export interface ValidationWarning {
179
+ path: string;
180
+ message: string;
181
+ }
182
+ export interface RegistryValidationResult {
183
+ valid: boolean;
184
+ errors: ValidationError[];
185
+ warnings: ValidationWarning[];
186
+ }
187
+ export interface ObservedCapabilities {
188
+ requested: string[];
189
+ observed: string[];
190
+ }
191
+ export interface ObservedTracker {
192
+ record(capability: string): void;
193
+ list(): string[];
194
+ snapshot(): ObservedCapabilities;
195
+ flush(): void;
196
+ }
197
+ export interface CommandResult {
198
+ stdout: string;
199
+ stderr: string;
200
+ code: number | null;
201
+ }
202
+ export interface LineParser {
203
+ push(chunk: Buffer | string): void;
204
+ end(): void;
205
+ }
206
+ export interface CollectFilesOptions {
207
+ ignoreNames?: string[];
208
+ ignorePaths?: string[];
209
+ }
210
+ export interface FileEntry {
211
+ name: string;
212
+ path: string;
213
+ type: 'file' | 'dir' | 'link' | 'other';
214
+ size: number;
215
+ }
216
+ export interface FileStat {
217
+ type: 'file' | 'dir' | 'link' | 'other';
218
+ size: number;
219
+ mtime: string;
220
+ }
221
+ export interface SignatureData {
222
+ algorithm: string;
223
+ publicKey: string;
224
+ signature: string;
225
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/zip.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ export declare function hashFile(filePath: string): Promise<string>;
2
+ export interface ZipDirectoryOptions {
3
+ inputDir: string;
4
+ outputPath: string;
5
+ ignoreNames?: string[];
6
+ ignorePaths?: string[];
7
+ }
8
+ export declare function zipDirectory({ inputDir, outputPath, ignoreNames, ignorePaths, }: ZipDirectoryOptions): Promise<void>;
9
+ export declare function readZipEntry(zipPath: string, entryName: string): Promise<string | null>;
package/dist/zip.js ADDED
@@ -0,0 +1,71 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { createHash } from 'crypto';
4
+ import yazl from 'yazl';
5
+ import yauzl from 'yauzl';
6
+ import { collectFiles } from './fs-utils.js';
7
+ export async function hashFile(filePath) {
8
+ return new Promise((resolve, reject) => {
9
+ const hash = createHash('sha256');
10
+ const stream = fs.createReadStream(filePath);
11
+ stream.on('data', (chunk) => hash.update(chunk));
12
+ stream.on('error', reject);
13
+ stream.on('end', () => resolve(hash.digest('hex')));
14
+ });
15
+ }
16
+ export async function zipDirectory({ inputDir, outputPath, ignoreNames = [], ignorePaths = [], }) {
17
+ await fs.promises.mkdir(path.dirname(outputPath), { recursive: true });
18
+ const zipfile = new yazl.ZipFile();
19
+ const files = await collectFiles(inputDir, { ignoreNames, ignorePaths });
20
+ for (const file of files) {
21
+ zipfile.addFile(file.fullPath, file.rel);
22
+ }
23
+ return new Promise((resolve, reject) => {
24
+ const outStream = fs.createWriteStream(outputPath);
25
+ zipfile.outputStream.pipe(outStream);
26
+ outStream.on('close', () => resolve());
27
+ outStream.on('error', reject);
28
+ zipfile.end();
29
+ });
30
+ }
31
+ export async function readZipEntry(zipPath, entryName) {
32
+ return new Promise((resolve, reject) => {
33
+ yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
34
+ if (err || !zipfile) {
35
+ reject(err || new Error('Unable to open zip'));
36
+ return;
37
+ }
38
+ let found = false;
39
+ zipfile.readEntry();
40
+ zipfile.on('entry', (entry) => {
41
+ if (entry.fileName === entryName || entry.fileName.endsWith(`/${entryName}`)) {
42
+ found = true;
43
+ zipfile.openReadStream(entry, (streamErr, stream) => {
44
+ if (streamErr || !stream) {
45
+ zipfile.close();
46
+ reject(streamErr || new Error('Unable to read zip entry'));
47
+ return;
48
+ }
49
+ const chunks = [];
50
+ stream.on('data', (chunk) => chunks.push(chunk));
51
+ stream.on('end', () => {
52
+ const content = Buffer.concat(chunks).toString('utf8');
53
+ zipfile.close();
54
+ resolve(content);
55
+ });
56
+ stream.on('error', reject);
57
+ });
58
+ }
59
+ else {
60
+ zipfile.readEntry();
61
+ }
62
+ });
63
+ zipfile.on('end', () => {
64
+ if (!found) {
65
+ zipfile.close();
66
+ resolve(null);
67
+ }
68
+ });
69
+ });
70
+ });
71
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@agentconnect/cli",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "homepage": "https://github.com/rayzhudev/agent-connect",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/rayzhudev/agent-connect.git",
10
+ "directory": "packages/cli"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/rayzhudev/agent-connect/issues"
14
+ },
15
+ "bin": {
16
+ "agentconnect": "./dist/index.js"
17
+ },
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc && esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/index.js --banner:js='#!/usr/bin/env node' --packages=external",
25
+ "dev": "tsc --watch"
26
+ },
27
+ "dependencies": {
28
+ "ajv": "^8.17.1",
29
+ "node-pty": "^1.1.0",
30
+ "ws": "^8.17.0",
31
+ "yauzl": "^2.10.0",
32
+ "yazl": "^2.5.1"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20",
36
+ "@types/ws": "^8",
37
+ "@types/yauzl": "^2",
38
+ "@types/yazl": "^2",
39
+ "typescript": "^5.6.2",
40
+ "esbuild": "^0.23.0"
41
+ },
42
+ "engines": {
43
+ "node": ">=20"
44
+ }
45
+ }