@anyforge/control 0.2.4 → 0.3.0
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/README.md +52 -4
- package/dist/cli.js +91 -135
- package/package.json +8 -6
- package/dist/commands/applyPatch.js +0 -92
- package/dist/commands/init.js +0 -296
- package/dist/commands/logout.js +0 -34
- package/dist/commands/status.js +0 -58
- package/dist/commands/whoami.js +0 -23
- package/dist/lib/configPatch.js +0 -524
- package/dist/lib/credentials.js +0 -40
package/dist/commands/init.js
DELETED
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'child_process';
|
|
2
|
-
import open from 'open';
|
|
3
|
-
import prompts from 'prompts';
|
|
4
|
-
import { green, dim, bold, yellow, cyan } from 'kleur/colors';
|
|
5
|
-
import { saveCredentials } from '../lib/credentials.js';
|
|
6
|
-
import { detectAndPatchConfigs, detectClaudeSubscription, patchClaudeTelemetry, } from '../lib/configPatch.js';
|
|
7
|
-
const POLL_INTERVAL_MS = 2_000;
|
|
8
|
-
const POLL_TIMEOUT_MS = 15 * 60 * 1000;
|
|
9
|
-
export async function runInit(opts) {
|
|
10
|
-
const { endpoint } = opts;
|
|
11
|
-
// eslint-disable-next-line no-console
|
|
12
|
-
console.log(bold('AnyForge Control — device authorisation'));
|
|
13
|
-
// eslint-disable-next-line no-console
|
|
14
|
-
console.log(dim(` endpoint: ${endpoint}`));
|
|
15
|
-
// 1. Issue a one-time code. We detect the git remote up-front when
|
|
16
|
-
// running in a repo so it can be attached to the API key later.
|
|
17
|
-
// Detection happens before scope-pick so the server has it
|
|
18
|
-
// regardless of which scope the operator chooses.
|
|
19
|
-
const cwd0 = process.cwd();
|
|
20
|
-
const gitRoot0 = findGitRoot(cwd0);
|
|
21
|
-
const earlyRepo = gitRoot0 ? detectGitRemoteRepo(gitRoot0) : null;
|
|
22
|
-
const initRes = await fetch(`${endpoint}/api/cli/init`, {
|
|
23
|
-
method: 'POST',
|
|
24
|
-
headers: { 'content-type': 'application/json' },
|
|
25
|
-
body: JSON.stringify({ repo: earlyRepo }),
|
|
26
|
-
});
|
|
27
|
-
const init = (await initRes.json());
|
|
28
|
-
if (!init.ok || !init.data) {
|
|
29
|
-
throw new Error(`Could not start the authorisation flow: ${init.error ?? 'unknown error'}`);
|
|
30
|
-
}
|
|
31
|
-
const { code, authUrl, pollUrl } = init.data;
|
|
32
|
-
// 2. Open the browser. Some shells (CI, headless) can't open one;
|
|
33
|
-
// the URL also prints so the user can copy it manually.
|
|
34
|
-
// eslint-disable-next-line no-console
|
|
35
|
-
console.log();
|
|
36
|
-
// eslint-disable-next-line no-console
|
|
37
|
-
console.log(`Open this URL in your browser to authorise:`);
|
|
38
|
-
// eslint-disable-next-line no-console
|
|
39
|
-
console.log(` ${cyan(authUrl)}`);
|
|
40
|
-
// eslint-disable-next-line no-console
|
|
41
|
-
console.log(dim(` Code: ${code}`));
|
|
42
|
-
// eslint-disable-next-line no-console
|
|
43
|
-
console.log();
|
|
44
|
-
try {
|
|
45
|
-
await open(authUrl);
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
// Headless / CI — the printed URL is fine.
|
|
49
|
-
}
|
|
50
|
-
// 3. Poll. Keep the message live so the user sees we're alive.
|
|
51
|
-
const credentials = await pollForToken({ pollUrl, endpoint });
|
|
52
|
-
// 4. Save credentials with restrictive perms.
|
|
53
|
-
await saveCredentials(credentials);
|
|
54
|
-
// eslint-disable-next-line no-console
|
|
55
|
-
console.log(green('✔') + ' API key saved to ' + dim('~/.anyforge/credentials'));
|
|
56
|
-
// 4b. A Claude Pro/Max subscription login is mutually exclusive with
|
|
57
|
-
// the BYOK inference proxy: the ANTHROPIC_AUTH_TOKEN we'd patch
|
|
58
|
-
// out-ranks (and thereby disables) the subscription login, and the
|
|
59
|
-
// proxy has no upstream Anthropic key for a subscription user. So
|
|
60
|
-
// we skip patching and steer them to the MCP server instead — which
|
|
61
|
-
// keeps their subscription for inference and adds AnyForge control
|
|
62
|
-
// on top. `--force` overrides for users who really do want a
|
|
63
|
-
// BYOK-keyed metered proxy.
|
|
64
|
-
const sub = await detectClaudeSubscription();
|
|
65
|
-
if (sub.isSubscription && !opts.force) {
|
|
66
|
-
await guideSubscriptionUser(sub, endpoint, credentials.apiKey);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
// 5. Pick a scope. The repo option only shows when we're sitting
|
|
70
|
-
// in a git working tree root — that matches the documented
|
|
71
|
-
// contract and avoids accidentally writing settings into a
|
|
72
|
-
// subdirectory the operator didn't mean to target.
|
|
73
|
-
const cwd = process.cwd();
|
|
74
|
-
const repoRoot = findGitRoot(cwd);
|
|
75
|
-
const scope = await pickScope(repoRoot);
|
|
76
|
-
// 6. Patch the chosen target. apiKey is inlined into the env map
|
|
77
|
-
// so Claude Code can read it without a shell restart.
|
|
78
|
-
const patches = await detectAndPatchConfigs({
|
|
79
|
-
endpoint,
|
|
80
|
-
apiKey: credentials.apiKey,
|
|
81
|
-
scope,
|
|
82
|
-
repoRoot,
|
|
83
|
-
});
|
|
84
|
-
if (patches.length === 0) {
|
|
85
|
-
// eslint-disable-next-line no-console
|
|
86
|
-
console.log(yellow('No agent configs were patched.'));
|
|
87
|
-
// eslint-disable-next-line no-console
|
|
88
|
-
console.log(dim(' Set ANTHROPIC_BASE_URL=' + endpoint + '/v1/anthropic and'));
|
|
89
|
-
// eslint-disable-next-line no-console
|
|
90
|
-
console.log(dim(' ANTHROPIC_AUTH_TOKEN=<your AnyForge key> manually if needed.'));
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
summarisePatches(patches);
|
|
94
|
-
}
|
|
95
|
-
// eslint-disable-next-line no-console
|
|
96
|
-
console.log();
|
|
97
|
-
if (sub.isSubscription && opts.force) {
|
|
98
|
-
// They overrode the subscription guard. Be explicit about the
|
|
99
|
-
// trade-off and the one-command way back.
|
|
100
|
-
// eslint-disable-next-line no-console
|
|
101
|
-
console.log(yellow(` ⚠ --force: your Claude ${sub.plan ?? ''} subscription login is now shadowed`));
|
|
102
|
-
// eslint-disable-next-line no-console
|
|
103
|
-
console.log(yellow(' by the AnyForge token — Claude Code will use the BYOK proxy, which'));
|
|
104
|
-
// eslint-disable-next-line no-console
|
|
105
|
-
console.log(yellow(' needs an Anthropic API key on your company. Run ') + bold('anyforge logout'));
|
|
106
|
-
// eslint-disable-next-line no-console
|
|
107
|
-
console.log(yellow(' to revert and restore your subscription.'));
|
|
108
|
-
}
|
|
109
|
-
if (scope === 'repo') {
|
|
110
|
-
// eslint-disable-next-line no-console
|
|
111
|
-
console.log(dim(' Re-run this command in another repo to patch it too,'));
|
|
112
|
-
// eslint-disable-next-line no-console
|
|
113
|
-
console.log(dim(' or run from any directory and choose "global" to install system-wide.'));
|
|
114
|
-
}
|
|
115
|
-
// eslint-disable-next-line no-console
|
|
116
|
-
console.log(bold('Done.') + ' Visit ' + cyan(`${endpoint}/usage`) + ' to see your traffic.');
|
|
117
|
-
}
|
|
118
|
-
async function pollForToken(args) {
|
|
119
|
-
const start = Date.now();
|
|
120
|
-
// eslint-disable-next-line no-console
|
|
121
|
-
process.stdout.write('Waiting for browser authorisation… ');
|
|
122
|
-
let dotCount = 0;
|
|
123
|
-
while (Date.now() - start < POLL_TIMEOUT_MS) {
|
|
124
|
-
await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
|
|
125
|
-
process.stdout.write('.');
|
|
126
|
-
dotCount += 1;
|
|
127
|
-
if (dotCount > 40) {
|
|
128
|
-
process.stdout.write('\n');
|
|
129
|
-
dotCount = 0;
|
|
130
|
-
}
|
|
131
|
-
const res = await fetch(args.pollUrl);
|
|
132
|
-
const body = (await res.json());
|
|
133
|
-
if (body.status === 'authorized' && body.data) {
|
|
134
|
-
process.stdout.write(' ' + green('done') + '\n');
|
|
135
|
-
return {
|
|
136
|
-
apiKey: body.data.apiKey,
|
|
137
|
-
companyId: body.data.companyId,
|
|
138
|
-
endpoint: args.endpoint,
|
|
139
|
-
savedAt: new Date().toISOString(),
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
if (body.status === 'expired' || body.status === 'not_found') {
|
|
143
|
-
process.stdout.write('\n');
|
|
144
|
-
throw new Error(`Authorisation ${body.status}. Re-run \`anyforge init\`.`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
throw new Error('Authorisation timed out (15 min). Re-run `anyforge init`.');
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Steer a subscription user to the MCP server instead of patching the
|
|
151
|
-
* BYOK proxy. The MCP server keeps their Claude Pro/Max login for
|
|
152
|
-
* inference and adds AnyForge control (HIL approvals, work items, crews)
|
|
153
|
-
* on top, with no auth conflict. Offers to run `claude mcp add` for them,
|
|
154
|
-
* and to enable OpenTelemetry usage metrics so their spend is still
|
|
155
|
-
* visible on the AnyForge dashboard even though the proxy isn't in path.
|
|
156
|
-
*/
|
|
157
|
-
async function guideSubscriptionUser(sub, endpoint, apiKey) {
|
|
158
|
-
const who = sub.email ? ` (${sub.email})` : '';
|
|
159
|
-
/* eslint-disable no-console */
|
|
160
|
-
console.log();
|
|
161
|
-
console.log(yellow(` ⚠ Claude ${sub.plan ?? ''} subscription login detected${who}.`));
|
|
162
|
-
console.log();
|
|
163
|
-
console.log(' AnyForge Control’s inference proxy is ' + bold('BYOK-only') + ' — it routes');
|
|
164
|
-
console.log(' Claude Code through your own Anthropic API key for metering and');
|
|
165
|
-
console.log(' governance. Pointing Claude Code at the proxy would ' + bold('override and'));
|
|
166
|
-
console.log(' ' + bold('disable your subscription login') + ', so we did NOT patch your config.');
|
|
167
|
-
console.log();
|
|
168
|
-
console.log(' Instead, use your subscription for inference and add the ' + bold('AnyForge'));
|
|
169
|
-
console.log(' ' + bold('MCP server') + ' for control (HIL approvals, work items, crews):');
|
|
170
|
-
console.log();
|
|
171
|
-
const mcpCmd = `claude mcp add anyforge --env ANYFORGE_API_KEY=${apiKey} -- npx -y @anyforge/mcp-server`;
|
|
172
|
-
const add = await prompts({
|
|
173
|
-
type: 'confirm',
|
|
174
|
-
name: 'go',
|
|
175
|
-
message: 'Add the AnyForge MCP server to Claude Code now?',
|
|
176
|
-
initial: true,
|
|
177
|
-
});
|
|
178
|
-
if (add.go) {
|
|
179
|
-
try {
|
|
180
|
-
execFileSync('claude', ['mcp', 'add', 'anyforge', '--env', `ANYFORGE_API_KEY=${apiKey}`, '--', 'npx', '-y', '@anyforge/mcp-server'], { stdio: 'inherit' });
|
|
181
|
-
console.log(green(' ✔') + ' AnyForge MCP server added to Claude Code.');
|
|
182
|
-
}
|
|
183
|
-
catch {
|
|
184
|
-
console.log(yellow(' Could not run `claude mcp add` automatically (is the `claude` CLI on PATH?).'));
|
|
185
|
-
console.log(dim(' Add it manually:'));
|
|
186
|
-
console.log(' ' + mcpCmd);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
console.log(dim(' Add it later with:'));
|
|
191
|
-
console.log(' ' + mcpCmd);
|
|
192
|
-
}
|
|
193
|
-
// Spend visibility without the proxy: Claude Code exports OpenTelemetry
|
|
194
|
-
// usage metrics (token counts + estimated cost — no prompt content) that
|
|
195
|
-
// AnyForge can ingest. Safe alongside the subscription login (no auth
|
|
196
|
-
// env touched).
|
|
197
|
-
console.log();
|
|
198
|
-
const tel = await prompts({
|
|
199
|
-
type: 'confirm',
|
|
200
|
-
name: 'go',
|
|
201
|
-
message: 'Send Claude Code usage to your AnyForge dashboard? (token counts only — no prompt content)',
|
|
202
|
-
initial: true,
|
|
203
|
-
});
|
|
204
|
-
if (tel.go) {
|
|
205
|
-
const sum = await patchClaudeTelemetry({ endpoint, apiKey });
|
|
206
|
-
if (sum) {
|
|
207
|
-
console.log(green(' ✔') + ' Usage telemetry enabled — view it at ' + cyan(`${endpoint}/usage`));
|
|
208
|
-
console.log(dim(' (written to ' + sum.path + '; `anyforge logout` removes it)'));
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
console.log(dim(' Skipped — your subscription usage stays local to Claude Code.'));
|
|
213
|
-
}
|
|
214
|
-
console.log();
|
|
215
|
-
console.log(dim(' Want the metered BYOK proxy instead? Re-run with ') + bold('--force') + dim(' using an'));
|
|
216
|
-
console.log(dim(' Anthropic API key on your company (this disables your subscription in'));
|
|
217
|
-
console.log(dim(' Claude Code; ') + bold('anyforge logout') + dim(' reverts it).'));
|
|
218
|
-
console.log();
|
|
219
|
-
console.log(bold('Done.') + ' Visit ' + cyan(`${endpoint}/usage`) + ' to see your traffic.');
|
|
220
|
-
/* eslint-enable no-console */
|
|
221
|
-
}
|
|
222
|
-
function findGitRoot(cwd) {
|
|
223
|
-
// Works from any subdirectory — git walks up the tree to find the root.
|
|
224
|
-
try {
|
|
225
|
-
return execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
226
|
-
cwd,
|
|
227
|
-
encoding: 'utf8',
|
|
228
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
229
|
-
}).trim();
|
|
230
|
-
}
|
|
231
|
-
catch {
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
async function pickScope(repoRoot) {
|
|
236
|
-
if (!repoRoot) {
|
|
237
|
-
// eslint-disable-next-line no-console
|
|
238
|
-
console.log(dim(' Not a git repo root — installing globally.'));
|
|
239
|
-
// eslint-disable-next-line no-console
|
|
240
|
-
console.log(dim(' Run this from a repo root if you want a per-repo install instead.'));
|
|
241
|
-
return 'global';
|
|
242
|
-
}
|
|
243
|
-
const choice = await prompts({
|
|
244
|
-
type: 'select',
|
|
245
|
-
name: 'scope',
|
|
246
|
-
message: 'Where should AnyForge patch Claude Code?',
|
|
247
|
-
choices: [
|
|
248
|
-
{ title: `This repo only (${repoRoot}/.claude/settings.local.json + .env)`, value: 'repo' },
|
|
249
|
-
{ title: 'Globally (~/.claude/settings.json)', value: 'global' },
|
|
250
|
-
],
|
|
251
|
-
initial: 0,
|
|
252
|
-
});
|
|
253
|
-
// prompts returns {} on cancel — default to repo since they were given the choice.
|
|
254
|
-
return choice.scope ?? 'repo';
|
|
255
|
-
}
|
|
256
|
-
function summarisePatches(patches) {
|
|
257
|
-
// eslint-disable-next-line no-console
|
|
258
|
-
console.log(green('✔') + ' Patched ' + patches.length + ' agent config' + (patches.length === 1 ? '' : 's') + ':');
|
|
259
|
-
for (const p of patches) {
|
|
260
|
-
// eslint-disable-next-line no-console
|
|
261
|
-
console.log(` ${dim('•')} ${p.label} ${dim('(' + p.path + ')')}`);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* Read the `origin` remote URL and normalise it to `owner/repo`.
|
|
266
|
-
* Handles both SSH (`git@github.com:owner/repo.git`) and HTTPS forms.
|
|
267
|
-
* Returns null when there is no remote, git is unavailable, or the
|
|
268
|
-
* URL can't be parsed into an owner/repo pair.
|
|
269
|
-
*
|
|
270
|
-
* Uses execFileSync (not exec/execSync) so no shell interpolation
|
|
271
|
-
* occurs — the args array is passed directly to the git binary.
|
|
272
|
-
*/
|
|
273
|
-
function detectGitRemoteRepo(repoRoot) {
|
|
274
|
-
try {
|
|
275
|
-
const raw = execFileSync('git', ['remote', 'get-url', 'origin'], {
|
|
276
|
-
cwd: repoRoot,
|
|
277
|
-
encoding: 'utf8',
|
|
278
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
279
|
-
}).trim();
|
|
280
|
-
// SSH: git@github.com:owner/repo.git
|
|
281
|
-
const sshMatch = raw.match(/^git@[\w.-]+:([\w.-]+\/[\w.-]+?)(?:\.git)?$/);
|
|
282
|
-
if (sshMatch)
|
|
283
|
-
return sshMatch[1];
|
|
284
|
-
// HTTPS: https://github.com/owner/repo[.git]
|
|
285
|
-
const httpsMatch = raw.match(/^https?:\/\/[\w.-]+\/([\w.-]+\/[\w.-]+?)(?:\.git)?$/);
|
|
286
|
-
if (httpsMatch)
|
|
287
|
-
return httpsMatch[1];
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
catch {
|
|
291
|
-
return null;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
// Re-exported so future commands (e.g. status) can reuse the prompt
|
|
295
|
-
// shape without duplicating the question text.
|
|
296
|
-
export { prompts };
|
package/dist/commands/logout.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { green, dim, yellow } from 'kleur/colors';
|
|
2
|
-
import { clearCredentials, loadCredentials } from '../lib/credentials.js';
|
|
3
|
-
import { revertClaudeCodeConfigs } from '../lib/configPatch.js';
|
|
4
|
-
/**
|
|
5
|
-
* `npx @anyforge/control logout` — wipe the local credential file AND
|
|
6
|
-
* revert the config patches `init` wrote (so a previously-shadowed
|
|
7
|
-
* Claude subscription login is restored). Does NOT revoke the key on
|
|
8
|
-
* the server side — that's a separate action on /settings → API keys.
|
|
9
|
-
*/
|
|
10
|
-
export async function runLogout() {
|
|
11
|
-
// Load before clearing so we know which proxy host we patched in
|
|
12
|
-
// (matters when the user ran `init --endpoint <staging>`).
|
|
13
|
-
const creds = await loadCredentials();
|
|
14
|
-
const removed = await clearCredentials();
|
|
15
|
-
const { reverted } = await revertClaudeCodeConfigs({ endpoint: creds?.endpoint ?? null });
|
|
16
|
-
/* eslint-disable no-console */
|
|
17
|
-
if (removed) {
|
|
18
|
-
console.log(green('✔') + ' Local credentials cleared.');
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
console.log(yellow('No credentials were saved on this machine.'));
|
|
22
|
-
}
|
|
23
|
-
if (reverted.length) {
|
|
24
|
-
console.log(green('✔') + ' Reverted patched configs:');
|
|
25
|
-
for (const p of reverted)
|
|
26
|
-
console.log(` ${dim('•')} ${p}`);
|
|
27
|
-
console.log(dim(' Your previous Claude Code login (if any) is restored.'));
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
console.log(dim(' No AnyForge config patches found to revert.'));
|
|
31
|
-
}
|
|
32
|
-
console.log(dim(' Revoke the key server-side at /settings → API keys.'));
|
|
33
|
-
/* eslint-enable no-console */
|
|
34
|
-
}
|
package/dist/commands/status.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { green, red, yellow, dim } from 'kleur/colors';
|
|
2
|
-
import { loadCredentials } from '../lib/credentials.js';
|
|
3
|
-
export async function runStatus(opts) {
|
|
4
|
-
const c = await loadCredentials();
|
|
5
|
-
if (!c) {
|
|
6
|
-
// eslint-disable-next-line no-console
|
|
7
|
-
console.log(yellow('Not logged in.') + ' Run ' + dim('npx @anyforge/control init') + ' first.');
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
const endpoint = opts.endpoint || c.endpoint;
|
|
11
|
-
// eslint-disable-next-line no-console
|
|
12
|
-
console.log(`Endpoint: ${endpoint}`);
|
|
13
|
-
// eslint-disable-next-line no-console
|
|
14
|
-
console.log(`Company: ${c.companyId}`);
|
|
15
|
-
// We don't have a dedicated /v1/health endpoint yet — the cheapest
|
|
16
|
-
// probe is a 1-token Anthropic Messages call. Costs ~$0.0001, fine.
|
|
17
|
-
const probeBody = {
|
|
18
|
-
model: 'claude-3-5-haiku-20241022',
|
|
19
|
-
max_tokens: 1,
|
|
20
|
-
messages: [{ role: 'user', content: 'Hi' }],
|
|
21
|
-
};
|
|
22
|
-
let res;
|
|
23
|
-
try {
|
|
24
|
-
res = await fetch(`${endpoint}/v1/anthropic/messages`, {
|
|
25
|
-
method: 'POST',
|
|
26
|
-
headers: {
|
|
27
|
-
'Content-Type': 'application/json',
|
|
28
|
-
'anthropic-version': '2023-06-01',
|
|
29
|
-
Authorization: `Bearer ${c.apiKey}`,
|
|
30
|
-
},
|
|
31
|
-
body: JSON.stringify(probeBody),
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
// eslint-disable-next-line no-console
|
|
36
|
-
console.log(red('✗ Proxy unreachable: ') + err.message);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
if (res.status === 401) {
|
|
40
|
-
// eslint-disable-next-line no-console
|
|
41
|
-
console.log(red('✗ API key rejected.') + ' Re-run ' + dim('npx @anyforge/control init') + '.');
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
if (res.status === 502) {
|
|
45
|
-
// eslint-disable-next-line no-console
|
|
46
|
-
console.log(red('✗ No BYOK Anthropic key configured for your company.'));
|
|
47
|
-
// eslint-disable-next-line no-console
|
|
48
|
-
console.log(dim(' Add one at ') + endpoint + dim('/settings → API keys.'));
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
if (!res.ok) {
|
|
52
|
-
// eslint-disable-next-line no-console
|
|
53
|
-
console.log(red(`✗ Proxy returned ${res.status}: `) + (await res.text()).slice(0, 200));
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
// eslint-disable-next-line no-console
|
|
57
|
-
console.log(green('✔ Proxy reachable, key valid, BYOK configured.'));
|
|
58
|
-
}
|
package/dist/commands/whoami.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { dim, yellow } from 'kleur/colors';
|
|
2
|
-
import { loadCredentials } from '../lib/credentials.js';
|
|
3
|
-
/**
|
|
4
|
-
* `npx @anyforge/control whoami` — show the company id tied to the
|
|
5
|
-
* currently saved credential. Useful for verifying the right device
|
|
6
|
-
* is configured before running long agent sessions.
|
|
7
|
-
*/
|
|
8
|
-
export async function runWhoami() {
|
|
9
|
-
const c = await loadCredentials();
|
|
10
|
-
if (!c) {
|
|
11
|
-
// eslint-disable-next-line no-console
|
|
12
|
-
console.log(yellow('Not logged in.') + ' Run ' + dim('npx @anyforge/control init') + ' first.');
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
// eslint-disable-next-line no-console
|
|
16
|
-
console.log(`Company: ${c.companyId}`);
|
|
17
|
-
// eslint-disable-next-line no-console
|
|
18
|
-
console.log(`Endpoint: ${c.endpoint}`);
|
|
19
|
-
// eslint-disable-next-line no-console
|
|
20
|
-
console.log(`Saved: ${new Date(c.savedAt).toLocaleString()}`);
|
|
21
|
-
// eslint-disable-next-line no-console
|
|
22
|
-
console.log(`Key tail: ${dim('…' + c.apiKey.slice(-4))}`);
|
|
23
|
-
}
|