@getforgeai/cli 0.1.0-beta.4 → 0.1.0-beta.5
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/cli.js +6 -2
- package/dist/commands/auth-setup-agent.d.ts +1 -0
- package/dist/commands/auth-setup-agent.js +30 -0
- package/dist/commands/config-set.js +4 -0
- package/dist/commands/connect.js +5 -5
- package/dist/config/config-manager.d.ts +2 -0
- package/dist/lib/daemon-entry.js +5 -5
- package/dist/orchestrator/agent-profiles.d.ts +10 -1
- package/dist/orchestrator/agent-profiles.js +83 -0
- package/dist/orchestrator/connectors/docker-connector.js +3 -1
- package/dist/vm/agent-auth-manager.d.ts +1 -1
- package/dist/vm/agent-auth-manager.js +15 -3
- package/dist/vm/session-manager.js +15 -1
- package/package.json +2 -2
- package/rootfs/Dockerfile +18 -16
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import { sessionRespondAction } from "./commands/session-respond.js";
|
|
|
20
20
|
import { taskOpenAction } from "./commands/task-open.js";
|
|
21
21
|
import { workflowOpenAction } from "./commands/workflow-open.js";
|
|
22
22
|
import { vmInitAction, vmStatusAction, vmCleanupAction, vmStopAllAction, setupClaudeTokenAction, } from "./commands/vm.js";
|
|
23
|
-
import { setupCodexAuthAction, setupOpenCodeAuthAction, } from "./commands/auth-setup-agent.js";
|
|
23
|
+
import { setupCodexAuthAction, setupKiloAuthAction, setupOpenCodeAuthAction, } from "./commands/auth-setup-agent.js";
|
|
24
24
|
const program = new Command();
|
|
25
25
|
program
|
|
26
26
|
.name("forge")
|
|
@@ -55,6 +55,10 @@ authCmd
|
|
|
55
55
|
.command("setup-opencode")
|
|
56
56
|
.description("Store OpenCode credentials (import opencode login or provider API key)")
|
|
57
57
|
.action(() => setupOpenCodeAuthAction());
|
|
58
|
+
authCmd
|
|
59
|
+
.command("setup-kilo")
|
|
60
|
+
.description("Store Kilo credentials (import kilo login or gateway API key)")
|
|
61
|
+
.action(() => setupKiloAuthAction());
|
|
58
62
|
program
|
|
59
63
|
.command("connect")
|
|
60
64
|
.description("Connect to ForgeAI Cloud (spawns background daemon by default)")
|
|
@@ -123,7 +127,7 @@ const configSetCmd = configCmd
|
|
|
123
127
|
.description("Set a configuration value");
|
|
124
128
|
configSetCmd
|
|
125
129
|
.command("agent-type <value>")
|
|
126
|
-
.description("Set agent type (CLAUDE_CODE, CODEX or
|
|
130
|
+
.description("Set agent type (CLAUDE_CODE, CODEX, OPENCODE or KILO)")
|
|
127
131
|
.action((value) => configSetAgentTypeAction(value));
|
|
128
132
|
configSetCmd
|
|
129
133
|
.command("agent-slots <value>")
|
|
@@ -11,5 +11,6 @@
|
|
|
11
11
|
* encrypted file elsewhere) and injected into agent containers at spawn.
|
|
12
12
|
*/
|
|
13
13
|
export declare function setupCodexAuthAction(): Promise<void>;
|
|
14
|
+
export declare function setupKiloAuthAction(): Promise<void>;
|
|
14
15
|
export declare function setupOpenCodeAuthAction(): Promise<void>;
|
|
15
16
|
//# sourceMappingURL=auth-setup-agent.d.ts.map
|
|
@@ -59,6 +59,36 @@ export async function setupCodexAuthAction() {
|
|
|
59
59
|
process.exit(1);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
export async function setupKiloAuthAction() {
|
|
63
|
+
try {
|
|
64
|
+
const imported = await tryImportHostAuthJson("KILO");
|
|
65
|
+
if (!imported) {
|
|
66
|
+
console.log(chalk.dim("No Kilo login found to import. You can paste a Kilo gateway API key instead\n" +
|
|
67
|
+
"(from your profile at app.kilo.ai — or run `kilo auth login` first, then\n" +
|
|
68
|
+
"re-run this command to import it)."));
|
|
69
|
+
const key = await ask("Kilo API key: ");
|
|
70
|
+
if (!key) {
|
|
71
|
+
console.error(chalk.red("No API key provided."));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
await saveAgentAuth("KILO", {
|
|
75
|
+
kind: "api_key",
|
|
76
|
+
envVar: "KILO_API_KEY",
|
|
77
|
+
value: key,
|
|
78
|
+
});
|
|
79
|
+
console.log(chalk.green("Kilo API key stored. Agents can now authenticate."));
|
|
80
|
+
}
|
|
81
|
+
const model = await ask("Kilo model (e.g. kilo/anthropic/claude-sonnet-4.5) — leave empty for Kilo's default: ");
|
|
82
|
+
if (model) {
|
|
83
|
+
setConfig({ kiloModel: model });
|
|
84
|
+
console.log(chalk.green(`Kilo model set to ${model}.`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
console.error(chalk.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
62
92
|
const OPENCODE_PROVIDER_ENV_VARS = {
|
|
63
93
|
anthropic: "ANTHROPIC_API_KEY",
|
|
64
94
|
openai: "OPENAI_API_KEY",
|
|
@@ -7,6 +7,7 @@ const VALID_OPENERS = ["terminal", "vscode", "finder"];
|
|
|
7
7
|
const AGENT_AUTH_SETUP_HINTS = {
|
|
8
8
|
CODEX: "forge auth setup-codex",
|
|
9
9
|
OPENCODE: "forge auth setup-opencode",
|
|
10
|
+
KILO: "forge auth setup-kilo",
|
|
10
11
|
};
|
|
11
12
|
export async function configSetAgentTypeAction(value) {
|
|
12
13
|
const upper = value.toUpperCase();
|
|
@@ -51,6 +52,9 @@ export function configGetAction() {
|
|
|
51
52
|
if (config.agentType === "OPENCODE") {
|
|
52
53
|
console.log(` OpenCode model: ${config.opencodeModel ?? "(OpenCode default)"}`);
|
|
53
54
|
}
|
|
55
|
+
if (config.agentType === "KILO") {
|
|
56
|
+
console.log(` Kilo model: ${config.kiloModel ?? "(Kilo default)"}`);
|
|
57
|
+
}
|
|
54
58
|
console.log(` Agent slots: ${config.agentSlots ?? 5}`);
|
|
55
59
|
console.log(` Opener: ${config.opener ?? "terminal"}`);
|
|
56
60
|
console.log(` Server URL: ${config.serverUrl}`);
|
package/dist/commands/connect.js
CHANGED
|
@@ -123,14 +123,14 @@ async function connectForeground(options) {
|
|
|
123
123
|
if (!ctx)
|
|
124
124
|
return;
|
|
125
125
|
// Ensure agent credentials are available for container authentication
|
|
126
|
-
if (ctx.agentType === "CODEX" ||
|
|
126
|
+
if (ctx.agentType === "CODEX" ||
|
|
127
|
+
ctx.agentType === "OPENCODE" ||
|
|
128
|
+
ctx.agentType === "KILO") {
|
|
127
129
|
try {
|
|
128
130
|
const { hasAgentAuth } = await import("../vm/agent-auth-manager.js");
|
|
129
131
|
if (!(await hasAgentAuth(ctx.agentType))) {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
: "forge auth setup-opencode";
|
|
133
|
-
console.warn(chalk.yellow(`[connect] No ${ctx.agentType} credentials stored. Agents may fail to authenticate. Run: ${setupCmd}`));
|
|
132
|
+
const { getAgentProfile } = await import("../orchestrator/agent-profiles.js");
|
|
133
|
+
console.warn(chalk.yellow(`[connect] No ${ctx.agentType} credentials stored. Agents may fail to authenticate. Run: ${getAgentProfile(ctx.agentType).authSetupHint}`));
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
catch {
|
|
@@ -13,6 +13,8 @@ export type CliConfig = {
|
|
|
13
13
|
agentType?: string;
|
|
14
14
|
/** OpenCode model in "provider/model" form (used when agentType is OPENCODE). */
|
|
15
15
|
opencodeModel?: string;
|
|
16
|
+
/** Kilo model, e.g. "kilo/anthropic/claude-sonnet-4.5" (used when agentType is KILO). */
|
|
17
|
+
kiloModel?: string;
|
|
16
18
|
agentSlots?: number;
|
|
17
19
|
opener?: Opener;
|
|
18
20
|
lastSyncCursor?: string;
|
package/dist/lib/daemon-entry.js
CHANGED
|
@@ -18,14 +18,14 @@ if (!ctx) {
|
|
|
18
18
|
}
|
|
19
19
|
// Check agent credential availability for the configured engine
|
|
20
20
|
try {
|
|
21
|
-
if (ctx.agentType === "CODEX" ||
|
|
21
|
+
if (ctx.agentType === "CODEX" ||
|
|
22
|
+
ctx.agentType === "OPENCODE" ||
|
|
23
|
+
ctx.agentType === "KILO") {
|
|
22
24
|
const { hasAgentAuth } = await import("../vm/agent-auth-manager.js");
|
|
23
25
|
if (!(await hasAgentAuth(ctx.agentType))) {
|
|
24
|
-
const
|
|
25
|
-
? "forge auth setup-codex"
|
|
26
|
-
: "forge auth setup-opencode";
|
|
26
|
+
const { getAgentProfile } = await import("../orchestrator/agent-profiles.js");
|
|
27
27
|
console.warn(chalk.yellow(`[daemon] No ${ctx.agentType} credentials stored — agents will fail to authenticate.`));
|
|
28
|
-
console.warn(chalk.yellow(`[daemon] Run: ${
|
|
28
|
+
console.warn(chalk.yellow(`[daemon] Run: ${getAgentProfile(ctx.agentType).authSetupHint}`));
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
else {
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* | CLAUDE_CODE | claude | /session/workspace/.claude/mcp_config.json (via --mcp-config) | --continue |
|
|
10
10
|
* | CODEX | codex | /home/agent/.codex/config.toml | exec resume --last |
|
|
11
11
|
* | OPENCODE | opencode | /home/agent/.config/opencode/opencode.json | run --continue |
|
|
12
|
+
* | KILO | kilo | /home/agent/.config/kilo/kilo.json | run --continue |
|
|
13
|
+
*
|
|
14
|
+
* KILO (kilo.ai) is an OpenCode fork: same config shape, same JSONL event
|
|
15
|
+
* stream (so it shares OpenCodeJsonlExtractor), Kilo gateway auth on top.
|
|
12
16
|
*/
|
|
13
17
|
import type { AgentType } from "@getforgeai/protocol";
|
|
14
18
|
import type { AgentStreamExtractor, QuestionData, ToolActivityData, TokenUsageData } from "./stream-json-extractor.js";
|
|
@@ -18,7 +22,12 @@ export type McpBridgeConfig = {
|
|
|
18
22
|
token: string;
|
|
19
23
|
};
|
|
20
24
|
export type AgentConfigOptions = {
|
|
21
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Model in "provider/model" form (from CLI config, optional).
|
|
27
|
+
* OpenCode: e.g. "anthropic/claude-sonnet-4-5".
|
|
28
|
+
* Kilo: gateway models are "kilo/<vendor>/<model>", e.g.
|
|
29
|
+
* "kilo/anthropic/claude-sonnet-4.5".
|
|
30
|
+
*/
|
|
22
31
|
model?: string;
|
|
23
32
|
};
|
|
24
33
|
export type AgentAuthErrorPatterns = {
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* | CLAUDE_CODE | claude | /session/workspace/.claude/mcp_config.json (via --mcp-config) | --continue |
|
|
10
10
|
* | CODEX | codex | /home/agent/.codex/config.toml | exec resume --last |
|
|
11
11
|
* | OPENCODE | opencode | /home/agent/.config/opencode/opencode.json | run --continue |
|
|
12
|
+
* | KILO | kilo | /home/agent/.config/kilo/kilo.json | run --continue |
|
|
13
|
+
*
|
|
14
|
+
* KILO (kilo.ai) is an OpenCode fork: same config shape, same JSONL event
|
|
15
|
+
* stream (so it shares OpenCodeJsonlExtractor), Kilo gateway auth on top.
|
|
12
16
|
*/
|
|
13
17
|
import { DEFAULT_AGENT_TYPE, isAgentType } from "@getforgeai/protocol";
|
|
14
18
|
import { CodexJsonlExtractor } from "./codex-jsonl-extractor.js";
|
|
@@ -224,12 +228,88 @@ const openCodeProfile = {
|
|
|
224
228
|
authSetupHint: "forge auth setup-opencode",
|
|
225
229
|
};
|
|
226
230
|
// ---------------------------------------------------------------------------
|
|
231
|
+
// Kilo (kilo.ai — OpenCode fork with the Kilo gateway on top)
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
/**
|
|
234
|
+
* Permission override merged over any config (KILO_PERMISSION env is merged
|
|
235
|
+
* over file config), so a repo-level kilo.json can never re-introduce "ask"
|
|
236
|
+
* prompts. Belt to the --auto flag's suspenders.
|
|
237
|
+
*/
|
|
238
|
+
const KILO_PERMISSION_JSON = JSON.stringify({
|
|
239
|
+
"*": "allow",
|
|
240
|
+
external_directory: { "**": "allow" },
|
|
241
|
+
});
|
|
242
|
+
const kiloProfile = {
|
|
243
|
+
type: "KILO",
|
|
244
|
+
binary: "kilo",
|
|
245
|
+
configTargetPath: "/home/agent/.config/kilo/kilo.json",
|
|
246
|
+
buildConfig(mcp, opts) {
|
|
247
|
+
return JSON.stringify({
|
|
248
|
+
$schema: "https://app.kilo.ai/config.json",
|
|
249
|
+
...(opts?.model ? { model: opts.model } : {}),
|
|
250
|
+
permission: {
|
|
251
|
+
"*": "allow",
|
|
252
|
+
external_directory: { "**": "allow" },
|
|
253
|
+
},
|
|
254
|
+
// Headless container hygiene: no auto-upgrade, no product telemetry
|
|
255
|
+
autoupdate: false,
|
|
256
|
+
experimental: { openTelemetry: false },
|
|
257
|
+
mcp: {
|
|
258
|
+
forgeai: {
|
|
259
|
+
type: "local",
|
|
260
|
+
command: ["node", "/session/forge-mcp-relay.mjs"],
|
|
261
|
+
environment: {
|
|
262
|
+
FORGEAI_MCP_HOST: mcp.host,
|
|
263
|
+
FORGEAI_MCP_PORT: String(mcp.port),
|
|
264
|
+
FORGEAI_MCP_TOKEN: mcp.token,
|
|
265
|
+
},
|
|
266
|
+
enabled: true,
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
}, null, 2);
|
|
270
|
+
},
|
|
271
|
+
buildArgs(prompt) {
|
|
272
|
+
// --auto: auto-approve permissions (headless runs REJECT asks otherwise).
|
|
273
|
+
// Spawn must close stdin: with a non-TTY open stdin, `kilo run` blocks
|
|
274
|
+
// reading it to EOF before sending the prompt (Bun.stdin.text()).
|
|
275
|
+
return ["run", "--auto", "--format", "json", prompt];
|
|
276
|
+
},
|
|
277
|
+
buildContinueArgs(prompt) {
|
|
278
|
+
return ["run", "--continue", "--auto", "--format", "json", prompt];
|
|
279
|
+
},
|
|
280
|
+
spawnEnv: {
|
|
281
|
+
KILO_PERMISSION: KILO_PERMISSION_JSON,
|
|
282
|
+
KILO_DISABLE_AUTOUPDATE: "1",
|
|
283
|
+
KILO_TELEMETRY_LEVEL: "off",
|
|
284
|
+
},
|
|
285
|
+
// Kilo scans .claude/skills/**/SKILL.md natively (OpenCode heritage)
|
|
286
|
+
nativeSkillDiscovery: true,
|
|
287
|
+
// Sessions live in SQLite (kilo.db + -wal/-shm) keyed to the project dir
|
|
288
|
+
stateDir: "/home/agent/.local/share/kilo",
|
|
289
|
+
snapshotExcludes: ["auth.json", "mcp-auth.json", "log", "repos", "bin"],
|
|
290
|
+
authErrorPatterns: {
|
|
291
|
+
// Kilo surfaces auth failures as error events from the gateway/provider —
|
|
292
|
+
// it never prints "Not logged in" on the run path, and env-var names in
|
|
293
|
+
// the stdout tail would be tool-output false positives (ForgeAI itself
|
|
294
|
+
// injects KILO_API_KEY). Keep only API-error-shaped tokens.
|
|
295
|
+
stderr: [],
|
|
296
|
+
combined: [
|
|
297
|
+
'"authentication_error"',
|
|
298
|
+
"invalid_api_key",
|
|
299
|
+
"Incorrect API key",
|
|
300
|
+
"AuthError",
|
|
301
|
+
],
|
|
302
|
+
},
|
|
303
|
+
authSetupHint: "forge auth setup-kilo",
|
|
304
|
+
};
|
|
305
|
+
// ---------------------------------------------------------------------------
|
|
227
306
|
// Registry
|
|
228
307
|
// ---------------------------------------------------------------------------
|
|
229
308
|
const PROFILES = {
|
|
230
309
|
CLAUDE_CODE: claudeProfile,
|
|
231
310
|
CODEX: codexProfile,
|
|
232
311
|
OPENCODE: openCodeProfile,
|
|
312
|
+
KILO: kiloProfile,
|
|
233
313
|
};
|
|
234
314
|
export function getAgentProfile(agentType) {
|
|
235
315
|
return PROFILES[resolveAgentType(agentType)];
|
|
@@ -245,6 +325,9 @@ export function createStreamExtractor(agentType, onText, onQuestion, onToolActiv
|
|
|
245
325
|
case "CODEX":
|
|
246
326
|
return new CodexJsonlExtractor(onText, onQuestion, onToolActivity, onTokenUsage, sessionId);
|
|
247
327
|
case "OPENCODE":
|
|
328
|
+
// Kilo is an OpenCode fork emitting the same JSONL event stream
|
|
329
|
+
// (text/tool_use/step_start/step_finish/error with sessionID + part).
|
|
330
|
+
case "KILO":
|
|
248
331
|
return new OpenCodeJsonlExtractor(onText, onQuestion, onToolActivity, onTokenUsage, sessionId);
|
|
249
332
|
default:
|
|
250
333
|
return new StreamJsonExtractor(onText, onQuestion, onToolActivity, onTokenUsage, sessionId);
|
|
@@ -40,7 +40,9 @@ async function resolveMcpRelayHost() {
|
|
|
40
40
|
* Format and install path depend on the agent engine (see agent-profiles.ts).
|
|
41
41
|
*/
|
|
42
42
|
function buildAgentConfig(profile, sessionIndex, mcpSecret, mcpHost) {
|
|
43
|
-
|
|
43
|
+
const cliConfig = getConfig();
|
|
44
|
+
const model = profile.type === "KILO" ? cliConfig.kiloModel : cliConfig.opencodeModel;
|
|
45
|
+
return profile.buildConfig({ host: mcpHost, port: 20000 + sessionIndex, token: mcpSecret }, { model });
|
|
44
46
|
}
|
|
45
47
|
/**
|
|
46
48
|
* Bash script installing the staged config + context files into the container.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Storage: macOS Keychain / encrypted file via secure-store.ts. The payload
|
|
14
14
|
* is stored as a JSON string.
|
|
15
15
|
*/
|
|
16
|
-
export type AuthAgent = "CODEX" | "OPENCODE";
|
|
16
|
+
export type AuthAgent = "CODEX" | "OPENCODE" | "KILO";
|
|
17
17
|
export type AgentAuthPayload = {
|
|
18
18
|
kind: "api_key";
|
|
19
19
|
value: string;
|
|
@@ -30,17 +30,29 @@ const STORE_LOCATIONS = {
|
|
|
30
30
|
fileName: "opencode-auth.json",
|
|
31
31
|
fileSalt: "forgeai-opencode-auth-salt",
|
|
32
32
|
},
|
|
33
|
+
KILO: {
|
|
34
|
+
keychainService: "ForgeAI-kilo-auth",
|
|
35
|
+
keychainAccount: "kilo-auth",
|
|
36
|
+
fileName: "kilo-auth.json",
|
|
37
|
+
fileSalt: "forgeai-kilo-auth-salt",
|
|
38
|
+
},
|
|
33
39
|
};
|
|
34
40
|
/** Container path where each engine expects its auth.json. */
|
|
35
41
|
export const AGENT_AUTH_JSON_PATHS = {
|
|
36
42
|
CODEX: "/home/agent/.codex/auth.json",
|
|
37
43
|
OPENCODE: "/home/agent/.local/share/opencode/auth.json",
|
|
44
|
+
KILO: "/home/agent/.local/share/kilo/auth.json",
|
|
38
45
|
};
|
|
39
46
|
/** Host path each engine's own CLI stores its auth.json at (for import). */
|
|
40
47
|
export function getHostAuthJsonPath(agent) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
switch (agent) {
|
|
49
|
+
case "CODEX":
|
|
50
|
+
return join(homedir(), ".codex", "auth.json");
|
|
51
|
+
case "OPENCODE":
|
|
52
|
+
return join(homedir(), ".local", "share", "opencode", "auth.json");
|
|
53
|
+
case "KILO":
|
|
54
|
+
return join(homedir(), ".local", "share", "kilo", "auth.json");
|
|
55
|
+
}
|
|
44
56
|
}
|
|
45
57
|
export async function saveAgentAuth(agent, payload) {
|
|
46
58
|
await saveSecret(STORE_LOCATIONS[agent], JSON.stringify(payload));
|
|
@@ -318,6 +318,11 @@ export class SessionManager {
|
|
|
318
318
|
secretEnv: await this.getAgentSecretEnv(resolveAgentType(agentType)),
|
|
319
319
|
user: "agent",
|
|
320
320
|
});
|
|
321
|
+
// Agents run one-shot and never receive stdin input. Close it so engines
|
|
322
|
+
// that read piped stdin to EOF before acting (OpenCode and Kilo both
|
|
323
|
+
// concatenate non-TTY stdin into the prompt via Bun.stdin.text()) don't
|
|
324
|
+
// block forever on the open docker-exec pipe.
|
|
325
|
+
dockerExecProcess.stdin?.end();
|
|
321
326
|
// Create a kill function that removes the container when the process exits
|
|
322
327
|
const killFn = async (signal) => {
|
|
323
328
|
try {
|
|
@@ -359,6 +364,13 @@ export class SessionManager {
|
|
|
359
364
|
return { [auth.envVar ?? "ANTHROPIC_API_KEY"]: auth.value };
|
|
360
365
|
}
|
|
361
366
|
}
|
|
367
|
+
if (agentType === "KILO") {
|
|
368
|
+
const auth = await this.getAgentAuth("KILO");
|
|
369
|
+
if (auth.kind === "api_key") {
|
|
370
|
+
// Kilo gateway key (app.kilo.ai profile) — read at request time
|
|
371
|
+
return { [auth.envVar ?? "KILO_API_KEY"]: auth.value };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
362
374
|
return {};
|
|
363
375
|
}
|
|
364
376
|
/** Load (and cache) stored Codex/OpenCode credentials, or throw with a fix hint. */
|
|
@@ -494,7 +506,9 @@ export class SessionManager {
|
|
|
494
506
|
*/
|
|
495
507
|
async refreshToken(agentType = "CLAUDE_CODE") {
|
|
496
508
|
const resolved = resolveAgentType(agentType);
|
|
497
|
-
if (resolved === "CODEX" ||
|
|
509
|
+
if (resolved === "CODEX" ||
|
|
510
|
+
resolved === "OPENCODE" ||
|
|
511
|
+
resolved === "KILO") {
|
|
498
512
|
const previous = this.agentAuthCache[resolved];
|
|
499
513
|
delete this.agentAuthCache[resolved];
|
|
500
514
|
const fresh = await loadAgentAuth(resolved);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getforgeai/cli",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.5",
|
|
4
4
|
"description": "ForgeAI CLI — runs AI coding agents in local Docker containers with your own AI tokens, and connects them to the ForgeAI cockpit.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"ora": "^8.2.0",
|
|
48
48
|
"socket.io-client": "^4.8.3",
|
|
49
49
|
"zod": "^4.3.6",
|
|
50
|
-
"@getforgeai/protocol": "0.1.0-beta.
|
|
50
|
+
"@getforgeai/protocol": "0.1.0-beta.3"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/node": "^25.0.2",
|
package/rootfs/Dockerfile
CHANGED
|
@@ -6,23 +6,25 @@ ENV DEBIAN_FRONTEND=noninteractive
|
|
|
6
6
|
RUN apt-get update -qq && \
|
|
7
7
|
apt-get install -y -qq --no-install-recommends \
|
|
8
8
|
ca-certificates curl git openssh-client build-essential \
|
|
9
|
-
python3 procps less sudo gnupg && \
|
|
9
|
+
python3 procps less sudo gnupg passwd xz-utils && \
|
|
10
10
|
rm -rf /var/lib/apt/lists/*
|
|
11
11
|
|
|
12
|
-
# Node.js 20 LTS
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
# Node.js 20 LTS from the official nodejs.org tarball — pinned version, no
|
|
13
|
+
# NodeSource apt repo (which intermittently 403s and breaks builds).
|
|
14
|
+
ARG TARGETARCH=arm64
|
|
15
|
+
ARG NODE_VERSION=20.20.2
|
|
16
|
+
RUN NODE_ARCH=$([ "$TARGETARCH" = "amd64" ] && echo "x64" || echo "$TARGETARCH") && \
|
|
17
|
+
curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz" | \
|
|
18
|
+
tar -xJ -C /usr/local --strip-components=1 && \
|
|
19
|
+
node --version && npm --version
|
|
20
20
|
|
|
21
|
-
# Agent CLIs: Claude Code (primary), Codex and
|
|
21
|
+
# Agent CLIs: Claude Code (primary), Codex, OpenCode and Kilo (alternative engines).
|
|
22
22
|
# No output pipe: `| tail` would mask npm failures (pipeline exit = tail's).
|
|
23
23
|
# The `command -v` checks make a partial install fail the build loudly.
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
# @kilocode/cli ships its platform binary via optionalDependencies + postinstall,
|
|
25
|
+
# so scripts must stay enabled for it.
|
|
26
|
+
RUN npm install -g --loglevel=error @anthropic-ai/claude-code @openai/codex opencode-ai @kilocode/cli && \
|
|
27
|
+
command -v claude && command -v codex && command -v opencode && command -v kilo
|
|
26
28
|
|
|
27
29
|
# ForgeAI MCP relay (baked-in copy for reference only — at runtime the CLI
|
|
28
30
|
# stages its own copy into /session/forge-mcp-relay.mjs so the relay version
|
|
@@ -30,10 +32,10 @@ RUN npm install -g --loglevel=error @anthropic-ai/claude-code @openai/codex open
|
|
|
30
32
|
RUN mkdir -p /opt/forgeai
|
|
31
33
|
COPY forge-mcp-relay.mjs /opt/forgeai/forge-mcp-relay.mjs
|
|
32
34
|
|
|
33
|
-
# Create non-root user (Claude Code refuses --dangerously-skip-permissions as root)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
# Create non-root user (Claude Code refuses --dangerously-skip-permissions as root).
|
|
36
|
+
# passwd is installed with the base packages above: re-running apt-get update
|
|
37
|
+
# here hits the NodeSource repo again, which intermittently 403s and fails builds.
|
|
38
|
+
RUN useradd -m -s /bin/bash -u 1001 agent
|
|
37
39
|
|
|
38
40
|
# Working directories (owned by agent user)
|
|
39
41
|
RUN mkdir -p /workspace /session /shared && \
|