@anyforge/control 0.2.2 → 0.2.3
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/commands/init.js +52 -9
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
|
+
import { execFileSync } from 'child_process';
|
|
2
3
|
import { homedir } from 'os';
|
|
3
4
|
import { join } from 'path';
|
|
4
5
|
import open from 'open';
|
|
@@ -14,8 +15,18 @@ export async function runInit(opts) {
|
|
|
14
15
|
console.log(bold('AnyForge Control — device authorisation'));
|
|
15
16
|
// eslint-disable-next-line no-console
|
|
16
17
|
console.log(dim(` endpoint: ${endpoint}`));
|
|
17
|
-
// 1. Issue a one-time code.
|
|
18
|
-
|
|
18
|
+
// 1. Issue a one-time code. We detect the git remote up-front when
|
|
19
|
+
// running in a repo so it can be attached to the API key later.
|
|
20
|
+
// Detection happens before scope-pick so the server has it
|
|
21
|
+
// regardless of which scope the operator chooses.
|
|
22
|
+
const cwd0 = process.cwd();
|
|
23
|
+
const gitRoot0 = findGitRoot(cwd0);
|
|
24
|
+
const earlyRepo = gitRoot0 ? detectGitRemoteRepo(gitRoot0) : null;
|
|
25
|
+
const initRes = await fetch(`${endpoint}/api/cli/init`, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { 'content-type': 'application/json' },
|
|
28
|
+
body: JSON.stringify({ repo: earlyRepo }),
|
|
29
|
+
});
|
|
19
30
|
const init = (await initRes.json());
|
|
20
31
|
if (!init.ok || !init.data) {
|
|
21
32
|
throw new Error(`Could not start the authorisation flow: ${init.error ?? 'unknown error'}`);
|
|
@@ -50,7 +61,7 @@ export async function runInit(opts) {
|
|
|
50
61
|
// contract and avoids accidentally writing settings into a
|
|
51
62
|
// subdirectory the operator didn't mean to target.
|
|
52
63
|
const cwd = process.cwd();
|
|
53
|
-
const repoRoot = (
|
|
64
|
+
const repoRoot = findGitRoot(cwd);
|
|
54
65
|
const scope = await pickScope(repoRoot);
|
|
55
66
|
// 6. Patch the chosen target. apiKey is inlined into the env map
|
|
56
67
|
// so Claude Code can read it without a shell restart.
|
|
@@ -146,15 +157,17 @@ async function hasClaudeCodeLogin() {
|
|
|
146
157
|
return false;
|
|
147
158
|
}
|
|
148
159
|
}
|
|
149
|
-
|
|
150
|
-
//
|
|
151
|
-
// accept either a directory or a regular file.
|
|
160
|
+
function findGitRoot(cwd) {
|
|
161
|
+
// Works from any subdirectory — git walks up the tree to find the root.
|
|
152
162
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
return execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
164
|
+
cwd,
|
|
165
|
+
encoding: 'utf8',
|
|
166
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
167
|
+
}).trim();
|
|
155
168
|
}
|
|
156
169
|
catch {
|
|
157
|
-
return
|
|
170
|
+
return null;
|
|
158
171
|
}
|
|
159
172
|
}
|
|
160
173
|
async function pickScope(repoRoot) {
|
|
@@ -186,6 +199,36 @@ function summarisePatches(patches) {
|
|
|
186
199
|
console.log(` ${dim('•')} ${p.label} ${dim('(' + p.path + ')')}`);
|
|
187
200
|
}
|
|
188
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Read the `origin` remote URL and normalise it to `owner/repo`.
|
|
204
|
+
* Handles both SSH (`git@github.com:owner/repo.git`) and HTTPS forms.
|
|
205
|
+
* Returns null when there is no remote, git is unavailable, or the
|
|
206
|
+
* URL can't be parsed into an owner/repo pair.
|
|
207
|
+
*
|
|
208
|
+
* Uses execFileSync (not exec/execSync) so no shell interpolation
|
|
209
|
+
* occurs — the args array is passed directly to the git binary.
|
|
210
|
+
*/
|
|
211
|
+
function detectGitRemoteRepo(repoRoot) {
|
|
212
|
+
try {
|
|
213
|
+
const raw = execFileSync('git', ['remote', 'get-url', 'origin'], {
|
|
214
|
+
cwd: repoRoot,
|
|
215
|
+
encoding: 'utf8',
|
|
216
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
217
|
+
}).trim();
|
|
218
|
+
// SSH: git@github.com:owner/repo.git
|
|
219
|
+
const sshMatch = raw.match(/^git@[\w.-]+:([\w.-]+\/[\w.-]+?)(?:\.git)?$/);
|
|
220
|
+
if (sshMatch)
|
|
221
|
+
return sshMatch[1];
|
|
222
|
+
// HTTPS: https://github.com/owner/repo[.git]
|
|
223
|
+
const httpsMatch = raw.match(/^https?:\/\/[\w.-]+\/([\w.-]+\/[\w.-]+?)(?:\.git)?$/);
|
|
224
|
+
if (httpsMatch)
|
|
225
|
+
return httpsMatch[1];
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
189
232
|
// Re-exported so future commands (e.g. status) can reuse the prompt
|
|
190
233
|
// shape without duplicating the question text.
|
|
191
234
|
export { prompts };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anyforge/control",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "AnyForge Control CLI — governed LLM proxy for Claude Code, Cursor, Cline, and any agent that talks to OpenAI- or Anthropic-shape APIs.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://anyforge.ai",
|