@wenrwa/marketplace-cli 0.4.3 → 0.4.4
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/dist/__tests__/bridge-repo-checkout.test.d.ts +1 -0
- package/dist/__tests__/bridge-repo-checkout.test.js +53 -0
- package/dist/__tests__/bridge-server.test.js +21 -0
- package/dist/bridge/realPty.js +2 -2
- package/dist/bridge/repoCheckout.d.ts +24 -0
- package/dist/bridge/repoCheckout.js +34 -0
- package/dist/bridge/server.d.ts +3 -0
- package/dist/bridge/server.js +13 -3
- package/dist/bridge/sessionManager.d.ts +2 -0
- package/dist/commands/bridge.js +15 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const repoCheckout_1 = require("../bridge/repoCheckout");
|
|
4
|
+
const BASE = '/Users/t/wenrwa-workspaces';
|
|
5
|
+
const makeDeps = (existing = [], cloneFails = false) => {
|
|
6
|
+
const runs = [];
|
|
7
|
+
return {
|
|
8
|
+
runs,
|
|
9
|
+
deps: {
|
|
10
|
+
baseDir: BASE,
|
|
11
|
+
exists: (p) => existing.includes(p),
|
|
12
|
+
mkdir: (_p) => { },
|
|
13
|
+
run: async (cmd, args) => {
|
|
14
|
+
runs.push([cmd, ...args]);
|
|
15
|
+
if (cloneFails)
|
|
16
|
+
throw new Error('clone failed');
|
|
17
|
+
return { stdout: '', code: 0 };
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
describe('parseRepoFullName', () => {
|
|
23
|
+
it('handles https, trailing .git, and bare owner/repo', () => {
|
|
24
|
+
expect((0, repoCheckout_1.parseRepoFullName)('https://github.com/BunnyDAO/CBP')).toBe('BunnyDAO/CBP');
|
|
25
|
+
expect((0, repoCheckout_1.parseRepoFullName)('https://github.com/BunnyDAO/CBP.git')).toBe('BunnyDAO/CBP');
|
|
26
|
+
expect((0, repoCheckout_1.parseRepoFullName)('BunnyDAO/CBP')).toBe('BunnyDAO/CBP');
|
|
27
|
+
expect((0, repoCheckout_1.parseRepoFullName)('not a repo url')).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('ensureRepoCheckout', () => {
|
|
31
|
+
it('reuses an existing checkout without running git', async () => {
|
|
32
|
+
const { deps, runs } = makeDeps([`${BASE}/BunnyDAO/CBP`]);
|
|
33
|
+
const result = await (0, repoCheckout_1.ensureRepoCheckout)('https://github.com/BunnyDAO/CBP', deps);
|
|
34
|
+
expect(result).toEqual({ path: `${BASE}/BunnyDAO/CBP`, action: 'existing' });
|
|
35
|
+
expect(runs).toHaveLength(0);
|
|
36
|
+
});
|
|
37
|
+
it('clones a missing repo into the workspace base dir', async () => {
|
|
38
|
+
const { deps, runs } = makeDeps();
|
|
39
|
+
const result = await (0, repoCheckout_1.ensureRepoCheckout)('https://github.com/BunnyDAO/CBP.git', deps);
|
|
40
|
+
expect(result).toEqual({ path: `${BASE}/BunnyDAO/CBP`, action: 'cloned' });
|
|
41
|
+
expect(runs[0].join(' ')).toBe(`git clone https://github.com/BunnyDAO/CBP.git ${BASE}/BunnyDAO/CBP`);
|
|
42
|
+
});
|
|
43
|
+
it('reports failure without throwing when the clone fails (no local git auth, etc.)', async () => {
|
|
44
|
+
const { deps } = makeDeps([], true);
|
|
45
|
+
const result = await (0, repoCheckout_1.ensureRepoCheckout)('https://github.com/Private/Repo', deps);
|
|
46
|
+
expect(result.action).toBe('failed');
|
|
47
|
+
});
|
|
48
|
+
it('rejects unparseable repo urls', async () => {
|
|
49
|
+
const { deps } = makeDeps();
|
|
50
|
+
const result = await (0, repoCheckout_1.ensureRepoCheckout)('garbage', deps);
|
|
51
|
+
expect(result.action).toBe('failed');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -140,6 +140,27 @@ describe('bridge WebSocket server', () => {
|
|
|
140
140
|
});
|
|
141
141
|
expect(bad.status).toBe(403);
|
|
142
142
|
});
|
|
143
|
+
it('threads repoUrl through resolveCwd into the PTY spawn and the opened frame', async () => {
|
|
144
|
+
await handle.close();
|
|
145
|
+
const cwds = [];
|
|
146
|
+
const factory = { spawn: (o) => { cwds.push(o.cwd); const p = new FakePty(); ptys.push(p); return p; } };
|
|
147
|
+
handle = await (0, server_1.startBridgeServer)({
|
|
148
|
+
sessionToken: TOKEN,
|
|
149
|
+
allowedOrigin: ORIGIN,
|
|
150
|
+
sessions: new sessionManager_1.SessionManager(factory),
|
|
151
|
+
resolveCwd: async (repoUrl) => (repoUrl.includes('CBP') ? '/tmp/wenrwa-workspaces/BunnyDAO/CBP' : null),
|
|
152
|
+
});
|
|
153
|
+
const { ws, q } = await openAndAuth(handle.port);
|
|
154
|
+
ws.send(JSON.stringify({ t: 'open', cols: 80, rows: 24, repoUrl: 'https://github.com/BunnyDAO/CBP' }));
|
|
155
|
+
const opened = await q.next();
|
|
156
|
+
expect(opened.cwd).toBe('/tmp/wenrwa-workspaces/BunnyDAO/CBP');
|
|
157
|
+
expect(cwds[0]).toBe('/tmp/wenrwa-workspaces/BunnyDAO/CBP');
|
|
158
|
+
ws.send(JSON.stringify({ t: 'open', cols: 80, rows: 24, repoUrl: 'https://github.com/Other/Repo' }));
|
|
159
|
+
const fallback = await q.next();
|
|
160
|
+
expect(fallback.cwd).toBeUndefined();
|
|
161
|
+
expect(fallback.note).toContain('home directory');
|
|
162
|
+
ws.close();
|
|
163
|
+
});
|
|
143
164
|
it('unauthenticated frames are rejected', async () => {
|
|
144
165
|
const ws = connect(handle.port);
|
|
145
166
|
await new Promise((r) => ws.on('open', r));
|
package/dist/bridge/realPty.js
CHANGED
|
@@ -68,12 +68,12 @@ function realPtyFactory() {
|
|
|
68
68
|
const pty = require('node-pty');
|
|
69
69
|
const shell = process.env.SHELL || (os.platform() === 'win32' ? 'powershell.exe' : '/bin/bash');
|
|
70
70
|
return {
|
|
71
|
-
spawn: ({ cols, rows }) => {
|
|
71
|
+
spawn: ({ cols, rows, cwd }) => {
|
|
72
72
|
const proc = pty.spawn(shell, [], {
|
|
73
73
|
name: 'xterm-256color',
|
|
74
74
|
cols,
|
|
75
75
|
rows,
|
|
76
|
-
cwd: os.homedir(),
|
|
76
|
+
cwd: cwd || os.homedir(),
|
|
77
77
|
env: process.env,
|
|
78
78
|
});
|
|
79
79
|
return {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bct UX round 2 (2026-07-30) — workspace-scoped terminals.
|
|
3
|
+
*
|
|
4
|
+
* The arena's mental model is workspace = repo, so the Terminal pane sends
|
|
5
|
+
* the workspace's GitHub URL with `open`, and the bridge lands the shell in
|
|
6
|
+
* a local checkout of that repo (cloning on first use with the USER'S own
|
|
7
|
+
* git credentials — the platform never touches repo auth). Checkouts live
|
|
8
|
+
* under ~/wenrwa-workspaces/<owner>/<repo>.
|
|
9
|
+
*/
|
|
10
|
+
export interface RepoCheckoutDeps {
|
|
11
|
+
baseDir: string;
|
|
12
|
+
exists: (path: string) => boolean;
|
|
13
|
+
mkdir: (path: string) => void;
|
|
14
|
+
run: (cmd: string, args: string[]) => Promise<{
|
|
15
|
+
stdout: string;
|
|
16
|
+
code: number;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
export interface CheckoutResult {
|
|
20
|
+
path?: string;
|
|
21
|
+
action: 'existing' | 'cloned' | 'failed';
|
|
22
|
+
}
|
|
23
|
+
export declare function parseRepoFullName(repoUrl: string): string | null;
|
|
24
|
+
export declare function ensureRepoCheckout(repoUrl: string, deps: RepoCheckoutDeps): Promise<CheckoutResult>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* bct UX round 2 (2026-07-30) — workspace-scoped terminals.
|
|
4
|
+
*
|
|
5
|
+
* The arena's mental model is workspace = repo, so the Terminal pane sends
|
|
6
|
+
* the workspace's GitHub URL with `open`, and the bridge lands the shell in
|
|
7
|
+
* a local checkout of that repo (cloning on first use with the USER'S own
|
|
8
|
+
* git credentials — the platform never touches repo auth). Checkouts live
|
|
9
|
+
* under ~/wenrwa-workspaces/<owner>/<repo>.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.parseRepoFullName = parseRepoFullName;
|
|
13
|
+
exports.ensureRepoCheckout = ensureRepoCheckout;
|
|
14
|
+
function parseRepoFullName(repoUrl) {
|
|
15
|
+
const m = repoUrl.match(/github\.com[/:]([\w.-]+)\/([\w.-]+?)(?:\.git)?(?:[/?#].*)?$/) ||
|
|
16
|
+
repoUrl.match(/^([\w.-]+)\/([\w.-]+)$/);
|
|
17
|
+
return m ? `${m[1]}/${m[2]}` : null;
|
|
18
|
+
}
|
|
19
|
+
async function ensureRepoCheckout(repoUrl, deps) {
|
|
20
|
+
const fullName = parseRepoFullName(repoUrl);
|
|
21
|
+
if (!fullName)
|
|
22
|
+
return { action: 'failed' };
|
|
23
|
+
const path = `${deps.baseDir}/${fullName}`;
|
|
24
|
+
if (deps.exists(path))
|
|
25
|
+
return { path, action: 'existing' };
|
|
26
|
+
try {
|
|
27
|
+
deps.mkdir(path.slice(0, path.lastIndexOf('/')));
|
|
28
|
+
await deps.run('git', ['clone', repoUrl, path]);
|
|
29
|
+
return { path, action: 'cloned' };
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return { action: 'failed' };
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/bridge/server.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ export interface BridgeServerOpts {
|
|
|
5
5
|
sessions: SessionManager;
|
|
6
6
|
/** 0 (default) = ephemeral port. */
|
|
7
7
|
port?: number;
|
|
8
|
+
/** Workspace-scoped terminals: resolve a repo URL to a local checkout dir
|
|
9
|
+
* (cloned on first use). Absent/null result → shell opens in $HOME. */
|
|
10
|
+
resolveCwd?: (repoUrl: string) => Promise<string | null>;
|
|
8
11
|
}
|
|
9
12
|
export interface BridgeServerHandle {
|
|
10
13
|
port: number;
|
package/dist/bridge/server.js
CHANGED
|
@@ -137,9 +137,19 @@ function startBridgeServer(opts) {
|
|
|
137
137
|
try {
|
|
138
138
|
switch (frame.t) {
|
|
139
139
|
case 'open': {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
const openSession = (cwd, note) => {
|
|
141
|
+
const sessionId = opts.sessions.open({ cols: frame.cols, rows: frame.rows, cwd });
|
|
142
|
+
attachTo(sessionId);
|
|
143
|
+
send(ws, { t: 'opened', sessionId, ...(cwd ? { cwd } : {}), ...(note ? { note } : {}) });
|
|
144
|
+
};
|
|
145
|
+
if (frame.repoUrl && opts.resolveCwd) {
|
|
146
|
+
opts.resolveCwd(frame.repoUrl)
|
|
147
|
+
.then((cwd) => openSession(cwd ?? undefined, cwd ? undefined : 'repo checkout unavailable — opened in home directory'))
|
|
148
|
+
.catch(() => openSession(undefined, 'repo checkout failed — opened in home directory'));
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
openSession();
|
|
152
|
+
}
|
|
143
153
|
break;
|
|
144
154
|
}
|
|
145
155
|
case 'attach': {
|
|
@@ -9,6 +9,7 @@ export interface PtyFactory {
|
|
|
9
9
|
spawn(opts: {
|
|
10
10
|
cols: number;
|
|
11
11
|
rows: number;
|
|
12
|
+
cwd?: string;
|
|
12
13
|
}): PtyLike;
|
|
13
14
|
}
|
|
14
15
|
export type SinkFrame = {
|
|
@@ -35,6 +36,7 @@ export declare class SessionManager {
|
|
|
35
36
|
open(opts: {
|
|
36
37
|
cols: number;
|
|
37
38
|
rows: number;
|
|
39
|
+
cwd?: string;
|
|
38
40
|
}): string;
|
|
39
41
|
attach(sessionId: string, sink: Sink): AttachmentHandle;
|
|
40
42
|
close(sessionId: string): void;
|
package/dist/commands/bridge.js
CHANGED
|
@@ -57,6 +57,7 @@ const realPty_1 = require("../bridge/realPty");
|
|
|
57
57
|
const config_1 = require("../bridge/config");
|
|
58
58
|
const mcpRegister_1 = require("../bridge/mcpRegister");
|
|
59
59
|
const autostart_1 = require("../bridge/autostart");
|
|
60
|
+
const repoCheckout_1 = require("../bridge/repoCheckout");
|
|
60
61
|
const config_2 = require("../utils/config");
|
|
61
62
|
exports.DEFAULT_BRIDGE_PORT = 8377;
|
|
62
63
|
const DEFAULT_ORIGIN = 'https://app.hiveop.io';
|
|
@@ -120,11 +121,25 @@ exports.bridgeCommand = new commander_1.Command('bridge')
|
|
|
120
121
|
console.log(chalk_1.default.yellow('! claude binary not found — bridge will run, but install Claude Code for MCP world-wiring'));
|
|
121
122
|
// 3. Serve.
|
|
122
123
|
const sessionToken = crypto.randomBytes(16).toString('hex');
|
|
124
|
+
const workspacesBase = `${os.homedir()}/wenrwa-workspaces`;
|
|
123
125
|
const handle = await (0, server_1.startBridgeServer)({
|
|
124
126
|
sessionToken,
|
|
125
127
|
allowedOrigin: opts.origin,
|
|
126
128
|
sessions: new sessionManager_1.SessionManager((0, realPty_1.realPtyFactory)()),
|
|
127
129
|
port: Number(opts.port),
|
|
130
|
+
// Workspace-scoped shells: the pane sends the workspace's GitHub URL;
|
|
131
|
+
// we land the PTY in a local checkout (user's own git credentials).
|
|
132
|
+
resolveCwd: async (repoUrl) => {
|
|
133
|
+
const result = await (0, repoCheckout_1.ensureRepoCheckout)(repoUrl, {
|
|
134
|
+
baseDir: workspacesBase,
|
|
135
|
+
exists: (p) => fs.existsSync(p),
|
|
136
|
+
mkdir: (p) => fs.mkdirSync(p, { recursive: true }),
|
|
137
|
+
run: execRunner,
|
|
138
|
+
});
|
|
139
|
+
if (result.action === 'cloned')
|
|
140
|
+
console.log(chalk_1.default.green(`✓ Cloned ${repoUrl} → ${result.path}`));
|
|
141
|
+
return result.path ?? null;
|
|
142
|
+
},
|
|
128
143
|
});
|
|
129
144
|
(0, config_1.writeDiscoveryFile)({ port: handle.port, sessionToken });
|
|
130
145
|
fs.appendFileSync((0, config_1.bridgeLogPath)(), `${new Date().toISOString()} bridge up on ${handle.address}:${handle.port}\n`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenrwa/marketplace-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "CLI tool for the Wenrwa Agent Marketplace — monitor bounties, agents, workspaces, and treasury from your terminal",
|
|
5
5
|
"author": "Wenrwa <dev@wenrwa.com>",
|
|
6
6
|
"license": "MIT",
|