@aion0/forge 0.4.16 → 0.5.1
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 +27 -2
- package/RELEASE_NOTES.md +21 -14
- package/app/api/agents/route.ts +17 -0
- package/app/api/delivery/[id]/route.ts +62 -0
- package/app/api/delivery/route.ts +40 -0
- package/app/api/mobile-chat/route.ts +13 -7
- package/app/api/monitor/route.ts +10 -6
- package/app/api/pipelines/[id]/route.ts +16 -3
- package/app/api/tasks/route.ts +2 -1
- package/app/api/workspace/[id]/agents/route.ts +35 -0
- package/app/api/workspace/[id]/memory/route.ts +23 -0
- package/app/api/workspace/[id]/smith/route.ts +22 -0
- package/app/api/workspace/[id]/stream/route.ts +28 -0
- package/app/api/workspace/route.ts +100 -0
- package/app/global-error.tsx +10 -4
- package/app/icon.ico +0 -0
- package/app/layout.tsx +2 -2
- package/app/login/LoginForm.tsx +96 -0
- package/app/login/page.tsx +7 -98
- package/app/page.tsx +2 -2
- package/bin/forge-server.mjs +13 -1
- package/check-forge-status.sh +9 -0
- package/components/ConversationEditor.tsx +411 -0
- package/components/ConversationGraphView.tsx +347 -0
- package/components/ConversationTerminalView.tsx +303 -0
- package/components/Dashboard.tsx +36 -39
- package/components/DashboardWrapper.tsx +9 -0
- package/components/DeliveryFlowEditor.tsx +491 -0
- package/components/DeliveryList.tsx +230 -0
- package/components/DeliveryWorkspace.tsx +589 -0
- package/components/DocTerminal.tsx +10 -2
- package/components/DocsViewer.tsx +10 -2
- package/components/HelpTerminal.tsx +11 -6
- package/components/InlinePipelineView.tsx +111 -0
- package/components/MobileView.tsx +20 -0
- package/components/MonitorPanel.tsx +9 -4
- package/components/NewTaskModal.tsx +32 -0
- package/components/PipelineEditor.tsx +49 -6
- package/components/PipelineView.tsx +482 -64
- package/components/ProjectDetail.tsx +314 -56
- package/components/ProjectManager.tsx +49 -4
- package/components/SessionView.tsx +27 -13
- package/components/SettingsModal.tsx +790 -124
- package/components/SkillsPanel.tsx +31 -8
- package/components/TaskBoard.tsx +3 -0
- package/components/WebTerminal.tsx +257 -43
- package/components/WorkspaceTree.tsx +221 -0
- package/components/WorkspaceView.tsx +2245 -0
- package/install.sh +2 -2
- package/lib/agents/claude-adapter.ts +104 -0
- package/lib/agents/generic-adapter.ts +64 -0
- package/lib/agents/index.ts +242 -0
- package/lib/agents/types.ts +70 -0
- package/lib/artifacts.ts +106 -0
- package/lib/delivery.ts +787 -0
- package/lib/forge-skills/forge-inbox.md +37 -0
- package/lib/forge-skills/forge-send.md +40 -0
- package/lib/forge-skills/forge-status.md +32 -0
- package/lib/forge-skills/forge-workspace-sync.md +37 -0
- package/lib/help-docs/00-overview.md +7 -1
- package/lib/help-docs/01-settings.md +159 -2
- package/lib/help-docs/05-pipelines.md +89 -0
- package/lib/help-docs/07-projects.md +35 -1
- package/lib/help-docs/11-workspace.md +254 -0
- package/lib/help-docs/CLAUDE.md +7 -2
- package/lib/init.ts +60 -10
- package/lib/pipeline.ts +537 -1
- package/lib/settings.ts +115 -22
- package/lib/skills.ts +249 -372
- package/lib/task-manager.ts +113 -33
- package/lib/telegram-bot.ts +33 -1
- package/lib/workspace/__tests__/state-machine.test.ts +388 -0
- package/lib/workspace/__tests__/workspace.test.ts +311 -0
- package/lib/workspace/agent-bus.ts +416 -0
- package/lib/workspace/agent-worker.ts +667 -0
- package/lib/workspace/backends/api-backend.ts +262 -0
- package/lib/workspace/backends/cli-backend.ts +479 -0
- package/lib/workspace/index.ts +82 -0
- package/lib/workspace/manager.ts +136 -0
- package/lib/workspace/orchestrator.ts +1914 -0
- package/lib/workspace/persistence.ts +310 -0
- package/lib/workspace/presets.ts +170 -0
- package/lib/workspace/skill-installer.ts +188 -0
- package/lib/workspace/smith-memory.ts +498 -0
- package/lib/workspace/types.ts +231 -0
- package/lib/workspace/watch-manager.ts +288 -0
- package/lib/workspace-standalone.ts +814 -0
- package/middleware.ts +1 -0
- package/next-env.d.ts +1 -1
- package/package.json +4 -1
- package/src/config/index.ts +12 -1
- package/src/core/db/database.ts +1 -0
- package/start.sh +7 -0
package/lib/settings.ts
CHANGED
|
@@ -7,26 +7,56 @@ import { getDataDir } from './dirs';
|
|
|
7
7
|
const DATA_DIR = getDataDir();
|
|
8
8
|
const SETTINGS_FILE = join(DATA_DIR, 'settings.yaml');
|
|
9
9
|
|
|
10
|
+
export interface AgentEntry {
|
|
11
|
+
// Base agent fields (for detected agents like claude, codex, aider)
|
|
12
|
+
path?: string; name?: string; enabled?: boolean;
|
|
13
|
+
flags?: string[]; taskFlags?: string; interactiveCmd?: string; resumeFlag?: string; outputFormat?: string;
|
|
14
|
+
models?: { terminal?: string; task?: string; telegram?: string; help?: string; mobile?: string };
|
|
15
|
+
skipPermissionsFlag?: string;
|
|
16
|
+
requiresTTY?: boolean;
|
|
17
|
+
// Profile fields (for profiles that extend a base agent)
|
|
18
|
+
base?: string; // base agent ID (e.g., 'claude') — makes this a profile
|
|
19
|
+
// API profile fields
|
|
20
|
+
type?: 'cli' | 'api'; // 'api' = API mode, default = 'cli'
|
|
21
|
+
provider?: string; // API provider (e.g., 'anthropic', 'google')
|
|
22
|
+
model?: string; // model override (for both CLI and API profiles)
|
|
23
|
+
apiKey?: string; // per-profile API key (encrypted)
|
|
24
|
+
env?: Record<string, string>; // environment variables injected when spawning CLI
|
|
25
|
+
cliType?: 'claude-code' | 'codex' | 'aider' | 'generic'; // CLI tool type — determines session support, resume flags, etc.
|
|
26
|
+
profile?: string; // linked profile ID — overrides model, env, etc. when launching
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ProviderEntry {
|
|
30
|
+
apiKey?: string; // encrypted, fallback to env var
|
|
31
|
+
defaultModel?: string;
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
10
35
|
export interface Settings {
|
|
11
|
-
projectRoots: string[];
|
|
12
|
-
docRoots: string[];
|
|
13
|
-
claudePath: string;
|
|
14
|
-
claudeHome: string;
|
|
15
|
-
telegramBotToken: string;
|
|
16
|
-
telegramChatId: string;
|
|
17
|
-
notifyOnComplete: boolean;
|
|
18
|
-
notifyOnFailure: boolean;
|
|
19
|
-
tunnelAutoStart: boolean;
|
|
20
|
-
telegramTunnelPassword: string;
|
|
21
|
-
taskModel: string;
|
|
22
|
-
pipelineModel: string;
|
|
23
|
-
telegramModel: string;
|
|
24
|
-
skipPermissions: boolean;
|
|
25
|
-
notificationRetentionDays: number;
|
|
26
|
-
skillsRepoUrl: string;
|
|
27
|
-
displayName: string;
|
|
28
|
-
displayEmail: string;
|
|
29
|
-
favoriteProjects: string[];
|
|
36
|
+
projectRoots: string[];
|
|
37
|
+
docRoots: string[];
|
|
38
|
+
claudePath: string;
|
|
39
|
+
claudeHome: string;
|
|
40
|
+
telegramBotToken: string;
|
|
41
|
+
telegramChatId: string;
|
|
42
|
+
notifyOnComplete: boolean;
|
|
43
|
+
notifyOnFailure: boolean;
|
|
44
|
+
tunnelAutoStart: boolean;
|
|
45
|
+
telegramTunnelPassword: string;
|
|
46
|
+
taskModel: string;
|
|
47
|
+
pipelineModel: string;
|
|
48
|
+
telegramModel: string;
|
|
49
|
+
skipPermissions: boolean;
|
|
50
|
+
notificationRetentionDays: number;
|
|
51
|
+
skillsRepoUrl: string;
|
|
52
|
+
displayName: string;
|
|
53
|
+
displayEmail: string;
|
|
54
|
+
favoriteProjects: string[];
|
|
55
|
+
defaultAgent: string;
|
|
56
|
+
telegramAgent: string;
|
|
57
|
+
docsAgent: string;
|
|
58
|
+
agents: Record<string, AgentEntry>;
|
|
59
|
+
providers: Record<string, ProviderEntry>; // API provider configs
|
|
30
60
|
}
|
|
31
61
|
|
|
32
62
|
const defaults: Settings = {
|
|
@@ -49,20 +79,65 @@ const defaults: Settings = {
|
|
|
49
79
|
displayName: 'Forge',
|
|
50
80
|
displayEmail: '',
|
|
51
81
|
favoriteProjects: [],
|
|
82
|
+
defaultAgent: 'claude',
|
|
83
|
+
telegramAgent: '',
|
|
84
|
+
docsAgent: '',
|
|
85
|
+
agents: {},
|
|
86
|
+
providers: {},
|
|
52
87
|
};
|
|
53
88
|
|
|
89
|
+
/** Decrypt nested apiKey fields in agents and providers */
|
|
90
|
+
function decryptNestedSecrets(settings: Settings): void {
|
|
91
|
+
// Decrypt provider apiKeys
|
|
92
|
+
if (settings.providers) {
|
|
93
|
+
for (const p of Object.values(settings.providers)) {
|
|
94
|
+
if (p.apiKey && isEncrypted(p.apiKey)) {
|
|
95
|
+
p.apiKey = decryptSecret(p.apiKey);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Decrypt agent profile apiKeys
|
|
100
|
+
if (settings.agents) {
|
|
101
|
+
for (const a of Object.values(settings.agents)) {
|
|
102
|
+
if (a.apiKey && isEncrypted(a.apiKey)) {
|
|
103
|
+
a.apiKey = decryptSecret(a.apiKey);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Encrypt nested apiKey fields in agents and providers */
|
|
110
|
+
function encryptNestedSecrets(settings: Settings): void {
|
|
111
|
+
if (settings.providers) {
|
|
112
|
+
for (const p of Object.values(settings.providers)) {
|
|
113
|
+
if (p.apiKey && !isEncrypted(p.apiKey)) {
|
|
114
|
+
p.apiKey = encryptSecret(p.apiKey);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (settings.agents) {
|
|
119
|
+
for (const a of Object.values(settings.agents)) {
|
|
120
|
+
if (a.apiKey && !isEncrypted(a.apiKey)) {
|
|
121
|
+
a.apiKey = encryptSecret(a.apiKey);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
54
127
|
/** Load settings with secrets decrypted (for internal use) */
|
|
55
128
|
export function loadSettings(): Settings {
|
|
56
129
|
if (!existsSync(SETTINGS_FILE)) return { ...defaults };
|
|
57
130
|
try {
|
|
58
131
|
const raw = readFileSync(SETTINGS_FILE, 'utf-8');
|
|
59
132
|
const parsed = { ...defaults, ...YAML.parse(raw) };
|
|
60
|
-
// Decrypt secret fields
|
|
133
|
+
// Decrypt top-level secret fields
|
|
61
134
|
for (const field of SECRET_FIELDS) {
|
|
62
135
|
if (parsed[field] && isEncrypted(parsed[field])) {
|
|
63
136
|
parsed[field] = decryptSecret(parsed[field]);
|
|
64
137
|
}
|
|
65
138
|
}
|
|
139
|
+
// Decrypt nested apiKeys
|
|
140
|
+
decryptNestedSecrets(parsed);
|
|
66
141
|
return parsed;
|
|
67
142
|
} catch {
|
|
68
143
|
return { ...defaults };
|
|
@@ -77,6 +152,21 @@ export function loadSettingsMasked(): Settings & { _secretStatus: Record<string,
|
|
|
77
152
|
status[field] = !!settings[field];
|
|
78
153
|
settings[field] = settings[field] ? '••••••••' : '';
|
|
79
154
|
}
|
|
155
|
+
// Mask nested apiKeys
|
|
156
|
+
if (settings.providers) {
|
|
157
|
+
for (const [name, p] of Object.entries(settings.providers)) {
|
|
158
|
+
status[`providers.${name}.apiKey`] = !!p.apiKey;
|
|
159
|
+
p.apiKey = p.apiKey ? '••••••••' : '';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (settings.agents) {
|
|
163
|
+
for (const [name, a] of Object.entries(settings.agents)) {
|
|
164
|
+
if (a.apiKey) {
|
|
165
|
+
status[`agents.${name}.apiKey`] = true;
|
|
166
|
+
a.apiKey = '••••••••';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
80
170
|
return { ...settings, _secretStatus: status };
|
|
81
171
|
}
|
|
82
172
|
|
|
@@ -84,13 +174,16 @@ export function loadSettingsMasked(): Settings & { _secretStatus: Record<string,
|
|
|
84
174
|
export function saveSettings(settings: Settings) {
|
|
85
175
|
const dir = dirname(SETTINGS_FILE);
|
|
86
176
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
87
|
-
//
|
|
88
|
-
const toSave =
|
|
177
|
+
// Deep copy to avoid mutating original
|
|
178
|
+
const toSave = JSON.parse(JSON.stringify(settings));
|
|
179
|
+
// Encrypt top-level secret fields
|
|
89
180
|
for (const field of SECRET_FIELDS) {
|
|
90
181
|
if (toSave[field] && !isEncrypted(toSave[field])) {
|
|
91
182
|
toSave[field] = encryptSecret(toSave[field]);
|
|
92
183
|
}
|
|
93
184
|
}
|
|
185
|
+
// Encrypt nested apiKeys
|
|
186
|
+
encryptNestedSecrets(toSave);
|
|
94
187
|
writeFileSync(SETTINGS_FILE, YAML.stringify(toSave), 'utf-8');
|
|
95
188
|
}
|
|
96
189
|
|