@ashulab/agent-forge 0.2.0 → 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 +2 -1
- package/dist/launcher.cjs +135 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ By default the installation token carries the full installation scope. Narrow it
|
|
|
87
87
|
|
|
88
88
|
Omitting the `permissions` key entirely is the safe default — the token then carries whatever the App grants. `repositories` works the same way: list it and the token can only touch those repos.
|
|
89
89
|
|
|
90
|
-
On launch, `agent-forge` prints the scope GitHub actually granted (`
|
|
90
|
+
On launch, `agent-forge` prints the scope GitHub actually granted (`scope` row in the summary, alongside the `account` the token is for); `agent-forge token --agent <name>` prints it to stderr. If a call fails with `403 Resource not accessible by integration`, check that row first.
|
|
91
91
|
|
|
92
92
|
## Add an agent
|
|
93
93
|
|
|
@@ -124,6 +124,7 @@ Other commands:
|
|
|
124
124
|
|
|
125
125
|
```bash
|
|
126
126
|
agent-forge --list
|
|
127
|
+
agent-forge --version
|
|
127
128
|
agent-forge --help
|
|
128
129
|
```
|
|
129
130
|
|
package/dist/launcher.cjs
CHANGED
|
@@ -4017,7 +4017,7 @@ var require_jsonwebtoken = __commonJS({
|
|
|
4017
4017
|
// src/launcher.js
|
|
4018
4018
|
var import_node_child_process3 = require("node:child_process");
|
|
4019
4019
|
var import_node_path4 = require("node:path");
|
|
4020
|
-
var
|
|
4020
|
+
var import_node_util4 = require("node:util");
|
|
4021
4021
|
|
|
4022
4022
|
// node_modules/.pnpm/@clack+core@1.4.3/node_modules/@clack/core/dist/index.mjs
|
|
4023
4023
|
var import_node_util = require("node:util");
|
|
@@ -5409,19 +5409,21 @@ function currentRepoSlug() {
|
|
|
5409
5409
|
async function installationForRepo(slug, jwtToken) {
|
|
5410
5410
|
try {
|
|
5411
5411
|
const data = await githubApi(`/repos/${slug.owner}/${slug.repo}/installation`, { jwtToken });
|
|
5412
|
-
return String(data.id);
|
|
5412
|
+
return { id: String(data.id), account: data.account?.login || data.account?.slug || null };
|
|
5413
5413
|
} catch {
|
|
5414
5414
|
return null;
|
|
5415
5415
|
}
|
|
5416
5416
|
}
|
|
5417
|
-
async function
|
|
5418
|
-
if (agent.installationId) return String(agent.installationId);
|
|
5417
|
+
async function resolveInstallation(agent, jwtToken) {
|
|
5418
|
+
if (agent.installationId) return { id: String(agent.installationId), account: null };
|
|
5419
5419
|
const slug = currentRepoSlug();
|
|
5420
5420
|
if (slug) {
|
|
5421
|
-
const
|
|
5422
|
-
if (
|
|
5421
|
+
const hit = await installationForRepo(slug, jwtToken);
|
|
5422
|
+
if (hit) return hit;
|
|
5423
5423
|
}
|
|
5424
|
-
|
|
5424
|
+
const installations = await listInstallations(jwtToken);
|
|
5425
|
+
const id = pickInstallation(installations, agent);
|
|
5426
|
+
return { id, account: installations.find((item) => item.id === id)?.account ?? null };
|
|
5425
5427
|
}
|
|
5426
5428
|
function buildTokenScope(agent) {
|
|
5427
5429
|
const scope = {};
|
|
@@ -5432,13 +5434,15 @@ function buildTokenScope(agent) {
|
|
|
5432
5434
|
async function generateGithubAppToken(agent) {
|
|
5433
5435
|
const privateKey = readPrivateKey(agent.privateKeyPath, agent.name || agent.appId);
|
|
5434
5436
|
const jwtToken = signAppJwt(agent.appId, privateKey);
|
|
5435
|
-
const installationId = await
|
|
5437
|
+
const { id: installationId, account } = await resolveInstallation(agent, jwtToken);
|
|
5436
5438
|
const data = await githubApi(`/app/installations/${installationId}/access_tokens`, {
|
|
5437
5439
|
jwtToken,
|
|
5438
5440
|
body: buildTokenScope(agent)
|
|
5439
5441
|
});
|
|
5440
5442
|
return {
|
|
5441
5443
|
token: data.token,
|
|
5444
|
+
account,
|
|
5445
|
+
expiresAt: data.expires_at || null,
|
|
5442
5446
|
// What GitHub actually granted, after both the App's config and our scoping.
|
|
5443
5447
|
permissions: data.permissions || {},
|
|
5444
5448
|
repositorySelection: data.repository_selection || "all"
|
|
@@ -5515,10 +5519,97 @@ function syncIdentityFile(providerName, agent, cwd = process.cwd()) {
|
|
|
5515
5519
|
return file;
|
|
5516
5520
|
}
|
|
5517
5521
|
|
|
5522
|
+
// src/format.js
|
|
5523
|
+
function formatScope(permissions, full = false) {
|
|
5524
|
+
const entries = Object.entries(permissions);
|
|
5525
|
+
if (entries.length === 0) return "full installation scope";
|
|
5526
|
+
if (full) return entries.map(([name, level]) => `${name}:${level}`).join(" ");
|
|
5527
|
+
const writes = entries.filter(([, level]) => level !== "read").map(([name]) => name);
|
|
5528
|
+
if (writes.length === 0) return `read-only (${entries.length})`;
|
|
5529
|
+
const reads = entries.length - writes.length;
|
|
5530
|
+
return `write: ${writes.join(", ")}${reads ? ` \xB7 +${reads} read` : ""}`;
|
|
5531
|
+
}
|
|
5532
|
+
function formatExpiry(iso) {
|
|
5533
|
+
const ms = new Date(iso).getTime() - Date.now();
|
|
5534
|
+
if (!Number.isFinite(ms)) return String(iso);
|
|
5535
|
+
const minutes = Math.round(ms / 6e4);
|
|
5536
|
+
return minutes > 0 ? `~${minutes}m` : "expired";
|
|
5537
|
+
}
|
|
5538
|
+
|
|
5518
5539
|
// src/cli.js
|
|
5540
|
+
var import_node_util3 = require("node:util");
|
|
5541
|
+
|
|
5542
|
+
// package.json
|
|
5543
|
+
var package_default = {
|
|
5544
|
+
name: "@ashulab/agent-forge",
|
|
5545
|
+
version: "0.3.0",
|
|
5546
|
+
description: "Give claude/codex/antigravity their own GitHub identity: a bot account, a scoped token minted per run, git attribution to match",
|
|
5547
|
+
license: "MIT",
|
|
5548
|
+
author: "Diego Ghersi <ghersidl@gmail.com>",
|
|
5549
|
+
type: "module",
|
|
5550
|
+
bin: {
|
|
5551
|
+
"agent-forge": "dist/launcher.cjs"
|
|
5552
|
+
},
|
|
5553
|
+
main: "dist/launcher.cjs",
|
|
5554
|
+
files: [
|
|
5555
|
+
"dist",
|
|
5556
|
+
"schema",
|
|
5557
|
+
"LICENSE",
|
|
5558
|
+
"README.md"
|
|
5559
|
+
],
|
|
5560
|
+
engines: {
|
|
5561
|
+
node: ">=24"
|
|
5562
|
+
},
|
|
5563
|
+
packageManager: "pnpm@11.20.0",
|
|
5564
|
+
repository: {
|
|
5565
|
+
type: "git",
|
|
5566
|
+
url: "git+https://github.com/AshuLab/agent-forge.git"
|
|
5567
|
+
},
|
|
5568
|
+
bugs: {
|
|
5569
|
+
url: "https://github.com/AshuLab/agent-forge/issues"
|
|
5570
|
+
},
|
|
5571
|
+
homepage: "https://github.com/AshuLab/agent-forge#readme",
|
|
5572
|
+
keywords: [
|
|
5573
|
+
"github-app",
|
|
5574
|
+
"cli",
|
|
5575
|
+
"coding-agent",
|
|
5576
|
+
"claude",
|
|
5577
|
+
"codex",
|
|
5578
|
+
"antigravity",
|
|
5579
|
+
"installation-token",
|
|
5580
|
+
"git-identity"
|
|
5581
|
+
],
|
|
5582
|
+
publishConfig: {
|
|
5583
|
+
access: "public"
|
|
5584
|
+
},
|
|
5585
|
+
scripts: {
|
|
5586
|
+
dev: "node src/launcher.js",
|
|
5587
|
+
test: "node --test",
|
|
5588
|
+
start: "node dist/launcher.cjs",
|
|
5589
|
+
build: "esbuild src/launcher.js --bundle --platform=node --format=cjs --target=node24 --outfile=dist/launcher.cjs --banner:js='#!/usr/bin/env node' && chmod +x dist/launcher.cjs",
|
|
5590
|
+
prepublishOnly: "pnpm build && pnpm test"
|
|
5591
|
+
},
|
|
5592
|
+
dependencies: {
|
|
5593
|
+
"@clack/prompts": "^1.7.0",
|
|
5594
|
+
jsonwebtoken: "^9.0.3"
|
|
5595
|
+
},
|
|
5596
|
+
devDependencies: {
|
|
5597
|
+
esbuild: "^0.28.2"
|
|
5598
|
+
}
|
|
5599
|
+
};
|
|
5600
|
+
|
|
5601
|
+
// src/cli.js
|
|
5602
|
+
var LOGO = `
|
|
5603
|
+
\u2584\u2584 \u2584\u2584\u2584 \u2584\u2584\u2584 \u2584\u2584 \u2584\u2584\u2584 \u2584\u2584\u2584 \u2584\u2584 \u2584\u2584 \u2584\u2584\u2584 \u2584\u2584\u2584
|
|
5604
|
+
\u2588\u2584\u2588 \u2588 \u2584 \u2588\u2584 \u2588 \u2588 \u2588 \u2588\u2584 \u2588 \u2588 \u2588\u2584\u2580 \u2588 \u2584 \u2588\u2584
|
|
5605
|
+
\u2588 \u2588 \u2580\u2584\u2588 \u2588\u2584\u2584 \u2588 \u2588 \u2588 \u2588 \u2580\u2584\u2580 \u2588 \u2588 \u2580\u2584\u2588 \u2588\u2584\u2584
|
|
5606
|
+
`;
|
|
5607
|
+
function printVersion() {
|
|
5608
|
+
console.log(package_default.version);
|
|
5609
|
+
}
|
|
5519
5610
|
function printHelp() {
|
|
5520
5611
|
console.log(`
|
|
5521
|
-
Agent Forge
|
|
5612
|
+
Agent Forge v${package_default.version}
|
|
5522
5613
|
|
|
5523
5614
|
Usage:
|
|
5524
5615
|
agent-forge
|
|
@@ -5526,6 +5617,7 @@ Usage:
|
|
|
5526
5617
|
agent-forge add guided setup for a new agent
|
|
5527
5618
|
agent-forge token --agent <name> print a fresh GitHub App token (for refresh)
|
|
5528
5619
|
agent-forge --list
|
|
5620
|
+
agent-forge --version
|
|
5529
5621
|
agent-forge --help
|
|
5530
5622
|
|
|
5531
5623
|
Requirements:
|
|
@@ -5539,10 +5631,13 @@ Config resolution (first match wins):
|
|
|
5539
5631
|
- ./agents.json (only if it already exists)
|
|
5540
5632
|
`);
|
|
5541
5633
|
}
|
|
5634
|
+
function bail(message = "Cancelled") {
|
|
5635
|
+
cancel(`\u2716 ${message}`);
|
|
5636
|
+
process.exit(0);
|
|
5637
|
+
}
|
|
5542
5638
|
function keep(value) {
|
|
5543
5639
|
if (isCancel(value)) {
|
|
5544
|
-
|
|
5545
|
-
process.exit(0);
|
|
5640
|
+
bail();
|
|
5546
5641
|
}
|
|
5547
5642
|
return value;
|
|
5548
5643
|
}
|
|
@@ -5614,7 +5709,7 @@ async function interactiveSelection(defaults = {}) {
|
|
|
5614
5709
|
if (agents.length === 0 && !defaults.agent) {
|
|
5615
5710
|
note("No agents configured yet. Each agent is a GitHub App identity the launcher runs under.", "First run");
|
|
5616
5711
|
if (!keep(await confirm({ message: "Add one now?" }))) {
|
|
5617
|
-
|
|
5712
|
+
outro(`Run ${(0, import_node_util3.styleText)("cyan", "agent-forge add")} when you're ready.`);
|
|
5618
5713
|
process.exit(0);
|
|
5619
5714
|
}
|
|
5620
5715
|
await addAgentWizard({ embedded: true });
|
|
@@ -5644,9 +5739,9 @@ async function interactiveSelection(defaults = {}) {
|
|
|
5644
5739
|
})
|
|
5645
5740
|
);
|
|
5646
5741
|
}
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5742
|
+
const launchPrompt = `Launch ${(0, import_node_util3.styleText)("green", agentChoice)} with ${(0, import_node_util3.styleText)(["cyan", "bold"], providerChoice)}?`;
|
|
5743
|
+
if (!keep(await confirm({ message: launchPrompt }))) {
|
|
5744
|
+
bail("Cancelled \u2014 nothing launched");
|
|
5650
5745
|
}
|
|
5651
5746
|
return { agentName: agentChoice, providerName: providerChoice };
|
|
5652
5747
|
}
|
|
@@ -5655,6 +5750,7 @@ function parseArgs() {
|
|
|
5655
5750
|
const result = {
|
|
5656
5751
|
command: args[0] && !args[0].startsWith("-") ? args[0] : void 0,
|
|
5657
5752
|
help: false,
|
|
5753
|
+
version: false,
|
|
5658
5754
|
list: false,
|
|
5659
5755
|
agent: void 0,
|
|
5660
5756
|
provider: void 0
|
|
@@ -5664,6 +5760,7 @@ function parseArgs() {
|
|
|
5664
5760
|
const arg = args[i2];
|
|
5665
5761
|
const next = args[i2 + 1];
|
|
5666
5762
|
if (arg === "--help" || arg === "-h") result.help = true;
|
|
5763
|
+
if (arg === "--version" || arg === "-v") result.version = true;
|
|
5667
5764
|
if (arg === "--list" || arg === "-l") result.list = true;
|
|
5668
5765
|
if (arg === "--agent" || arg === "-a") {
|
|
5669
5766
|
result.agent = value(next);
|
|
@@ -5677,7 +5774,8 @@ function parseArgs() {
|
|
|
5677
5774
|
return result;
|
|
5678
5775
|
}
|
|
5679
5776
|
function showIntro() {
|
|
5680
|
-
|
|
5777
|
+
console.log((0, import_node_util3.styleText)("cyan", LOGO));
|
|
5778
|
+
intro(`Agent Forge ${(0, import_node_util3.styleText)("dim", `v${package_default.version}`)}`);
|
|
5681
5779
|
}
|
|
5682
5780
|
|
|
5683
5781
|
// src/launcher.js
|
|
@@ -5703,10 +5801,10 @@ async function launchAgent(agentName, providerName) {
|
|
|
5703
5801
|
const providerInfo = getProviderInfo(providerName, agent);
|
|
5704
5802
|
const s = spinner();
|
|
5705
5803
|
s.start("Minting installation token");
|
|
5706
|
-
const { token: githubToken, permissions, repositorySelection } = await generateGithubAppToken(agent);
|
|
5804
|
+
const { token: githubToken, account, expiresAt, permissions, repositorySelection } = await generateGithubAppToken(agent);
|
|
5707
5805
|
const botId = await resolveBotId(agent);
|
|
5708
5806
|
const gitIdentity = buildGitIdentity(agent, botId);
|
|
5709
|
-
s.stop(`Token ready for ${(0,
|
|
5807
|
+
s.stop(`Token ready for ${(0, import_node_util4.styleText)("green", gitIdentity.GIT_AUTHOR_NAME)}`);
|
|
5710
5808
|
const identityFile = syncIdentityFile(providerName, agent);
|
|
5711
5809
|
const runtimeEnv = {
|
|
5712
5810
|
...process.env,
|
|
@@ -5714,18 +5812,24 @@ async function launchAgent(agentName, providerName) {
|
|
|
5714
5812
|
GH_TOKEN: githubToken,
|
|
5715
5813
|
...gitIdentity
|
|
5716
5814
|
};
|
|
5717
|
-
const
|
|
5718
|
-
const scopeText =
|
|
5719
|
-
const
|
|
5815
|
+
const narrowed = Boolean(agent.permissions) || Boolean(agent.repositories);
|
|
5816
|
+
const scopeText = formatScope(permissions, narrowed);
|
|
5817
|
+
const repoSlug = currentRepoSlug();
|
|
5818
|
+
const row = (label, value) => `${(0, import_node_util4.styleText)("dim", label.padEnd(10))}${value}`;
|
|
5720
5819
|
const summary = [
|
|
5721
|
-
row("provider", (0,
|
|
5722
|
-
row("identity", (0,
|
|
5723
|
-
row("token", (0, import_node_util3.styleText)("dim", repositorySelection === "selected" ? `${scopeText} \xB7 selected repos` : scopeText))
|
|
5820
|
+
row("provider", (0, import_node_util4.styleText)(["cyan", "bold"], providerName)),
|
|
5821
|
+
row("identity", (0, import_node_util4.styleText)("green", gitIdentity.GIT_AUTHOR_NAME))
|
|
5724
5822
|
];
|
|
5823
|
+
if (account) summary.push(row("account", (0, import_node_util4.styleText)("yellow", account)));
|
|
5824
|
+
if (repoSlug) summary.push(row("repo", (0, import_node_util4.styleText)("dim", `${repoSlug.owner}/${repoSlug.repo}`)));
|
|
5825
|
+
summary.push(
|
|
5826
|
+
row("scope", (0, import_node_util4.styleText)("dim", repositorySelection === "selected" ? `${scopeText} \xB7 selected repos` : scopeText))
|
|
5827
|
+
);
|
|
5828
|
+
if (expiresAt) summary.push(row("expires", (0, import_node_util4.styleText)("dim", formatExpiry(expiresAt))));
|
|
5725
5829
|
if (identityFile) {
|
|
5726
|
-
summary.push(row("memory", (0,
|
|
5830
|
+
summary.push(row("memory", (0, import_node_util4.styleText)("dim", (0, import_node_path4.relative)(process.cwd(), identityFile) || identityFile)));
|
|
5727
5831
|
}
|
|
5728
|
-
note(summary.join("\n"), (0,
|
|
5832
|
+
note(summary.join("\n"), (0, import_node_util4.styleText)("bold", agent.label || agent.name));
|
|
5729
5833
|
const providerArgs = getProviderPromptArgs(providerName, agent);
|
|
5730
5834
|
const child = (0, import_node_child_process3.spawn)(providerInfo.command, providerArgs, {
|
|
5731
5835
|
stdio: "inherit",
|
|
@@ -5739,7 +5843,7 @@ async function launchAgent(agentName, providerName) {
|
|
|
5739
5843
|
cancel(`Could not execute ${providerInfo.command}: ${error.message}`);
|
|
5740
5844
|
process.exit(1);
|
|
5741
5845
|
});
|
|
5742
|
-
outro(`Running ${(0,
|
|
5846
|
+
outro(`Running ${(0, import_node_util4.styleText)("cyan", providerName)}\u2026`);
|
|
5743
5847
|
}
|
|
5744
5848
|
async function main() {
|
|
5745
5849
|
const flags = parseArgs();
|
|
@@ -5747,6 +5851,10 @@ async function main() {
|
|
|
5747
5851
|
printHelp();
|
|
5748
5852
|
return;
|
|
5749
5853
|
}
|
|
5854
|
+
if (flags.version) {
|
|
5855
|
+
printVersion();
|
|
5856
|
+
return;
|
|
5857
|
+
}
|
|
5750
5858
|
if (flags.list) {
|
|
5751
5859
|
listAgents();
|
|
5752
5860
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashulab/agent-forge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Give claude/codex/antigravity their own GitHub identity: a bot account, a scoped token minted per run, git attribution to match",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Diego Ghersi <ghersidl@gmail.com>",
|