@wenrwa/marketplace-cli 0.5.23 → 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 +168 -1
- package/dist/__tests__/bridge-local-context-writer.test.js +51 -2
- package/dist/__tests__/bridge-memory-brief.test.d.ts +1 -0
- package/dist/__tests__/bridge-memory-brief.test.js +107 -0
- package/dist/__tests__/bridge-memory-pointer.test.d.ts +1 -0
- package/dist/__tests__/bridge-memory-pointer.test.js +92 -0
- 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 +24 -1
- package/dist/bridge/contextWriter.js +61 -5
- package/dist/bridge/localContextWriter.d.ts +13 -1
- package/dist/bridge/localContextWriter.js +31 -4
- package/dist/bridge/memoryBrief.d.ts +41 -0
- package/dist/bridge/memoryBrief.js +63 -0
- package/dist/bridge/memoryPointer.d.ts +49 -0
- package/dist/bridge/memoryPointer.js +62 -0
- package/dist/bridge/pairing.js +7 -1
- package/dist/bridge/relayClient.d.ts +20 -1
- package/dist/bridge/relayClient.js +45 -6
- package/dist/bridge/server.d.ts +10 -1
- package/dist/bridge/server.js +29 -10
- package/dist/commands/auth.d.ts +17 -0
- package/dist/commands/auth.js +202 -27
- package/dist/commands/bridge.js +82 -2
- 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 +2 -2
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
|
@@ -73,6 +73,7 @@ const repoCheckout_1 = require("../bridge/repoCheckout");
|
|
|
73
73
|
const bundleInstaller_1 = require("../bridge/bundleInstaller");
|
|
74
74
|
const agentLaunch_1 = require("../bridge/agentLaunch");
|
|
75
75
|
const contextWriter_1 = require("../bridge/contextWriter");
|
|
76
|
+
const memoryBrief_1 = require("../bridge/memoryBrief");
|
|
76
77
|
const containerFileRelay_1 = require("../bridge/containerFileRelay");
|
|
77
78
|
const claudeTranscriptReader_1 = require("../bridge/claudeTranscriptReader");
|
|
78
79
|
const config_2 = require("../utils/config");
|
|
@@ -704,9 +705,17 @@ exports.bridgeCommand = new commander_1.Command('bridge')
|
|
|
704
705
|
reposBaseDir,
|
|
705
706
|
mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
|
|
706
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 }),
|
|
707
715
|
});
|
|
708
716
|
console.log(chalk_1.default.green(`✓ Installed crew bundle → workspace ${workspaceId} (${result.written.length} files` +
|
|
709
717
|
(result.skipped.length ? `, ${result.skipped.length} skipped` : '') +
|
|
718
|
+
(result.pruned.length ? `, ${result.pruned.length} stale crew member(s) removed` : '') +
|
|
710
719
|
')'));
|
|
711
720
|
return result;
|
|
712
721
|
},
|
|
@@ -714,7 +723,44 @@ exports.bridgeCommand = new commander_1.Command('bridge')
|
|
|
714
723
|
// into a fenced managed region of /data/repos/<workspaceId>/CLAUDE.md so
|
|
715
724
|
// Claude Code reads it natively next turn. A FILE, never PTY input;
|
|
716
725
|
// path-safe + idempotent; empty content removes the region.
|
|
717
|
-
|
|
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
|
+
},
|
|
756
|
+
writeContext: async ({ workspaceId, content }) => {
|
|
757
|
+
// wmr-03 — the workspace's memory brief, fetched at manifest time so
|
|
758
|
+
// the agent has it in context from its next turn. Never blocks the
|
|
759
|
+
// write: fetchMemoryBrief resolves null on any failure (PRD D2), and
|
|
760
|
+
// carries its own bounded timeout so a hanging backend cannot stall a
|
|
761
|
+
// terminal opening.
|
|
762
|
+
const memoryAvailable = Boolean(process.env.WENRWA_CONNECT_TOKEN);
|
|
763
|
+
const memoryBrief = memoryAvailable ? await (0, memoryBrief_1.fetchMemoryBrief)(workspaceId) : null;
|
|
718
764
|
const result = (0, contextWriter_1.writeContext)({
|
|
719
765
|
workspaceId,
|
|
720
766
|
content,
|
|
@@ -724,6 +770,10 @@ exports.bridgeCommand = new commander_1.Command('bridge')
|
|
|
724
770
|
// means the MCP tools this pointer names are not registered here —
|
|
725
771
|
// pointing at tools that do not exist is worse than silence.
|
|
726
772
|
workGraphAvailable: Boolean(process.env.WENRWA_CONNECT_TOKEN),
|
|
773
|
+
// wmr-01 — same gate, same reason: the token is what proves the
|
|
774
|
+
// MCP tools named by the memory pointer are registered here.
|
|
775
|
+
memoryAvailable,
|
|
776
|
+
memoryBrief,
|
|
727
777
|
}, {
|
|
728
778
|
reposBaseDir,
|
|
729
779
|
exists: (p) => fs.existsSync(p),
|
|
@@ -1019,10 +1069,30 @@ exports.bridgeCommand = new commander_1.Command('bridge')
|
|
|
1019
1069
|
// lws-08 (PRD D9) — a context-manifest frame writes ONLY
|
|
1020
1070
|
// `.hiveop/context.md` under the resolved local workspace's project
|
|
1021
1071
|
// root; unknown workspaceId → null (server.ts errors the frame).
|
|
1022
|
-
|
|
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
|
+
},
|
|
1087
|
+
writeLocalContext: async ({ workspaceId, content }) => {
|
|
1023
1088
|
const entry = (0, folderRegistry_1.findEntryByWorkspaceId)((0, folderRegistry_1.loadFolderRegistry)(), workspaceId);
|
|
1024
1089
|
if (!entry)
|
|
1025
1090
|
return null;
|
|
1091
|
+
// wmr-04 — same gate as the work-graph pointer on this lane: whether
|
|
1092
|
+
// THIS bridge run actually registered the MCP server. Fetch failures
|
|
1093
|
+
// resolve null and leave the standing instruction in place (PRD D2).
|
|
1094
|
+
const memoryAvailable = mcp.action !== 'claude-not-found';
|
|
1095
|
+
const memoryBrief = memoryAvailable ? await (0, memoryBrief_1.fetchMemoryBrief)(workspaceId) : null;
|
|
1026
1096
|
return (0, localContextWriter_1.writeLocalContext)({
|
|
1027
1097
|
projectRoot: entry.path,
|
|
1028
1098
|
content,
|
|
@@ -1034,6 +1104,8 @@ exports.bridgeCommand = new commander_1.Command('bridge')
|
|
|
1034
1104
|
// for "are the tools this pointer names actually registered" —
|
|
1035
1105
|
// pointing at tools that do not exist is worse than silence.
|
|
1036
1106
|
workGraphAvailable: mcp.action !== 'claude-not-found',
|
|
1107
|
+
memoryAvailable,
|
|
1108
|
+
memoryBrief,
|
|
1037
1109
|
}, realLocalContextWriterDeps());
|
|
1038
1110
|
},
|
|
1039
1111
|
// lws-06 — best-effort `claude` binary check before spawning an
|
|
@@ -1182,9 +1254,17 @@ exports.bridgeCommand = new commander_1.Command('bridge')
|
|
|
1182
1254
|
workspaceRoot: entry.path,
|
|
1183
1255
|
mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
|
|
1184
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 }),
|
|
1185
1264
|
});
|
|
1186
1265
|
console.log(chalk_1.default.green(`✓ Installed crew bundle → ${entry.path} (${result.written.length} files` +
|
|
1187
1266
|
(result.skipped.length ? `, ${result.skipped.length} skipped` : '') +
|
|
1267
|
+
(result.pruned.length ? `, ${result.pruned.length} stale crew member(s) removed` : '') +
|
|
1188
1268
|
')'));
|
|
1189
1269
|
return result;
|
|
1190
1270
|
},
|
|
@@ -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
|
+
}
|