@wenrwa/marketplace-cli 0.3.0 → 0.4.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.
@@ -0,0 +1,37 @@
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', '--scope', 'user', exports.MCP_SERVER_NAME]).catch(() => { });
26
+ // Also clear a stale default(local)-scope entry from older bridge versions.
27
+ await opts.run('claude', ['mcp', 'remove', exports.MCP_SERVER_NAME]).catch(() => { });
28
+ }
29
+ const args = [
30
+ 'mcp', 'add', '--scope', 'user', exports.MCP_SERVER_NAME,
31
+ '-e', `WENRWA_CONNECT_TOKEN=${opts.connectToken}`,
32
+ '-e', `MARKETPLACE_API_URL=${opts.apiUrl}`,
33
+ '--', 'npx', '@wenrwa/marketplace-mcp',
34
+ ];
35
+ await opts.run('claude', args);
36
+ return { action: exists ? 're-registered' : 'registered', command: `claude ${args.join(' ')}` };
37
+ }
@@ -0,0 +1,2 @@
1
+ import { PtyFactory } from './sessionManager';
2
+ export declare function realPtyFactory(): PtyFactory;
@@ -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;
@@ -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
+ });
@@ -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
+ });