@wenrwa/marketplace-cli 0.5.24 → 0.6.2
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__/auth-api-key-format.test.d.ts +1 -0
- package/dist/__tests__/auth-api-key-format.test.js +41 -0
- package/dist/__tests__/auth-identity.test.d.ts +1 -0
- package/dist/__tests__/auth-identity.test.js +46 -0
- package/dist/__tests__/auth-login-core.test.d.ts +1 -0
- package/dist/__tests__/auth-login-core.test.js +145 -0
- package/dist/__tests__/auth.test.js +246 -57
- package/dist/__tests__/bridge-bundle-installer.test.js +101 -0
- package/dist/__tests__/bridge-context-writer.test.js +81 -1
- package/dist/__tests__/bridge-local-context-writer.test.js +11 -2
- package/dist/__tests__/client.test.js +26 -4
- package/dist/__tests__/config.test.js +36 -4
- package/dist/__tests__/openBrowser.test.d.ts +1 -0
- package/dist/__tests__/openBrowser.test.js +42 -0
- package/dist/__tests__/prompt.test.d.ts +1 -0
- package/dist/__tests__/prompt.test.js +49 -0
- package/dist/auth/identity.d.ts +22 -0
- package/dist/auth/identity.js +20 -0
- package/dist/auth/login.d.ts +80 -0
- package/dist/auth/login.js +61 -0
- package/dist/bridge/apiClient.d.ts +25 -0
- package/dist/bridge/apiClient.js +7 -1
- package/dist/bridge/bundleInstaller.d.ts +14 -0
- package/dist/bridge/bundleInstaller.js +56 -2
- package/dist/bridge/contextWriter.d.ts +11 -1
- package/dist/bridge/contextWriter.js +52 -8
- package/dist/bridge/localContextWriter.d.ts +6 -1
- package/dist/bridge/localContextWriter.js +22 -7
- package/dist/bridge/pairing.js +7 -1
- package/dist/bridge/relayClient.d.ts +9 -0
- package/dist/bridge/relayClient.js +25 -0
- package/dist/bridge/server.d.ts +6 -0
- package/dist/bridge/server.js +8 -0
- package/dist/commands/auth.d.ts +17 -0
- package/dist/commands/auth.js +202 -27
- package/dist/commands/bridge.js +61 -0
- package/dist/index.js +0 -0
- package/dist/utils/apiKeyFormat.d.ts +9 -0
- package/dist/utils/apiKeyFormat.js +26 -0
- package/dist/utils/client.js +7 -1
- package/dist/utils/config.d.ts +3 -0
- package/dist/utils/config.js +28 -2
- package/dist/utils/openBrowser.d.ts +16 -0
- package/dist/utils/openBrowser.js +30 -0
- package/dist/utils/prompt.d.ts +9 -0
- package/dist/utils/prompt.js +63 -0
- package/package.json +1 -1
package/dist/commands/auth.js
CHANGED
|
@@ -1,37 +1,129 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
39
|
exports.authCommand = void 0;
|
|
40
|
+
/**
|
|
41
|
+
* wha-08 (docs/prd/retire-wallet-header-auth.md, docs/CONTEXT-identity.md) —
|
|
42
|
+
* `wen-market auth login` / `auth logout` / `auth whoami`.
|
|
43
|
+
*
|
|
44
|
+
* `login` runs the device-code flow (lws-14's mechanism, purpose=cli_login —
|
|
45
|
+
* see auth/login.ts): a code is minted and printed with the approval URL
|
|
46
|
+
* (https://marketplace.wenrwa.com/settings/cli-login?code=<CODE>), the
|
|
47
|
+
* browser is opened as best-effort convenience, and the CLI polls until a
|
|
48
|
+
* human approves or denies it there. `--api-key` skips all of that for CI:
|
|
49
|
+
* an existing key is verified with one authenticated call and saved
|
|
50
|
+
* directly. Either way the CLI authenticates as an API key (X-API-Key) —
|
|
51
|
+
* never the retired X-Wallet-Pubkey header (see utils/client.ts).
|
|
52
|
+
*
|
|
53
|
+
* `logout` revokes the stored key server-side (DELETE /keys/:id — see
|
|
54
|
+
* ApiKeyController#revoke's wha-08 self-revoke-only guard) before deleting
|
|
55
|
+
* it locally, so a copied config file stops working too.
|
|
56
|
+
*/
|
|
7
57
|
const commander_1 = require("commander");
|
|
58
|
+
const os = __importStar(require("os"));
|
|
8
59
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
60
|
const config_1 = require("../utils/config");
|
|
61
|
+
const client_1 = require("../utils/client");
|
|
62
|
+
const apiClient_1 = require("../bridge/apiClient");
|
|
63
|
+
const login_1 = require("../auth/login");
|
|
64
|
+
const identity_1 = require("../auth/identity");
|
|
65
|
+
const prompt_1 = require("../utils/prompt");
|
|
66
|
+
const openBrowser_1 = require("../utils/openBrowser");
|
|
67
|
+
const apiKeyFormat_1 = require("../utils/apiKeyFormat");
|
|
10
68
|
const format_1 = require("../utils/format");
|
|
69
|
+
const WEB_URL = process.env.WENRWA_WEB_URL || 'https://marketplace.wenrwa.com';
|
|
70
|
+
const SETTINGS_KEYS_URL = `${WEB_URL}/settings`;
|
|
11
71
|
exports.authCommand = new commander_1.Command('auth').description('Authentication commands');
|
|
12
72
|
exports.authCommand
|
|
13
73
|
.command('login')
|
|
14
|
-
.description('
|
|
15
|
-
.
|
|
74
|
+
.description('Sign in via your browser, or provide a CI key with --api-key')
|
|
75
|
+
.option('--api-key <key>', 'Use an existing API key non-interactively (for CI)')
|
|
16
76
|
.option('--api-url <url>', 'Marketplace API URL')
|
|
17
77
|
.action(async (opts) => {
|
|
18
78
|
const apiUrl = opts.apiUrl || (0, config_1.getApiUrl)();
|
|
79
|
+
const existing = (0, config_1.loadConfig)();
|
|
80
|
+
if (existing) {
|
|
81
|
+
const proceed = await confirmReplaceExistingLogin(existing);
|
|
82
|
+
if (!proceed)
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (opts.apiKey) {
|
|
86
|
+
await loginWithApiKey(opts.apiKey, apiUrl);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
await runDeviceLogin(apiUrl);
|
|
90
|
+
});
|
|
91
|
+
exports.authCommand
|
|
92
|
+
.command('logout')
|
|
93
|
+
.description('Sign out and revoke the stored API key')
|
|
94
|
+
.action(async () => {
|
|
95
|
+
const config = (0, config_1.loadConfig)();
|
|
96
|
+
if (!config) {
|
|
97
|
+
(0, format_1.info)('Not signed in.');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
let revokedServerSide = false;
|
|
101
|
+
let warning = null;
|
|
19
102
|
try {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
103
|
+
const client = (0, client_1.getClient)();
|
|
104
|
+
const data = await client.get('/keys');
|
|
105
|
+
const prefix = config.apiKey.slice(0, 16);
|
|
106
|
+
const match = (data.keys || []).find((k) => k.keyPrefix === prefix);
|
|
107
|
+
if (match) {
|
|
108
|
+
await client.del(`/keys/${match.id}`);
|
|
109
|
+
revokedServerSide = true;
|
|
23
110
|
}
|
|
24
111
|
else {
|
|
25
|
-
|
|
112
|
+
warning = 'Could not find this key on the server (it may already be revoked).';
|
|
26
113
|
}
|
|
27
114
|
}
|
|
28
|
-
catch {
|
|
29
|
-
|
|
115
|
+
catch (err) {
|
|
116
|
+
warning = `Could not revoke the key on the server: ${err?.message || err}`;
|
|
117
|
+
}
|
|
118
|
+
// Local credentials come out regardless of the server outcome — a stale
|
|
119
|
+
// or unreachable server must never leave the CLI looking "signed in".
|
|
120
|
+
(0, config_1.clearConfig)();
|
|
121
|
+
if (revokedServerSide) {
|
|
122
|
+
console.log(chalk_1.default.green('✓ Signed out. API key revoked.'));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
(0, format_1.error)(`${warning} Removed the key locally — it may still work server-side until it expires or is revoked another way.`);
|
|
30
126
|
}
|
|
31
|
-
(0, config_1.saveConfig)({ apiKey: opts.apiKey, apiUrl });
|
|
32
|
-
(0, format_1.success)(`Logged in. Config saved.`);
|
|
33
|
-
console.log(` API URL: ${chalk_1.default.dim(apiUrl)}`);
|
|
34
|
-
console.log(` Key: ${chalk_1.default.dim(opts.apiKey.slice(0, 8))}...`);
|
|
35
127
|
});
|
|
36
128
|
exports.authCommand
|
|
37
129
|
.command('whoami')
|
|
@@ -39,25 +131,108 @@ exports.authCommand
|
|
|
39
131
|
.action(async () => {
|
|
40
132
|
const config = (0, config_1.loadConfig)();
|
|
41
133
|
if (!config) {
|
|
42
|
-
(0, format_1.error)('Not
|
|
134
|
+
(0, format_1.error)('Not signed in. Run: wen-market auth login');
|
|
43
135
|
process.exit(1);
|
|
136
|
+
return;
|
|
44
137
|
}
|
|
45
138
|
console.log(chalk_1.default.bold('Current session:'));
|
|
46
139
|
console.log(` API URL: ${config.apiUrl}`);
|
|
47
|
-
console.log(` Key: ${config.apiKey}`);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
const agent = data.agent;
|
|
53
|
-
if (agent) {
|
|
54
|
-
console.log(` Name: ${agent.name || 'N/A'}`);
|
|
55
|
-
console.log(` Status: ${agent.status || 'active'}`);
|
|
56
|
-
console.log(` Rep: ${agent.weighted_reputation ?? 'N/A'}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
140
|
+
console.log(` Key: ${(0, apiKeyFormat_1.shortenKey)(config.apiKey)}`);
|
|
141
|
+
const identity = await (0, identity_1.resolveIdentity)({ apiUrl: config.apiUrl, apiKey: config.apiKey });
|
|
142
|
+
if (identity.ok) {
|
|
143
|
+
if (identity.name)
|
|
144
|
+
console.log(` Name: ${identity.name}`);
|
|
59
145
|
}
|
|
60
|
-
|
|
61
|
-
(0, format_1.info)(
|
|
146
|
+
else {
|
|
147
|
+
(0, format_1.info)(`Could not verify identity: ${identity.message}`);
|
|
62
148
|
}
|
|
63
149
|
});
|
|
150
|
+
// ── Already-signed-in gate ────────────────────────────────────────────
|
|
151
|
+
async function confirmReplaceExistingLogin(existing) {
|
|
152
|
+
const label = (0, apiKeyFormat_1.shortenKey)(existing.apiKey);
|
|
153
|
+
if (!(0, prompt_1.isInteractive)()) {
|
|
154
|
+
(0, format_1.error)(`Already signed in (${label}). Run this in an interactive terminal to confirm, or \`wen-market auth logout\` first.`);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
const yes = await (0, prompt_1.confirm)(`Already signed in as ${label}. Sign in again? (y/N) `, false);
|
|
158
|
+
if (!yes) {
|
|
159
|
+
(0, format_1.info)('Cancelled — staying signed in.');
|
|
160
|
+
}
|
|
161
|
+
return yes;
|
|
162
|
+
}
|
|
163
|
+
// ── --api-key (CI) path ─────────────────────────────────────────────────
|
|
164
|
+
async function loginWithApiKey(key, apiUrl) {
|
|
165
|
+
if ((0, apiKeyFormat_1.looksLikeWalletPubkey)(key)) {
|
|
166
|
+
(0, format_1.error)(`That's a wallet address, not an API key. Run \`wen-market auth login\` or create a key at ${SETTINGS_KEYS_URL}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (!(0, apiKeyFormat_1.isRealApiKey)(key)) {
|
|
171
|
+
(0, format_1.error)(`Not a valid API key (expected one starting with wm_sk_). Run \`wen-market auth login\` or create a key at ${SETTINGS_KEYS_URL}`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const identity = await (0, identity_1.resolveIdentity)({ apiUrl, apiKey: key });
|
|
176
|
+
if (!identity.ok) {
|
|
177
|
+
(0, format_1.error)(`Could not verify this API key: ${identity.message}`);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
(0, config_1.saveConfig)({ apiKey: key, apiUrl });
|
|
182
|
+
console.log(chalk_1.default.green(`✓ Signed in as ${identity.name || (0, apiKeyFormat_1.shortenKey)(key)}`));
|
|
183
|
+
}
|
|
184
|
+
// ── Browser device flow ─────────────────────────────────────────────────
|
|
185
|
+
async function runDeviceLogin(apiUrl) {
|
|
186
|
+
let cancelled = false;
|
|
187
|
+
const onSigint = () => {
|
|
188
|
+
cancelled = true;
|
|
189
|
+
console.log(chalk_1.default.yellow('\nCancelling — finishing the in-flight request…'));
|
|
190
|
+
};
|
|
191
|
+
process.on('SIGINT', onSigint);
|
|
192
|
+
try {
|
|
193
|
+
const outcome = await (0, login_1.loginCore)({ apiUrl }, {
|
|
194
|
+
requestPairingCode: apiClient_1.requestPairingCode,
|
|
195
|
+
pollPairingCode: apiClient_1.pollPairingCode,
|
|
196
|
+
hostname: () => os.hostname(),
|
|
197
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
198
|
+
now: () => Date.now(),
|
|
199
|
+
isCancelled: () => cancelled,
|
|
200
|
+
onCodeReady: ({ code, expiresAt }) => {
|
|
201
|
+
const url = `${WEB_URL}/settings/cli-login?code=${code}`;
|
|
202
|
+
const minutesLeft = Math.max(1, Math.round((new Date(expiresAt).getTime() - Date.now()) / 60000));
|
|
203
|
+
console.log('');
|
|
204
|
+
console.log(chalk_1.default.bold.cyan(` ${code}`));
|
|
205
|
+
console.log('');
|
|
206
|
+
console.log(chalk_1.default.dim(`Approve at ${chalk_1.default.underline(url)}`));
|
|
207
|
+
console.log(chalk_1.default.dim(`Expires in ~${minutesLeft} min. Waiting for approval… (Ctrl+C to cancel)`));
|
|
208
|
+
(0, openBrowser_1.openBrowser)(url);
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
await reportLoginOutcome(outcome, apiUrl);
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
process.off('SIGINT', onSigint);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async function reportLoginOutcome(outcome, apiUrl) {
|
|
218
|
+
switch (outcome.result.outcome) {
|
|
219
|
+
case 'approved': {
|
|
220
|
+
(0, config_1.saveConfig)({ apiKey: outcome.result.apiKey, apiUrl });
|
|
221
|
+
const identity = await (0, identity_1.resolveIdentity)({ apiUrl, apiKey: outcome.result.apiKey });
|
|
222
|
+
const name = identity.ok && identity.name ? identity.name : (0, apiKeyFormat_1.shortenKey)(outcome.result.apiKey);
|
|
223
|
+
console.log(chalk_1.default.green(`✓ Signed in as ${name}`));
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
case 'denied':
|
|
227
|
+
(0, format_1.error)('Login denied in the browser.');
|
|
228
|
+
return;
|
|
229
|
+
case 'expired':
|
|
230
|
+
case 'timeout':
|
|
231
|
+
(0, format_1.error)('Code expired — run `wen-market auth login` again.');
|
|
232
|
+
return;
|
|
233
|
+
case 'cancelled':
|
|
234
|
+
default:
|
|
235
|
+
console.log(chalk_1.default.yellow('Login cancelled — no changes made.'));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
package/dist/commands/bridge.js
CHANGED
|
@@ -705,9 +705,17 @@ exports.bridgeCommand = new commander_1.Command('bridge')
|
|
|
705
705
|
reposBaseDir,
|
|
706
706
|
mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
|
|
707
707
|
writeFile: (p, content) => fs.writeFileSync(p, content),
|
|
708
|
+
// Crew swap: replace the previous crew's members instead of
|
|
709
|
+
// piling on top of them. The installer decides WHAT may go (only
|
|
710
|
+
// marker-carrying files in .claude/agents that this bundle does
|
|
711
|
+
// not include); these three only give it eyes and hands.
|
|
712
|
+
listDir: (dir) => (fs.existsSync(dir) ? fs.readdirSync(dir) : []),
|
|
713
|
+
readFile: (p) => fs.readFileSync(p, 'utf-8'),
|
|
714
|
+
remove: (p) => fs.rmSync(p, { force: true }),
|
|
708
715
|
});
|
|
709
716
|
console.log(chalk_1.default.green(`✓ Installed crew bundle → workspace ${workspaceId} (${result.written.length} files` +
|
|
710
717
|
(result.skipped.length ? `, ${result.skipped.length} skipped` : '') +
|
|
718
|
+
(result.pruned.length ? `, ${result.pruned.length} stale crew member(s) removed` : '') +
|
|
711
719
|
')'));
|
|
712
720
|
return result;
|
|
713
721
|
},
|
|
@@ -715,6 +723,36 @@ exports.bridgeCommand = new commander_1.Command('bridge')
|
|
|
715
723
|
// into a fenced managed region of /data/repos/<workspaceId>/CLAUDE.md so
|
|
716
724
|
// Claude Code reads it natively next turn. A FILE, never PTY input;
|
|
717
725
|
// path-safe + idempotent; empty content removes the region.
|
|
726
|
+
/*
|
|
727
|
+
wmr-07 — the pointer refresh the hosted bridge fires when a session
|
|
728
|
+
opens. Same writer, same gate, same fetch as the manifest path below;
|
|
729
|
+
the only difference is `pointersOnly`, which keeps whatever file
|
|
730
|
+
content the region already holds.
|
|
731
|
+
|
|
732
|
+
It exists because the manifest frame is not a reliable trigger: arena
|
|
733
|
+
only sends one when the pilot has files selected, so most workspaces
|
|
734
|
+
never got the pointers at all.
|
|
735
|
+
*/
|
|
736
|
+
refreshContextPointers: async ({ workspaceId }) => {
|
|
737
|
+
const memoryAvailable = Boolean(process.env.WENRWA_CONNECT_TOKEN);
|
|
738
|
+
const memoryBrief = memoryAvailable ? await (0, memoryBrief_1.fetchMemoryBrief)(workspaceId) : null;
|
|
739
|
+
const result = (0, contextWriter_1.writeContext)({
|
|
740
|
+
workspaceId,
|
|
741
|
+
pointersOnly: true,
|
|
742
|
+
workGraphAvailable: Boolean(process.env.WENRWA_CONNECT_TOKEN),
|
|
743
|
+
memoryAvailable,
|
|
744
|
+
memoryBrief,
|
|
745
|
+
}, {
|
|
746
|
+
reposBaseDir,
|
|
747
|
+
exists: (p) => fs.existsSync(p),
|
|
748
|
+
readFile: (p) => fs.readFileSync(p, 'utf-8'),
|
|
749
|
+
writeFile: (p, content) => fs.writeFileSync(p, content),
|
|
750
|
+
mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
|
|
751
|
+
});
|
|
752
|
+
if (result.action !== 'noop') {
|
|
753
|
+
console.log(chalk_1.default.green(`✓ Context pointers ${result.action} → workspace ${workspaceId}`));
|
|
754
|
+
}
|
|
755
|
+
},
|
|
718
756
|
writeContext: async ({ workspaceId, content }) => {
|
|
719
757
|
// wmr-03 — the workspace's memory brief, fetched at manifest time so
|
|
720
758
|
// the agent has it in context from its next turn. Never blocks the
|
|
@@ -1031,6 +1069,21 @@ exports.bridgeCommand = new commander_1.Command('bridge')
|
|
|
1031
1069
|
// lws-08 (PRD D9) — a context-manifest frame writes ONLY
|
|
1032
1070
|
// `.hiveop/context.md` under the resolved local workspace's project
|
|
1033
1071
|
// root; unknown workspaceId → null (server.ts errors the frame).
|
|
1072
|
+
// wmr-07 — the local twin of refreshContextPointers above.
|
|
1073
|
+
refreshLocalContextPointers: async ({ workspaceId }) => {
|
|
1074
|
+
const entry = (0, folderRegistry_1.findEntryByWorkspaceId)((0, folderRegistry_1.loadFolderRegistry)(), workspaceId);
|
|
1075
|
+
if (!entry)
|
|
1076
|
+
return;
|
|
1077
|
+
const memoryAvailable = mcp.action !== 'claude-not-found';
|
|
1078
|
+
const memoryBrief = memoryAvailable ? await (0, memoryBrief_1.fetchMemoryBrief)(workspaceId) : null;
|
|
1079
|
+
(0, localContextWriter_1.writeLocalContext)({
|
|
1080
|
+
projectRoot: entry.path,
|
|
1081
|
+
pointersOnly: true,
|
|
1082
|
+
workGraphAvailable: mcp.action !== 'claude-not-found',
|
|
1083
|
+
memoryAvailable,
|
|
1084
|
+
memoryBrief,
|
|
1085
|
+
}, realLocalContextWriterDeps());
|
|
1086
|
+
},
|
|
1034
1087
|
writeLocalContext: async ({ workspaceId, content }) => {
|
|
1035
1088
|
const entry = (0, folderRegistry_1.findEntryByWorkspaceId)((0, folderRegistry_1.loadFolderRegistry)(), workspaceId);
|
|
1036
1089
|
if (!entry)
|
|
@@ -1201,9 +1254,17 @@ exports.bridgeCommand = new commander_1.Command('bridge')
|
|
|
1201
1254
|
workspaceRoot: entry.path,
|
|
1202
1255
|
mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
|
|
1203
1256
|
writeFile: (p, content) => fs.writeFileSync(p, content),
|
|
1257
|
+
// Crew swap: replace the previous crew's members instead of
|
|
1258
|
+
// piling on top of them. The installer decides WHAT may go (only
|
|
1259
|
+
// marker-carrying files in .claude/agents that this bundle does
|
|
1260
|
+
// not include); these three only give it eyes and hands.
|
|
1261
|
+
listDir: (dir) => (fs.existsSync(dir) ? fs.readdirSync(dir) : []),
|
|
1262
|
+
readFile: (p) => fs.readFileSync(p, 'utf-8'),
|
|
1263
|
+
remove: (p) => fs.rmSync(p, { force: true }),
|
|
1204
1264
|
});
|
|
1205
1265
|
console.log(chalk_1.default.green(`✓ Installed crew bundle → ${entry.path} (${result.written.length} files` +
|
|
1206
1266
|
(result.skipped.length ? `, ${result.skipped.length} skipped` : '') +
|
|
1267
|
+
(result.pruned.length ? `, ${result.pruned.length} stale crew member(s) removed` : '') +
|
|
1207
1268
|
')'));
|
|
1208
1269
|
return result;
|
|
1209
1270
|
},
|
package/dist/index.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wha-08 — distinguishing a real API key from the old habit of pasting a
|
|
3
|
+
* bare Solana wallet public key into --api-key (docs/CONTEXT-identity.md:
|
|
4
|
+
* "an API key is always a secret credential, never a public key").
|
|
5
|
+
*/
|
|
6
|
+
export declare function isRealApiKey(value: string): boolean;
|
|
7
|
+
export declare function looksLikeWalletPubkey(value: string): boolean;
|
|
8
|
+
/** Short, safe-to-print reference to a secret — never the whole key. */
|
|
9
|
+
export declare function shortenKey(value: string): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* wha-08 — distinguishing a real API key from the old habit of pasting a
|
|
4
|
+
* bare Solana wallet public key into --api-key (docs/CONTEXT-identity.md:
|
|
5
|
+
* "an API key is always a secret credential, never a public key").
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.isRealApiKey = isRealApiKey;
|
|
9
|
+
exports.looksLikeWalletPubkey = looksLikeWalletPubkey;
|
|
10
|
+
exports.shortenKey = shortenKey;
|
|
11
|
+
// Matches ApiKeyService#generateKey in backend/src/services/api-key.service.ts.
|
|
12
|
+
const API_KEY_PREFIX = 'wm_sk_';
|
|
13
|
+
// Solana public keys are base58 (no 0, O, I, l), typically 32-44 chars.
|
|
14
|
+
const BASE58_PUBKEY_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
15
|
+
function isRealApiKey(value) {
|
|
16
|
+
return typeof value === 'string' && value.startsWith(API_KEY_PREFIX) && value.length > API_KEY_PREFIX.length;
|
|
17
|
+
}
|
|
18
|
+
function looksLikeWalletPubkey(value) {
|
|
19
|
+
return typeof value === 'string' && !value.startsWith(API_KEY_PREFIX) && BASE58_PUBKEY_RE.test(value);
|
|
20
|
+
}
|
|
21
|
+
/** Short, safe-to-print reference to a secret — never the whole key. */
|
|
22
|
+
function shortenKey(value) {
|
|
23
|
+
if (!value)
|
|
24
|
+
return value;
|
|
25
|
+
return value.length <= 14 ? value : `${value.slice(0, 12)}…`;
|
|
26
|
+
}
|
package/dist/utils/client.js
CHANGED
|
@@ -13,9 +13,15 @@ class ApiClient {
|
|
|
13
13
|
}
|
|
14
14
|
async request(method, path, body) {
|
|
15
15
|
const url = `${this.apiUrl}${path}`;
|
|
16
|
+
// wha-08 (docs/CONTEXT-identity.md, docs/prd/retire-wallet-header-auth.md):
|
|
17
|
+
// the interactive CLI authenticates with an API key, sent as X-API-Key.
|
|
18
|
+
// X-Wallet-Pubkey (the retired header) is never sent by this client —
|
|
19
|
+
// requireConfig() already refuses to load a config whose "apiKey" is
|
|
20
|
+
// actually a bare wallet public key, so `this.apiKey` here is always a
|
|
21
|
+
// real wm_sk_ credential.
|
|
16
22
|
const headers = {
|
|
17
23
|
'Content-Type': 'application/json',
|
|
18
|
-
'X-
|
|
24
|
+
'X-API-Key': this.apiKey,
|
|
19
25
|
};
|
|
20
26
|
const res = await fetch(url, {
|
|
21
27
|
method,
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -4,5 +4,8 @@ export interface CliConfig {
|
|
|
4
4
|
}
|
|
5
5
|
export declare function loadConfig(): CliConfig | null;
|
|
6
6
|
export declare function saveConfig(config: CliConfig): void;
|
|
7
|
+
/** wha-08: deletes the local config file — used by `auth logout`. Silent no-op
|
|
8
|
+
* if it's already gone (e.g. a previous logout, or never logged in). */
|
|
9
|
+
export declare function clearConfig(): void;
|
|
7
10
|
export declare function requireConfig(): CliConfig;
|
|
8
11
|
export declare function getApiUrl(): string;
|
package/dist/utils/config.js
CHANGED
|
@@ -35,14 +35,17 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.loadConfig = loadConfig;
|
|
37
37
|
exports.saveConfig = saveConfig;
|
|
38
|
+
exports.clearConfig = clearConfig;
|
|
38
39
|
exports.requireConfig = requireConfig;
|
|
39
40
|
exports.getApiUrl = getApiUrl;
|
|
40
41
|
const fs = __importStar(require("fs"));
|
|
41
42
|
const path = __importStar(require("path"));
|
|
42
43
|
const os = __importStar(require("os"));
|
|
44
|
+
const apiKeyFormat_1 = require("./apiKeyFormat");
|
|
43
45
|
const CONFIG_DIR = path.join(os.homedir(), '.wen-market');
|
|
44
46
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
45
|
-
|
|
47
|
+
// Published CLI: default to production. Local development sets MARKETPLACE_API_URL.
|
|
48
|
+
const DEFAULT_API_URL = 'https://api.wenrwa.com/api/v1';
|
|
46
49
|
function loadConfig() {
|
|
47
50
|
try {
|
|
48
51
|
const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
@@ -56,11 +59,34 @@ function saveConfig(config) {
|
|
|
56
59
|
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
57
60
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
|
|
58
61
|
}
|
|
62
|
+
/** wha-08: deletes the local config file — used by `auth logout`. Silent no-op
|
|
63
|
+
* if it's already gone (e.g. a previous logout, or never logged in). */
|
|
64
|
+
function clearConfig() {
|
|
65
|
+
try {
|
|
66
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// already gone — nothing to do
|
|
70
|
+
}
|
|
71
|
+
}
|
|
59
72
|
function requireConfig() {
|
|
60
73
|
const config = loadConfig();
|
|
61
74
|
if (!config) {
|
|
62
|
-
console.error('Not logged in. Run: wen-market auth login
|
|
75
|
+
console.error('Not logged in. Run: wen-market auth login');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
return config;
|
|
78
|
+
}
|
|
79
|
+
// wha-08 (docs/CONTEXT-identity.md): the interactive CLI used to store a
|
|
80
|
+
// bare wallet public key as "apiKey" and send it as X-Wallet-Pubkey (the
|
|
81
|
+
// retired header — public keys are public, so that proved nothing). An
|
|
82
|
+
// existing config left over from that era must not be silently reused —
|
|
83
|
+
// fail fast with guidance rather than let the client send a header that
|
|
84
|
+
// no longer authenticates anything.
|
|
85
|
+
if (!(0, apiKeyFormat_1.isRealApiKey)(config.apiKey)) {
|
|
86
|
+
console.error('Your saved credential looks like a wallet address, not an API key.');
|
|
87
|
+
console.error('Run: wen-market auth login');
|
|
63
88
|
process.exit(1);
|
|
89
|
+
return config;
|
|
64
90
|
}
|
|
65
91
|
return config;
|
|
66
92
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wha-08 — best-effort "open the approval URL in a browser" for
|
|
3
|
+
* `wen-market auth login`. The code + URL are always printed regardless of
|
|
4
|
+
* whether this works (over SSH there is no browser at all), so any failure
|
|
5
|
+
* here is swallowed — never surfaced as an error.
|
|
6
|
+
*/
|
|
7
|
+
import { SpawnOptions } from 'child_process';
|
|
8
|
+
export type SpawnedChild = {
|
|
9
|
+
unref(): void;
|
|
10
|
+
on(event: 'error', cb: (err: Error) => void): void;
|
|
11
|
+
};
|
|
12
|
+
export type SpawnFn = (command: string, args: string[], options?: SpawnOptions) => SpawnedChild;
|
|
13
|
+
export declare function openBrowser(url: string, opts?: {
|
|
14
|
+
platform?: NodeJS.Platform;
|
|
15
|
+
spawnFn?: SpawnFn;
|
|
16
|
+
}): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.openBrowser = openBrowser;
|
|
4
|
+
/**
|
|
5
|
+
* wha-08 — best-effort "open the approval URL in a browser" for
|
|
6
|
+
* `wen-market auth login`. The code + URL are always printed regardless of
|
|
7
|
+
* whether this works (over SSH there is no browser at all), so any failure
|
|
8
|
+
* here is swallowed — never surfaced as an error.
|
|
9
|
+
*/
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
function commandFor(platform, url) {
|
|
12
|
+
if (platform === 'darwin')
|
|
13
|
+
return { command: 'open', args: [url] };
|
|
14
|
+
if (platform === 'win32')
|
|
15
|
+
return { command: 'cmd', args: ['/c', 'start', '""', url] };
|
|
16
|
+
return { command: 'xdg-open', args: [url] };
|
|
17
|
+
}
|
|
18
|
+
function openBrowser(url, opts = {}) {
|
|
19
|
+
const platform = opts.platform ?? process.platform;
|
|
20
|
+
const spawnFn = opts.spawnFn ?? child_process_1.spawn;
|
|
21
|
+
try {
|
|
22
|
+
const { command, args } = commandFor(platform, url);
|
|
23
|
+
const child = spawnFn(command, args, { stdio: 'ignore', detached: true });
|
|
24
|
+
child.on('error', () => { });
|
|
25
|
+
child.unref();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// best-effort — nothing to do, the user still has the printed URL
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ConfirmStreams {
|
|
2
|
+
input: NodeJS.ReadableStream;
|
|
3
|
+
output: NodeJS.WritableStream;
|
|
4
|
+
}
|
|
5
|
+
export declare function isInteractive(stdin?: Pick<NodeJS.ReadStream, 'isTTY'>, stdout?: Pick<NodeJS.WriteStream, 'isTTY'>): boolean;
|
|
6
|
+
/** Asks `question`, returns `defaultValue` on a bare Enter. Only "y"/"yes"
|
|
7
|
+
* (case-insensitive) count as yes — everything else, including garbage
|
|
8
|
+
* input, is a no. */
|
|
9
|
+
export declare function confirm(question: string, defaultValue?: boolean, streams?: ConfirmStreams): Promise<boolean>;
|
|
@@ -0,0 +1,63 @@
|
|
|
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.isInteractive = isInteractive;
|
|
37
|
+
exports.confirm = confirm;
|
|
38
|
+
/**
|
|
39
|
+
* wha-08 — minimal y/N confirmation prompt for "Already signed in as X.
|
|
40
|
+
* Sign in again? (y/N)". Streams are injectable so this is testable without
|
|
41
|
+
* a real TTY.
|
|
42
|
+
*/
|
|
43
|
+
const readline = __importStar(require("readline"));
|
|
44
|
+
function isInteractive(stdin = process.stdin, stdout = process.stdout) {
|
|
45
|
+
return Boolean(stdin.isTTY && stdout.isTTY);
|
|
46
|
+
}
|
|
47
|
+
/** Asks `question`, returns `defaultValue` on a bare Enter. Only "y"/"yes"
|
|
48
|
+
* (case-insensitive) count as yes — everything else, including garbage
|
|
49
|
+
* input, is a no. */
|
|
50
|
+
function confirm(question, defaultValue = false, streams = { input: process.stdin, output: process.stdout }) {
|
|
51
|
+
const rl = readline.createInterface({ input: streams.input, output: streams.output });
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
rl.question(question, (answer) => {
|
|
54
|
+
rl.close();
|
|
55
|
+
const trimmed = answer.trim().toLowerCase();
|
|
56
|
+
if (!trimmed) {
|
|
57
|
+
resolve(defaultValue);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
resolve(trimmed === 'y' || trimmed === 'yes');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenrwa/marketplace-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.2",
|
|
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",
|