@wenrwa/marketplace-cli 0.3.0 → 0.4.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/README.md +79 -7
- package/dist/__tests__/bridge-config-mcp.test.d.ts +1 -0
- package/dist/__tests__/bridge-config-mcp.test.js +117 -0
- package/dist/__tests__/bridge-server.test.d.ts +1 -0
- package/dist/__tests__/bridge-server.test.js +137 -0
- package/dist/__tests__/bridge-session-manager.test.d.ts +1 -0
- package/dist/__tests__/bridge-session-manager.test.js +104 -0
- package/dist/__tests__/config.test.js +3 -3
- package/dist/bridge/config.d.ts +11 -0
- package/dist/bridge/config.js +79 -0
- package/dist/bridge/mcpRegister.d.ts +28 -0
- package/dist/bridge/mcpRegister.js +35 -0
- package/dist/bridge/realPty.d.ts +2 -0
- package/dist/bridge/realPty.js +88 -0
- package/dist/bridge/server.d.ts +14 -0
- package/dist/bridge/server.js +182 -0
- package/dist/bridge/sessionManager.d.ts +42 -0
- package/dist/bridge/sessionManager.js +79 -0
- package/dist/commands/agent.js +98 -1
- package/dist/commands/apps.js +53 -0
- package/dist/commands/bounty.js +254 -0
- package/dist/commands/bridge.d.ts +3 -0
- package/dist/commands/bridge.js +122 -0
- package/dist/commands/credits.d.ts +2 -0
- package/dist/commands/credits.js +128 -0
- package/dist/commands/prompts.d.ts +2 -0
- package/dist/commands/prompts.js +136 -0
- package/dist/commands/repo.d.ts +2 -0
- package/dist/commands/repo.js +197 -0
- package/dist/commands/workspace.js +168 -18
- package/dist/index.js +8 -0
- package/dist/utils/client.d.ts +1 -0
- package/dist/utils/client.js +3 -0
- package/dist/utils/config.js +3 -3
- package/package.json +10 -15
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MCP_SERVER_NAME = void 0;
|
|
4
|
+
exports.redactToken = redactToken;
|
|
5
|
+
exports.registerMcpServer = registerMcpServer;
|
|
6
|
+
exports.MCP_SERVER_NAME = 'wenrwa-marketplace';
|
|
7
|
+
/** Connect Tokens must never appear in output — redact before printing. */
|
|
8
|
+
function redactToken(text) {
|
|
9
|
+
return text.replace(/wm_ct_[0-9a-f]+/g, 'wm_ct_…<redacted>');
|
|
10
|
+
}
|
|
11
|
+
async function registerMcpServer(opts) {
|
|
12
|
+
let listOutput;
|
|
13
|
+
try {
|
|
14
|
+
const list = await opts.run('claude', ['mcp', 'list']);
|
|
15
|
+
listOutput = list.stdout;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return { action: 'claude-not-found' };
|
|
19
|
+
}
|
|
20
|
+
const exists = listOutput.includes(exports.MCP_SERVER_NAME);
|
|
21
|
+
if (exists && !opts.forceUpdate) {
|
|
22
|
+
return { action: 'already-registered' };
|
|
23
|
+
}
|
|
24
|
+
if (exists && opts.forceUpdate) {
|
|
25
|
+
await opts.run('claude', ['mcp', 'remove', exports.MCP_SERVER_NAME]);
|
|
26
|
+
}
|
|
27
|
+
const args = [
|
|
28
|
+
'mcp', 'add', exports.MCP_SERVER_NAME,
|
|
29
|
+
'-e', `WENRWA_CONNECT_TOKEN=${opts.connectToken}`,
|
|
30
|
+
'-e', `MARKETPLACE_API_URL=${opts.apiUrl}`,
|
|
31
|
+
'--', 'npx', '@wenrwa/marketplace-mcp',
|
|
32
|
+
];
|
|
33
|
+
await opts.run('claude', args);
|
|
34
|
+
return { action: exists ? 're-registered' : 'registered', command: `claude ${args.join(' ')}` };
|
|
35
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.realPtyFactory = realPtyFactory;
|
|
37
|
+
/**
|
|
38
|
+
* bct-05 — real PTY factory (node-pty). Kept in its own module so every
|
|
39
|
+
* other bridge module stays testable with fakes; this file is the only one
|
|
40
|
+
* that touches node-pty and is exercised in bct-10's manual pass, not CI.
|
|
41
|
+
*/
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
/**
|
|
46
|
+
* npm's prebuild extraction can drop the execute bit on node-pty's
|
|
47
|
+
* darwin spawn-helper, which surfaces as an opaque "posix_spawnp failed."
|
|
48
|
+
* on every session open (found in the bct-10 pre-flight). Self-heal it.
|
|
49
|
+
*/
|
|
50
|
+
function ensureSpawnHelperExecutable() {
|
|
51
|
+
try {
|
|
52
|
+
const helper = path.join(path.dirname(require.resolve('node-pty/package.json')), 'prebuilds', `${os.platform()}-${os.arch()}`, 'spawn-helper');
|
|
53
|
+
if (fs.existsSync(helper)) {
|
|
54
|
+
const mode = fs.statSync(helper).mode;
|
|
55
|
+
if ((mode & 0o111) === 0)
|
|
56
|
+
fs.chmodSync(helper, mode | 0o755);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
/* best effort — spawn will surface any real failure */
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function realPtyFactory() {
|
|
64
|
+
// Lazy require: node-pty is a native module; loading it at import time
|
|
65
|
+
// would make every CLI command pay for it (or crash on a broken build).
|
|
66
|
+
ensureSpawnHelperExecutable();
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
68
|
+
const pty = require('node-pty');
|
|
69
|
+
const shell = process.env.SHELL || (os.platform() === 'win32' ? 'powershell.exe' : '/bin/bash');
|
|
70
|
+
return {
|
|
71
|
+
spawn: ({ cols, rows }) => {
|
|
72
|
+
const proc = pty.spawn(shell, [], {
|
|
73
|
+
name: 'xterm-256color',
|
|
74
|
+
cols,
|
|
75
|
+
rows,
|
|
76
|
+
cwd: os.homedir(),
|
|
77
|
+
env: process.env,
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
write: (data) => proc.write(data),
|
|
81
|
+
resize: (cols2, rows2) => proc.resize(cols2, rows2),
|
|
82
|
+
kill: () => proc.kill(),
|
|
83
|
+
onData: (cb) => proc.onData(cb),
|
|
84
|
+
onExit: (cb) => proc.onExit(({ exitCode }) => cb(exitCode)),
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SessionManager } from './sessionManager';
|
|
2
|
+
export interface BridgeServerOpts {
|
|
3
|
+
sessionToken: string;
|
|
4
|
+
allowedOrigin: string;
|
|
5
|
+
sessions: SessionManager;
|
|
6
|
+
/** 0 (default) = ephemeral port. */
|
|
7
|
+
port?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface BridgeServerHandle {
|
|
10
|
+
port: number;
|
|
11
|
+
address: string;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export declare function startBridgeServer(opts: BridgeServerOpts): Promise<BridgeServerHandle>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.startBridgeServer = startBridgeServer;
|
|
37
|
+
/**
|
|
38
|
+
* bct-05 — Terminal Bridge WebSocket server.
|
|
39
|
+
*
|
|
40
|
+
* Loopback-only by construction: the listener binds 127.0.0.1 and there is
|
|
41
|
+
* deliberately no host option. Two gates guard every connection:
|
|
42
|
+
* 1. Origin pinned to the arena origin (browsers always send it) — any
|
|
43
|
+
* other site, including LAN hosts, is closed with 4403.
|
|
44
|
+
* 2. A per-run session token in the first frame ({v:1, token}) — mismatch
|
|
45
|
+
* closes with 4401. The token lives in the discovery file that only the
|
|
46
|
+
* local user can read (config.ts).
|
|
47
|
+
*
|
|
48
|
+
* Input frames are routed through the SessionManager attachment handle of
|
|
49
|
+
* the sending connection, so a superseded (taken-over) connection's input is
|
|
50
|
+
* structurally inert — see sessionManager.ts and ADR-0028.
|
|
51
|
+
*/
|
|
52
|
+
const http = __importStar(require("http"));
|
|
53
|
+
const ws_1 = require("ws");
|
|
54
|
+
const PROTOCOL_VERSION = 1;
|
|
55
|
+
function send(ws, frame) {
|
|
56
|
+
if (ws.readyState === ws_1.WebSocket.OPEN)
|
|
57
|
+
ws.send(JSON.stringify(frame));
|
|
58
|
+
}
|
|
59
|
+
function startBridgeServer(opts) {
|
|
60
|
+
// HTTP layer exists for one endpoint: GET /discovery. Browsers cannot read
|
|
61
|
+
// the discovery FILE, so the pane fetches the session token from here —
|
|
62
|
+
// response is CORS-pinned to the arena origin, so no other website can
|
|
63
|
+
// read it. (Same-machine cross-user curl with a forged Origin remains
|
|
64
|
+
// possible; documented as out of scope for v1 in PROTOCOL.md.)
|
|
65
|
+
const httpServer = http.createServer((req, res) => {
|
|
66
|
+
if (req.method === 'GET' && req.url === '/discovery') {
|
|
67
|
+
if (req.headers.origin !== opts.allowedOrigin) {
|
|
68
|
+
res.writeHead(403).end();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
res.writeHead(200, {
|
|
72
|
+
'content-type': 'application/json',
|
|
73
|
+
'access-control-allow-origin': opts.allowedOrigin,
|
|
74
|
+
});
|
|
75
|
+
res.end(JSON.stringify({ v: PROTOCOL_VERSION, token: opts.sessionToken }));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
res.writeHead(404).end();
|
|
79
|
+
});
|
|
80
|
+
const wss = new ws_1.WebSocketServer({ server: httpServer });
|
|
81
|
+
wss.on('connection', (ws, req) => {
|
|
82
|
+
if (req.headers.origin !== opts.allowedOrigin) {
|
|
83
|
+
ws.close(4403, 'origin not allowed');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let authed = false;
|
|
87
|
+
const handles = new Map();
|
|
88
|
+
const attachTo = (sessionId) => {
|
|
89
|
+
const handle = opts.sessions.attach(sessionId, (frame) => {
|
|
90
|
+
if (frame.t === 'data')
|
|
91
|
+
send(ws, { t: 'data', sessionId, data: frame.data });
|
|
92
|
+
else if (frame.t === 'takeover')
|
|
93
|
+
send(ws, { t: 'takeover', sessionId });
|
|
94
|
+
});
|
|
95
|
+
handles.set(sessionId, handle);
|
|
96
|
+
return handle;
|
|
97
|
+
};
|
|
98
|
+
ws.on('message', (raw) => {
|
|
99
|
+
let frame;
|
|
100
|
+
try {
|
|
101
|
+
frame = JSON.parse(String(raw));
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
send(ws, { t: 'error', message: 'invalid frame' });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!authed) {
|
|
108
|
+
if ('v' in frame && frame.v === PROTOCOL_VERSION && frame.token === opts.sessionToken) {
|
|
109
|
+
authed = true;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
send(ws, { t: 'error', message: 'authentication required' });
|
|
113
|
+
ws.close(4401, 'unauthenticated');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!('t' in frame)) {
|
|
117
|
+
send(ws, { t: 'error', message: 'invalid frame' });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
switch (frame.t) {
|
|
122
|
+
case 'open': {
|
|
123
|
+
const sessionId = opts.sessions.open({ cols: frame.cols, rows: frame.rows });
|
|
124
|
+
attachTo(sessionId);
|
|
125
|
+
send(ws, { t: 'opened', sessionId });
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case 'attach': {
|
|
129
|
+
const handle = attachTo(frame.sessionId);
|
|
130
|
+
send(ws, { t: 'replay', sessionId: frame.sessionId, data: handle.replay });
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 'input':
|
|
134
|
+
handles.get(frame.sessionId)?.write(frame.data);
|
|
135
|
+
break;
|
|
136
|
+
case 'resize':
|
|
137
|
+
handles.get(frame.sessionId)?.resize(frame.cols, frame.rows);
|
|
138
|
+
break;
|
|
139
|
+
case 'detach':
|
|
140
|
+
handles.get(frame.sessionId)?.detach();
|
|
141
|
+
handles.delete(frame.sessionId);
|
|
142
|
+
break;
|
|
143
|
+
case 'close':
|
|
144
|
+
handles.delete(frame.sessionId);
|
|
145
|
+
opts.sessions.close(frame.sessionId);
|
|
146
|
+
break;
|
|
147
|
+
default:
|
|
148
|
+
send(ws, { t: 'error', message: 'unknown frame type' });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
send(ws, { t: 'error', message: err instanceof Error ? err.message : 'bridge error' });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
ws.on('close', () => {
|
|
156
|
+
for (const handle of handles.values())
|
|
157
|
+
handle.detach();
|
|
158
|
+
handles.clear();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
httpServer.once('error', reject);
|
|
163
|
+
httpServer.listen(opts.port ?? 0, '127.0.0.1', () => {
|
|
164
|
+
const addr = httpServer.address();
|
|
165
|
+
if (addr === null || typeof addr === 'string') {
|
|
166
|
+
reject(new Error('bridge server failed to bind'));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
resolve({
|
|
170
|
+
port: addr.port,
|
|
171
|
+
address: addr.address,
|
|
172
|
+
close: () => new Promise((res, rej) => {
|
|
173
|
+
for (const client of wss.clients)
|
|
174
|
+
client.terminate();
|
|
175
|
+
wss.close(() => {
|
|
176
|
+
httpServer.close((err) => (err ? rej(err) : res()));
|
|
177
|
+
});
|
|
178
|
+
}),
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface PtyLike {
|
|
2
|
+
write(data: string): void;
|
|
3
|
+
resize(cols: number, rows: number): void;
|
|
4
|
+
kill(): void;
|
|
5
|
+
onData(cb: (data: string) => void): void;
|
|
6
|
+
onExit(cb: (code: number) => void): void;
|
|
7
|
+
}
|
|
8
|
+
export interface PtyFactory {
|
|
9
|
+
spawn(opts: {
|
|
10
|
+
cols: number;
|
|
11
|
+
rows: number;
|
|
12
|
+
}): PtyLike;
|
|
13
|
+
}
|
|
14
|
+
export type SinkFrame = {
|
|
15
|
+
t: 'data';
|
|
16
|
+
data: string;
|
|
17
|
+
} | {
|
|
18
|
+
t: 'takeover';
|
|
19
|
+
};
|
|
20
|
+
export type Sink = (frame: SinkFrame) => void;
|
|
21
|
+
export interface AttachmentHandle {
|
|
22
|
+
/** Scrollback accumulated while detached (and before), replayed once. */
|
|
23
|
+
replay: string;
|
|
24
|
+
write(data: string): void;
|
|
25
|
+
resize(cols: number, rows: number): void;
|
|
26
|
+
detach(): void;
|
|
27
|
+
}
|
|
28
|
+
export declare class SessionManager {
|
|
29
|
+
private factory;
|
|
30
|
+
private sessions;
|
|
31
|
+
private scrollbackLimit;
|
|
32
|
+
constructor(factory: PtyFactory, opts?: {
|
|
33
|
+
scrollbackLimit?: number;
|
|
34
|
+
});
|
|
35
|
+
open(opts: {
|
|
36
|
+
cols: number;
|
|
37
|
+
rows: number;
|
|
38
|
+
}): string;
|
|
39
|
+
attach(sessionId: string, sink: Sink): AttachmentHandle;
|
|
40
|
+
close(sessionId: string): void;
|
|
41
|
+
list(): string[];
|
|
42
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SessionManager = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* bct-05 — Terminal Bridge session manager.
|
|
9
|
+
*
|
|
10
|
+
* Owns the PTYs on the user's machine. Transport-agnostic: the WebSocket
|
|
11
|
+
* server (server.ts) attaches sinks and drives writes exclusively through
|
|
12
|
+
* the AttachmentHandle returned by attach(). That handle is the ONLY write
|
|
13
|
+
* path to a PTY (ADR-0028: no platform surface may inject input) — a handle
|
|
14
|
+
* superseded by a later attach (last-attach-wins) or detached becomes inert.
|
|
15
|
+
*
|
|
16
|
+
* Sessions survive detach: output keeps accumulating in a bounded scrollback
|
|
17
|
+
* buffer and is replayed on the next attach, so a browser refresh never
|
|
18
|
+
* kills the user's Claude Code run.
|
|
19
|
+
*/
|
|
20
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
21
|
+
const DEFAULT_SCROLLBACK_LIMIT = 500_000; // ~500KB per session
|
|
22
|
+
class SessionManager {
|
|
23
|
+
factory;
|
|
24
|
+
sessions = new Map();
|
|
25
|
+
scrollbackLimit;
|
|
26
|
+
constructor(factory, opts = {}) {
|
|
27
|
+
this.factory = factory;
|
|
28
|
+
this.scrollbackLimit = opts.scrollbackLimit ?? DEFAULT_SCROLLBACK_LIMIT;
|
|
29
|
+
}
|
|
30
|
+
open(opts) {
|
|
31
|
+
const id = crypto_1.default.randomBytes(8).toString('hex');
|
|
32
|
+
const pty = this.factory.spawn(opts);
|
|
33
|
+
const session = { pty, scrollback: '', sink: null, attachEpoch: 0, exited: false };
|
|
34
|
+
pty.onData((data) => {
|
|
35
|
+
session.scrollback = (session.scrollback + data).slice(-this.scrollbackLimit);
|
|
36
|
+
session.sink?.({ t: 'data', data });
|
|
37
|
+
});
|
|
38
|
+
pty.onExit(() => {
|
|
39
|
+
session.exited = true;
|
|
40
|
+
session.sink = null;
|
|
41
|
+
this.sessions.delete(id);
|
|
42
|
+
});
|
|
43
|
+
this.sessions.set(id, session);
|
|
44
|
+
return id;
|
|
45
|
+
}
|
|
46
|
+
attach(sessionId, sink) {
|
|
47
|
+
const session = this.sessions.get(sessionId);
|
|
48
|
+
if (!session)
|
|
49
|
+
throw new Error(`Unknown session: ${sessionId}`);
|
|
50
|
+
// Last-attach-wins: notify and supersede any prior attachment.
|
|
51
|
+
session.sink?.({ t: 'takeover' });
|
|
52
|
+
session.attachEpoch += 1;
|
|
53
|
+
const epoch = session.attachEpoch;
|
|
54
|
+
session.sink = sink;
|
|
55
|
+
const live = () => this.sessions.get(sessionId) === session && session.attachEpoch === epoch;
|
|
56
|
+
return {
|
|
57
|
+
replay: session.scrollback,
|
|
58
|
+
write: (data) => { if (live())
|
|
59
|
+
session.pty.write(data); },
|
|
60
|
+
resize: (cols, rows) => { if (live())
|
|
61
|
+
session.pty.resize(cols, rows); },
|
|
62
|
+
detach: () => {
|
|
63
|
+
if (live())
|
|
64
|
+
session.sink = null;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
close(sessionId) {
|
|
69
|
+
const session = this.sessions.get(sessionId);
|
|
70
|
+
if (!session)
|
|
71
|
+
return;
|
|
72
|
+
session.pty.kill();
|
|
73
|
+
this.sessions.delete(sessionId);
|
|
74
|
+
}
|
|
75
|
+
list() {
|
|
76
|
+
return [...this.sessions.keys()];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.SessionManager = SessionManager;
|
package/dist/commands/agent.js
CHANGED
|
@@ -27,9 +27,10 @@ exports.agentCommand
|
|
|
27
27
|
console.log('No agents found.');
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
|
-
console.log((0, format_1.makeTable)(['Wallet', 'Name', 'Reputation', 'Capabilities', 'Status'], agents.map((a) => [
|
|
30
|
+
console.log((0, format_1.makeTable)(['Wallet', 'Name', 'Points', 'Reputation', 'Capabilities', 'Status'], agents.map((a) => [
|
|
31
31
|
(0, format_1.shorten)(a.wallet_pubkey),
|
|
32
32
|
a.name || '—',
|
|
33
|
+
String(a.points ?? 0),
|
|
33
34
|
a.weighted_reputation != null ? Number(a.weighted_reputation).toFixed(2) : '—',
|
|
34
35
|
(a.capabilities || []).slice(0, 3).join(', '),
|
|
35
36
|
a.status || 'active',
|
|
@@ -53,6 +54,7 @@ exports.agentCommand
|
|
|
53
54
|
console.log(` Description: ${agent.description || '—'}`);
|
|
54
55
|
console.log(` Capabilities: ${(agent.capabilities || []).join(', ')}`);
|
|
55
56
|
console.log(` Status: ${agent.status || 'active'}`);
|
|
57
|
+
console.log(` Points: ${agent.points ?? 0}`);
|
|
56
58
|
console.log(` Reputation: ${agent.weighted_reputation != null ? Number(agent.weighted_reputation).toFixed(2) : '—'}`);
|
|
57
59
|
console.log(` Completed: ${agent.completed_count ?? 0}`);
|
|
58
60
|
console.log(` Failed: ${agent.failed_count ?? 0}`);
|
|
@@ -111,3 +113,98 @@ exports.agentCommand
|
|
|
111
113
|
process.exit(0);
|
|
112
114
|
});
|
|
113
115
|
});
|
|
116
|
+
exports.agentCommand
|
|
117
|
+
.command('register')
|
|
118
|
+
.description('Register as an agent on the marketplace')
|
|
119
|
+
.requiredOption('--name <name>', 'Agent name')
|
|
120
|
+
.option('--model <model>', 'AI model identifier')
|
|
121
|
+
.option('--capabilities <caps>', 'Comma-separated capabilities')
|
|
122
|
+
.action(async (opts) => {
|
|
123
|
+
try {
|
|
124
|
+
const client = (0, client_1.getClient)();
|
|
125
|
+
const body = { name: opts.name };
|
|
126
|
+
if (opts.model)
|
|
127
|
+
body.model = opts.model;
|
|
128
|
+
if (opts.capabilities)
|
|
129
|
+
body.capabilities = opts.capabilities.split(',').map((c) => c.trim());
|
|
130
|
+
const data = await client.post('/agents/register', body);
|
|
131
|
+
(0, format_1.heading)('Agent Registered');
|
|
132
|
+
console.log(` Wallet: ${data.agent.wallet_pubkey}`);
|
|
133
|
+
console.log(` Name: ${data.agent.name}`);
|
|
134
|
+
if (data.agent.model)
|
|
135
|
+
console.log(` Model: ${data.agent.model}`);
|
|
136
|
+
if (data.agent.capabilities?.length)
|
|
137
|
+
console.log(` Caps: ${data.agent.capabilities.join(', ')}`);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
(0, format_1.error)(err.message);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
exports.agentCommand
|
|
144
|
+
.command('update')
|
|
145
|
+
.description('Update your agent profile')
|
|
146
|
+
.option('--name <name>', 'New display name')
|
|
147
|
+
.option('--model <model>', 'New model identifier')
|
|
148
|
+
.option('--description <desc>', 'New description')
|
|
149
|
+
.option('--capabilities <caps>', 'Comma-separated capabilities (replaces existing)')
|
|
150
|
+
.action(async (opts) => {
|
|
151
|
+
try {
|
|
152
|
+
const body = {};
|
|
153
|
+
if (opts.name)
|
|
154
|
+
body.name = opts.name;
|
|
155
|
+
if (opts.model)
|
|
156
|
+
body.model = opts.model;
|
|
157
|
+
if (opts.description)
|
|
158
|
+
body.description = opts.description;
|
|
159
|
+
if (opts.capabilities)
|
|
160
|
+
body.capabilities = opts.capabilities.split(',').map((c) => c.trim());
|
|
161
|
+
if (Object.keys(body).length === 0) {
|
|
162
|
+
(0, format_1.error)('Provide at least one field to update (--name, --model, --description, --capabilities)');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const client = (0, client_1.getClient)();
|
|
166
|
+
const data = await client.patch('/agents/me', body);
|
|
167
|
+
(0, format_1.heading)('Agent Updated');
|
|
168
|
+
console.log(` Name: ${data.agent.name}`);
|
|
169
|
+
if (data.agent.model)
|
|
170
|
+
console.log(` Model: ${data.agent.model}`);
|
|
171
|
+
if (data.agent.description)
|
|
172
|
+
console.log(` Desc: ${data.agent.description}`);
|
|
173
|
+
if (data.agent.capabilities?.length)
|
|
174
|
+
console.log(` Caps: ${data.agent.capabilities.join(', ')}`);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
(0, format_1.error)(err.message);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
exports.agentCommand
|
|
181
|
+
.command('github-status')
|
|
182
|
+
.description('Check if your GitHub account is connected')
|
|
183
|
+
.action(async () => {
|
|
184
|
+
try {
|
|
185
|
+
const client = (0, client_1.getClient)();
|
|
186
|
+
const data = await client.get('/auth/github/status');
|
|
187
|
+
if (data.connected) {
|
|
188
|
+
console.log(chalk_1.default.green('Connected') + ` — @${data.username}`);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
console.log(chalk_1.default.dim('Not connected'));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
(0, format_1.error)(err.message);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
exports.agentCommand
|
|
199
|
+
.command('github-disconnect')
|
|
200
|
+
.description('Disconnect your GitHub account from the marketplace')
|
|
201
|
+
.action(async () => {
|
|
202
|
+
try {
|
|
203
|
+
const client = (0, client_1.getClient)();
|
|
204
|
+
await client.del('/me/github');
|
|
205
|
+
console.log(chalk_1.default.green('GitHub account disconnected.'));
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
(0, format_1.error)(err.message);
|
|
209
|
+
}
|
|
210
|
+
});
|
package/dist/commands/apps.js
CHANGED
|
@@ -198,3 +198,56 @@ exports.appsCommand
|
|
|
198
198
|
(0, format_1.error)(err.message);
|
|
199
199
|
}
|
|
200
200
|
});
|
|
201
|
+
exports.appsCommand
|
|
202
|
+
.command('hosting')
|
|
203
|
+
.description('Show hosting subscription status for all apps')
|
|
204
|
+
.action(async () => {
|
|
205
|
+
try {
|
|
206
|
+
const client = (0, client_1.getClient)();
|
|
207
|
+
const data = await client.get('/me/hosting-subscriptions');
|
|
208
|
+
const subs = data.subscriptions || [];
|
|
209
|
+
if (subs.length === 0) {
|
|
210
|
+
console.log('No hosting subscriptions. Deploy an app first.');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
console.log((0, format_1.makeTable)(['Project', 'Slug', 'Status', 'Credits/mo', 'Next Billing', 'URL'], subs.map((s) => [
|
|
214
|
+
s.project_title?.slice(0, 20) || (0, format_1.shorten)(s.project_id),
|
|
215
|
+
s.project_slug ? `${s.project_slug}.wenrwa.app` : '—',
|
|
216
|
+
(0, format_1.statusColor)(s.status),
|
|
217
|
+
s.credits_per_month?.toLocaleString() || '—',
|
|
218
|
+
s.next_billing_at ? (0, format_1.formatTimeAgo)(s.next_billing_at) : '—',
|
|
219
|
+
s.deployed_url || '—',
|
|
220
|
+
])));
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
(0, format_1.error)(err.message);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
exports.appsCommand
|
|
227
|
+
.command('reactivate')
|
|
228
|
+
.argument('<project-id>', 'Project ID to reactivate')
|
|
229
|
+
.description('Reactivate an archived hosted app')
|
|
230
|
+
.action(async (projectId) => {
|
|
231
|
+
try {
|
|
232
|
+
const client = (0, client_1.getClient)();
|
|
233
|
+
await client.post(`/projects/${projectId}/reactivate`);
|
|
234
|
+
console.log(chalk_1.default.green(`Hosting reactivated for project ${projectId}`));
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
(0, format_1.error)(err.message);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
exports.appsCommand
|
|
241
|
+
.command('cancel-hosting')
|
|
242
|
+
.argument('<project-id>', 'Project ID to cancel hosting for')
|
|
243
|
+
.description('Cancel hosting for a hosted app')
|
|
244
|
+
.action(async (projectId) => {
|
|
245
|
+
try {
|
|
246
|
+
const client = (0, client_1.getClient)();
|
|
247
|
+
await client.post(`/projects/${projectId}/cancel-hosting`);
|
|
248
|
+
console.log(chalk_1.default.green(`Hosting cancelled for project ${projectId}`));
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
(0, format_1.error)(err.message);
|
|
252
|
+
}
|
|
253
|
+
});
|