@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.
Files changed (47) hide show
  1. package/dist/__tests__/auth-api-key-format.test.d.ts +1 -0
  2. package/dist/__tests__/auth-api-key-format.test.js +41 -0
  3. package/dist/__tests__/auth-identity.test.d.ts +1 -0
  4. package/dist/__tests__/auth-identity.test.js +46 -0
  5. package/dist/__tests__/auth-login-core.test.d.ts +1 -0
  6. package/dist/__tests__/auth-login-core.test.js +145 -0
  7. package/dist/__tests__/auth.test.js +246 -57
  8. package/dist/__tests__/bridge-bundle-installer.test.js +101 -0
  9. package/dist/__tests__/bridge-context-writer.test.js +81 -1
  10. package/dist/__tests__/bridge-local-context-writer.test.js +11 -2
  11. package/dist/__tests__/client.test.js +26 -4
  12. package/dist/__tests__/config.test.js +36 -4
  13. package/dist/__tests__/openBrowser.test.d.ts +1 -0
  14. package/dist/__tests__/openBrowser.test.js +42 -0
  15. package/dist/__tests__/prompt.test.d.ts +1 -0
  16. package/dist/__tests__/prompt.test.js +49 -0
  17. package/dist/auth/identity.d.ts +22 -0
  18. package/dist/auth/identity.js +20 -0
  19. package/dist/auth/login.d.ts +80 -0
  20. package/dist/auth/login.js +61 -0
  21. package/dist/bridge/apiClient.d.ts +25 -0
  22. package/dist/bridge/apiClient.js +7 -1
  23. package/dist/bridge/bundleInstaller.d.ts +14 -0
  24. package/dist/bridge/bundleInstaller.js +56 -2
  25. package/dist/bridge/contextWriter.d.ts +11 -1
  26. package/dist/bridge/contextWriter.js +52 -8
  27. package/dist/bridge/localContextWriter.d.ts +6 -1
  28. package/dist/bridge/localContextWriter.js +22 -7
  29. package/dist/bridge/pairing.js +7 -1
  30. package/dist/bridge/relayClient.d.ts +9 -0
  31. package/dist/bridge/relayClient.js +25 -0
  32. package/dist/bridge/server.d.ts +6 -0
  33. package/dist/bridge/server.js +8 -0
  34. package/dist/commands/auth.d.ts +17 -0
  35. package/dist/commands/auth.js +202 -27
  36. package/dist/commands/bridge.js +61 -0
  37. package/dist/index.js +0 -0
  38. package/dist/utils/apiKeyFormat.d.ts +9 -0
  39. package/dist/utils/apiKeyFormat.js +26 -0
  40. package/dist/utils/client.js +7 -1
  41. package/dist/utils/config.d.ts +3 -0
  42. package/dist/utils/config.js +28 -2
  43. package/dist/utils/openBrowser.d.ts +16 -0
  44. package/dist/utils/openBrowser.js +30 -0
  45. package/dist/utils/prompt.d.ts +9 -0
  46. package/dist/utils/prompt.js +63 -0
  47. package/package.json +1 -1
@@ -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('Authenticate with the marketplace')
15
- .requiredOption('--api-key <key>', 'API key or wallet pubkey')
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 res = await fetch(`${apiUrl}/agents/${opts.apiKey}`);
21
- if (res.ok) {
22
- (0, format_1.info)(`Authenticated as agent: ${opts.apiKey}`);
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
- (0, format_1.info)('API key stored (agent not found — may be a poster or new user)');
112
+ warning = 'Could not find this key on the server (it may already be revoked).';
26
113
  }
27
114
  }
28
- catch {
29
- (0, format_1.info)('API key stored (could not reach server — will validate on next request)');
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 logged in. Run: wen-market auth login --api-key <key>');
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
- try {
49
- const res = await fetch(`${config.apiUrl}/agents/${config.apiKey}`);
50
- if (res.ok) {
51
- const data = await res.json();
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
- catch {
61
- (0, format_1.info)('Could not reach API to verify identity');
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
+ }
@@ -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
+ }
@@ -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-Wallet-Pubkey': this.apiKey,
24
+ 'X-API-Key': this.apiKey,
19
25
  };
20
26
  const res = await fetch(url, {
21
27
  method,
@@ -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;
@@ -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
- const DEFAULT_API_URL = 'http://localhost:3002/api/v1';
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 --api-key <key>');
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.5.24",
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",