@wenrwa/marketplace-cli 0.4.2 → 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-autostart.test.d.ts +1 -0
- package/dist/__tests__/bridge-autostart.test.js +68 -0
- 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/autostart.d.ts +35 -0
- package/dist/bridge/autostart.js +69 -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 +58 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const autostart_1 = require("../bridge/autostart");
|
|
4
|
+
const HOME = '/Users/testuser';
|
|
5
|
+
const makeDeps = (platform = 'darwin', hasConfig = true) => {
|
|
6
|
+
const writes = [];
|
|
7
|
+
const removed = [];
|
|
8
|
+
const runs = [];
|
|
9
|
+
return {
|
|
10
|
+
writes, removed, runs,
|
|
11
|
+
deps: {
|
|
12
|
+
platform,
|
|
13
|
+
homedir: () => HOME,
|
|
14
|
+
hasBridgeConfig: () => hasConfig,
|
|
15
|
+
mkdir: (_p) => { },
|
|
16
|
+
writeFile: (path, content, mode) => { writes.push({ path, content, mode }); },
|
|
17
|
+
removeFile: (path) => { removed.push(path); },
|
|
18
|
+
run: async (cmd, args) => { runs.push([cmd, ...args]); return { stdout: '', code: 0 }; },
|
|
19
|
+
uid: () => 501,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
describe('launchAgentPlist', () => {
|
|
24
|
+
it('runs the bridge through a login shell (PATH-safe) with KeepAlive + RunAtLoad', () => {
|
|
25
|
+
const xml = (0, autostart_1.launchAgentPlist)({ logPath: `${HOME}/.wen-market/bridge.log` });
|
|
26
|
+
expect(xml).toContain(`<string>${autostart_1.AUTOSTART_LABEL}</string>`);
|
|
27
|
+
expect(xml).toContain('<string>/bin/zsh</string>');
|
|
28
|
+
expect(xml).toContain('npx -y @wenrwa/marketplace-cli bridge');
|
|
29
|
+
expect(xml).toContain('<key>RunAtLoad</key>');
|
|
30
|
+
expect(xml).toContain('<key>KeepAlive</key>');
|
|
31
|
+
expect(xml).toContain(`${HOME}/.wen-market/bridge.log`);
|
|
32
|
+
// The Connect Token must never be baked into the plist — the bridge
|
|
33
|
+
// reads it from its 0600 config at start.
|
|
34
|
+
expect(xml).not.toContain('wm_ct_');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('installAutostart', () => {
|
|
38
|
+
it('writes the LaunchAgent and bootstraps it via launchctl', async () => {
|
|
39
|
+
const { deps, writes, runs } = makeDeps();
|
|
40
|
+
const result = await (0, autostart_1.installAutostart)(deps);
|
|
41
|
+
expect(result.action).toBe('installed');
|
|
42
|
+
expect(writes).toHaveLength(1);
|
|
43
|
+
expect(writes[0].path).toBe(`${HOME}/Library/LaunchAgents/${autostart_1.AUTOSTART_LABEL}.plist`);
|
|
44
|
+
const flat = runs.map((r) => r.join(' ')).join('\n');
|
|
45
|
+
expect(flat).toContain('launchctl');
|
|
46
|
+
expect(flat).toContain(`gui/501/${autostart_1.AUTOSTART_LABEL}`);
|
|
47
|
+
});
|
|
48
|
+
it('refuses when no Connect Token is stored yet (bridge could never start)', async () => {
|
|
49
|
+
const { deps } = makeDeps('darwin', false);
|
|
50
|
+
const result = await (0, autostart_1.installAutostart)(deps);
|
|
51
|
+
expect(result.action).toBe('no-token');
|
|
52
|
+
});
|
|
53
|
+
it('reports unsupported on non-macOS platforms', async () => {
|
|
54
|
+
const { deps, writes } = makeDeps('linux');
|
|
55
|
+
const result = await (0, autostart_1.installAutostart)(deps);
|
|
56
|
+
expect(result.action).toBe('unsupported');
|
|
57
|
+
expect(writes).toHaveLength(0);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('uninstallAutostart', () => {
|
|
61
|
+
it('boots the agent out and removes the plist', async () => {
|
|
62
|
+
const { deps, removed, runs } = makeDeps();
|
|
63
|
+
const result = await (0, autostart_1.uninstallAutostart)(deps);
|
|
64
|
+
expect(result.action).toBe('uninstalled');
|
|
65
|
+
expect(removed[0]).toBe(`${HOME}/Library/LaunchAgents/${autostart_1.AUTOSTART_LABEL}.plist`);
|
|
66
|
+
expect(runs.map((r) => r.join(' ')).join('\n')).toContain('bootout');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -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));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge autostart (bct follow-up, 2026-07-30) — `wen-market bridge install`.
|
|
3
|
+
*
|
|
4
|
+
* Turns the one-liner into one-time-EVER setup: a macOS LaunchAgent starts
|
|
5
|
+
* the bridge at login and keeps it alive, so the Terminal pane always finds
|
|
6
|
+
* it. The plist runs through a login shell so the user's PATH (nvm/homebrew
|
|
7
|
+
* node) resolves; the Connect Token is NEVER baked into the plist — the
|
|
8
|
+
* bridge reads it from its 0600 config file at start.
|
|
9
|
+
*
|
|
10
|
+
* Windows/Linux: not yet supported — the command says so instead of
|
|
11
|
+
* guessing at Task Scheduler/systemd (add per real demand).
|
|
12
|
+
*/
|
|
13
|
+
export declare const AUTOSTART_LABEL = "com.wenrwa.bridge";
|
|
14
|
+
export interface AutostartDeps {
|
|
15
|
+
platform: string;
|
|
16
|
+
homedir: () => string;
|
|
17
|
+
hasBridgeConfig: () => boolean;
|
|
18
|
+
mkdir: (path: string) => void;
|
|
19
|
+
writeFile: (path: string, content: string, mode?: number) => void;
|
|
20
|
+
removeFile: (path: string) => void;
|
|
21
|
+
run: (cmd: string, args: string[]) => Promise<{
|
|
22
|
+
stdout: string;
|
|
23
|
+
code: number;
|
|
24
|
+
}>;
|
|
25
|
+
uid: () => number;
|
|
26
|
+
}
|
|
27
|
+
export interface AutostartResult {
|
|
28
|
+
action: 'installed' | 'uninstalled' | 'unsupported' | 'no-token';
|
|
29
|
+
plistPath?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function launchAgentPlist(opts: {
|
|
32
|
+
logPath: string;
|
|
33
|
+
}): string;
|
|
34
|
+
export declare function installAutostart(deps: AutostartDeps): Promise<AutostartResult>;
|
|
35
|
+
export declare function uninstallAutostart(deps: AutostartDeps): Promise<AutostartResult>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Bridge autostart (bct follow-up, 2026-07-30) — `wen-market bridge install`.
|
|
4
|
+
*
|
|
5
|
+
* Turns the one-liner into one-time-EVER setup: a macOS LaunchAgent starts
|
|
6
|
+
* the bridge at login and keeps it alive, so the Terminal pane always finds
|
|
7
|
+
* it. The plist runs through a login shell so the user's PATH (nvm/homebrew
|
|
8
|
+
* node) resolves; the Connect Token is NEVER baked into the plist — the
|
|
9
|
+
* bridge reads it from its 0600 config file at start.
|
|
10
|
+
*
|
|
11
|
+
* Windows/Linux: not yet supported — the command says so instead of
|
|
12
|
+
* guessing at Task Scheduler/systemd (add per real demand).
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.AUTOSTART_LABEL = void 0;
|
|
16
|
+
exports.launchAgentPlist = launchAgentPlist;
|
|
17
|
+
exports.installAutostart = installAutostart;
|
|
18
|
+
exports.uninstallAutostart = uninstallAutostart;
|
|
19
|
+
exports.AUTOSTART_LABEL = 'com.wenrwa.bridge';
|
|
20
|
+
function launchAgentPlist(opts) {
|
|
21
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
22
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
23
|
+
<plist version="1.0">
|
|
24
|
+
<dict>
|
|
25
|
+
<key>Label</key>
|
|
26
|
+
<string>${exports.AUTOSTART_LABEL}</string>
|
|
27
|
+
<key>ProgramArguments</key>
|
|
28
|
+
<array>
|
|
29
|
+
<string>/bin/zsh</string>
|
|
30
|
+
<string>-lc</string>
|
|
31
|
+
<string>exec npx -y @wenrwa/marketplace-cli bridge</string>
|
|
32
|
+
</array>
|
|
33
|
+
<key>RunAtLoad</key>
|
|
34
|
+
<true/>
|
|
35
|
+
<key>KeepAlive</key>
|
|
36
|
+
<true/>
|
|
37
|
+
<key>StandardOutPath</key>
|
|
38
|
+
<string>${opts.logPath}</string>
|
|
39
|
+
<key>StandardErrorPath</key>
|
|
40
|
+
<string>${opts.logPath}</string>
|
|
41
|
+
</dict>
|
|
42
|
+
</plist>
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
45
|
+
function plistPath(deps) {
|
|
46
|
+
return `${deps.homedir()}/Library/LaunchAgents/${exports.AUTOSTART_LABEL}.plist`;
|
|
47
|
+
}
|
|
48
|
+
async function installAutostart(deps) {
|
|
49
|
+
if (deps.platform !== 'darwin')
|
|
50
|
+
return { action: 'unsupported' };
|
|
51
|
+
if (!deps.hasBridgeConfig())
|
|
52
|
+
return { action: 'no-token' };
|
|
53
|
+
const path = plistPath(deps);
|
|
54
|
+
deps.mkdir(`${deps.homedir()}/Library/LaunchAgents`);
|
|
55
|
+
deps.writeFile(path, launchAgentPlist({ logPath: `${deps.homedir()}/.wen-market/bridge.log` }), 0o644);
|
|
56
|
+
const domain = `gui/${deps.uid()}`;
|
|
57
|
+
// Re-installs: bootout first (ignore failure when not loaded), then bootstrap.
|
|
58
|
+
await deps.run('launchctl', ['bootout', `${domain}/${exports.AUTOSTART_LABEL}`]).catch(() => { });
|
|
59
|
+
await deps.run('launchctl', ['bootstrap', domain, path]);
|
|
60
|
+
return { action: 'installed', plistPath: path };
|
|
61
|
+
}
|
|
62
|
+
async function uninstallAutostart(deps) {
|
|
63
|
+
if (deps.platform !== 'darwin')
|
|
64
|
+
return { action: 'unsupported' };
|
|
65
|
+
const path = plistPath(deps);
|
|
66
|
+
await deps.run('launchctl', ['bootout', `gui/${deps.uid()}/${exports.AUTOSTART_LABEL}`]).catch(() => { });
|
|
67
|
+
deps.removeFile(path);
|
|
68
|
+
return { action: 'uninstalled', plistPath: path };
|
|
69
|
+
}
|
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
|
@@ -47,6 +47,7 @@ exports.bridgeCommand = exports.DEFAULT_BRIDGE_PORT = void 0;
|
|
|
47
47
|
*/
|
|
48
48
|
const crypto = __importStar(require("crypto"));
|
|
49
49
|
const fs = __importStar(require("fs"));
|
|
50
|
+
const os = __importStar(require("os"));
|
|
50
51
|
const child_process_1 = require("child_process");
|
|
51
52
|
const commander_1 = require("commander");
|
|
52
53
|
const chalk_1 = __importDefault(require("chalk"));
|
|
@@ -55,6 +56,8 @@ const server_1 = require("../bridge/server");
|
|
|
55
56
|
const realPty_1 = require("../bridge/realPty");
|
|
56
57
|
const config_1 = require("../bridge/config");
|
|
57
58
|
const mcpRegister_1 = require("../bridge/mcpRegister");
|
|
59
|
+
const autostart_1 = require("../bridge/autostart");
|
|
60
|
+
const repoCheckout_1 = require("../bridge/repoCheckout");
|
|
58
61
|
const config_2 = require("../utils/config");
|
|
59
62
|
exports.DEFAULT_BRIDGE_PORT = 8377;
|
|
60
63
|
const DEFAULT_ORIGIN = 'https://app.hiveop.io';
|
|
@@ -72,6 +75,16 @@ const execRunner = (cmd, args) => new Promise((resolve, reject) => {
|
|
|
72
75
|
resolve({ stdout: stdout ?? '', code: 0 });
|
|
73
76
|
});
|
|
74
77
|
});
|
|
78
|
+
const realAutostartDeps = () => ({
|
|
79
|
+
platform: os.platform(),
|
|
80
|
+
homedir: os.homedir,
|
|
81
|
+
hasBridgeConfig: () => (0, config_1.loadBridgeConfig)() !== null,
|
|
82
|
+
mkdir: (p) => fs.mkdirSync(p, { recursive: true }),
|
|
83
|
+
writeFile: (p, content, mode) => fs.writeFileSync(p, content, mode !== undefined ? { mode } : {}),
|
|
84
|
+
removeFile: (p) => fs.rmSync(p, { force: true }),
|
|
85
|
+
run: execRunner,
|
|
86
|
+
uid: () => process.getuid?.() ?? 501,
|
|
87
|
+
});
|
|
75
88
|
exports.bridgeCommand = new commander_1.Command('bridge')
|
|
76
89
|
.description('Run the local Terminal Bridge for the arena Terminal Pane (BYO Claude Code)')
|
|
77
90
|
.option('--token <connectToken>', 'Connect Token from arena settings (stored on first run, never logged)')
|
|
@@ -108,15 +121,60 @@ exports.bridgeCommand = new commander_1.Command('bridge')
|
|
|
108
121
|
console.log(chalk_1.default.yellow('! claude binary not found — bridge will run, but install Claude Code for MCP world-wiring'));
|
|
109
122
|
// 3. Serve.
|
|
110
123
|
const sessionToken = crypto.randomBytes(16).toString('hex');
|
|
124
|
+
const workspacesBase = `${os.homedir()}/wenrwa-workspaces`;
|
|
111
125
|
const handle = await (0, server_1.startBridgeServer)({
|
|
112
126
|
sessionToken,
|
|
113
127
|
allowedOrigin: opts.origin,
|
|
114
128
|
sessions: new sessionManager_1.SessionManager((0, realPty_1.realPtyFactory)()),
|
|
115
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
|
+
},
|
|
116
143
|
});
|
|
117
144
|
(0, config_1.writeDiscoveryFile)({ port: handle.port, sessionToken });
|
|
118
145
|
fs.appendFileSync((0, config_1.bridgeLogPath)(), `${new Date().toISOString()} bridge up on ${handle.address}:${handle.port}\n`);
|
|
119
146
|
console.log(chalk_1.default.cyan(`Terminal Bridge listening on ${handle.address}:${handle.port} (origin: ${opts.origin})`));
|
|
120
147
|
console.log(chalk_1.default.dim('Open your arena workspace and add a Terminal pane — it will connect automatically.'));
|
|
121
148
|
console.log(chalk_1.default.dim('Ctrl+C stops the bridge (terminal sessions end with it).'));
|
|
149
|
+
console.log(chalk_1.default.dim('Tip: `wen-market bridge install` makes this start automatically at login.'));
|
|
150
|
+
});
|
|
151
|
+
exports.bridgeCommand
|
|
152
|
+
.command('install')
|
|
153
|
+
.description('Start the bridge automatically at login (macOS LaunchAgent)')
|
|
154
|
+
.action(async () => {
|
|
155
|
+
const result = await (0, autostart_1.installAutostart)(realAutostartDeps());
|
|
156
|
+
if (result.action === 'installed') {
|
|
157
|
+
console.log(chalk_1.default.green(`✓ Bridge installed as a login service (${result.plistPath})`));
|
|
158
|
+
console.log(chalk_1.default.dim('It is running now and will start at every login. `wen-market bridge uninstall` removes it.'));
|
|
159
|
+
}
|
|
160
|
+
else if (result.action === 'no-token') {
|
|
161
|
+
console.error(chalk_1.default.red('No Connect Token stored yet. Run once first: wen-market bridge --token <wm_ct_…>'));
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.error(chalk_1.default.yellow('Autostart is currently macOS-only — run `wen-market bridge` manually on this platform.'));
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
exports.bridgeCommand
|
|
170
|
+
.command('uninstall')
|
|
171
|
+
.description('Remove the login autostart service')
|
|
172
|
+
.action(async () => {
|
|
173
|
+
const result = await (0, autostart_1.uninstallAutostart)(realAutostartDeps());
|
|
174
|
+
if (result.action === 'uninstalled') {
|
|
175
|
+
console.log(chalk_1.default.green('✓ Bridge autostart removed.'));
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
console.error(chalk_1.default.yellow('Autostart is currently macOS-only — nothing to remove here.'));
|
|
179
|
+
}
|
|
122
180
|
});
|
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",
|