@wundam/orchex 1.0.0-rc.2 → 1.0.0-rc.21
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 +59 -18
- package/dist/cloud-executor.d.ts +71 -0
- package/dist/cloud-executor.js +335 -0
- package/dist/cloud-sync.d.ts +8 -0
- package/dist/cloud-sync.js +52 -0
- package/dist/config.d.ts +30 -4
- package/dist/config.js +61 -2
- package/dist/context-builder.d.ts +2 -0
- package/dist/context-builder.js +11 -3
- package/dist/cost.js +1 -1
- package/dist/entitlements/jwt.d.ts +7 -0
- package/dist/entitlements/jwt.js +78 -0
- package/dist/entitlements/resolve.d.ts +17 -0
- package/dist/entitlements/resolve.js +49 -0
- package/dist/entitlements/types.d.ts +21 -0
- package/dist/entitlements/types.js +4 -0
- package/dist/executors/base.d.ts +1 -1
- package/dist/executors/bedrock-executor.d.ts +39 -0
- package/dist/executors/bedrock-executor.js +197 -0
- package/dist/executors/index.d.ts +1 -0
- package/dist/executors/index.js +24 -1
- package/dist/index.js +468 -23
- package/dist/intelligence/index.d.ts +44 -0
- package/dist/intelligence/index.js +160 -0
- package/dist/key-cache.d.ts +31 -0
- package/dist/key-cache.js +84 -0
- package/dist/login-helpers.d.ts +25 -0
- package/dist/login-helpers.js +54 -0
- package/dist/manifest.js +18 -1
- package/dist/mcp-instructions.d.ts +1 -0
- package/dist/mcp-instructions.js +84 -0
- package/dist/mcp-resources.d.ts +8 -0
- package/dist/mcp-resources.js +420 -0
- package/dist/model-cache.d.ts +18 -0
- package/dist/model-cache.js +62 -0
- package/dist/model-validator.d.ts +20 -0
- package/dist/model-validator.js +125 -0
- package/dist/orchestrator.d.ts +14 -0
- package/dist/orchestrator.js +191 -32
- package/dist/setup/ide-registry.d.ts +13 -0
- package/dist/setup/ide-registry.js +51 -0
- package/dist/setup/index.d.ts +1 -0
- package/dist/setup/index.js +111 -0
- package/dist/tier-gating.js +0 -16
- package/dist/tiers.d.ts +35 -5
- package/dist/tiers.js +39 -3
- package/dist/tools.d.ts +6 -1
- package/dist/tools.js +852 -95
- package/dist/types.d.ts +71 -60
- package/dist/types.js +3 -0
- package/dist/waves.d.ts +1 -1
- package/dist/waves.js +29 -2
- package/package.json +41 -5
- package/src/entitlements/public-key.pem +9 -0
- package/dist/intelligence/anti-pattern-detector.d.ts +0 -117
- package/dist/intelligence/anti-pattern-detector.js +0 -327
- package/dist/intelligence/budget-enforcer.d.ts +0 -119
- package/dist/intelligence/budget-enforcer.js +0 -226
- package/dist/intelligence/context-optimizer.d.ts +0 -111
- package/dist/intelligence/context-optimizer.js +0 -282
- package/dist/intelligence/cost-tracker.d.ts +0 -114
- package/dist/intelligence/cost-tracker.js +0 -183
- package/dist/intelligence/deliverable-extractor.d.ts +0 -134
- package/dist/intelligence/deliverable-extractor.js +0 -909
- package/dist/intelligence/dependency-inferrer.d.ts +0 -87
- package/dist/intelligence/dependency-inferrer.js +0 -403
- package/dist/intelligence/diagnostics.d.ts +0 -33
- package/dist/intelligence/diagnostics.js +0 -64
- package/dist/intelligence/error-analyzer.d.ts +0 -7
- package/dist/intelligence/error-analyzer.js +0 -76
- package/dist/intelligence/file-chunker.d.ts +0 -15
- package/dist/intelligence/file-chunker.js +0 -64
- package/dist/intelligence/fix-stream-manager.d.ts +0 -59
- package/dist/intelligence/fix-stream-manager.js +0 -212
- package/dist/intelligence/heuristics.d.ts +0 -23
- package/dist/intelligence/heuristics.js +0 -124
- package/dist/intelligence/learning-engine.d.ts +0 -157
- package/dist/intelligence/learning-engine.js +0 -433
- package/dist/intelligence/learning-feedback.d.ts +0 -96
- package/dist/intelligence/learning-feedback.js +0 -202
- package/dist/intelligence/pattern-analyzer.d.ts +0 -35
- package/dist/intelligence/pattern-analyzer.js +0 -189
- package/dist/intelligence/plan-parser.d.ts +0 -124
- package/dist/intelligence/plan-parser.js +0 -498
- package/dist/intelligence/planner.d.ts +0 -29
- package/dist/intelligence/planner.js +0 -86
- package/dist/intelligence/self-healer.d.ts +0 -16
- package/dist/intelligence/self-healer.js +0 -84
- package/dist/intelligence/slicing-metrics.d.ts +0 -62
- package/dist/intelligence/slicing-metrics.js +0 -202
- package/dist/intelligence/slicing-templates.d.ts +0 -81
- package/dist/intelligence/slicing-templates.js +0 -420
- package/dist/intelligence/split-suggester.d.ts +0 -69
- package/dist/intelligence/split-suggester.js +0 -176
- package/dist/intelligence/stream-generator.d.ts +0 -90
- package/dist/intelligence/stream-generator.js +0 -452
- package/dist/telemetry/telemetry-types.d.ts +0 -85
- package/dist/telemetry/telemetry-types.js +0 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,9 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { exec as execChildProcess } from 'child_process';
|
|
4
4
|
import { registerTools, setProjectDir } from './tools.js';
|
|
5
|
-
import {
|
|
5
|
+
import { ORCHEX_INSTRUCTIONS } from './mcp-instructions.js';
|
|
6
|
+
import { registerResources } from './mcp-resources.js';
|
|
7
|
+
import { loadConfig, saveConfig, maskConfigForDisplay, resolveApiUrl, PRODUCTION_URL, LLMProviderSchema } from './config.js';
|
|
6
8
|
import { buildVerificationMessage, buildStatusMessage, parseLoginApiResponse, } from './login-helpers.js';
|
|
7
9
|
/** Opens browser cross-platform. Errors are silently ignored — URL already printed to terminal. */
|
|
8
10
|
function openBrowser(url) {
|
|
@@ -12,18 +14,12 @@ function openBrowser(url) {
|
|
|
12
14
|
execChildProcess(cmd, () => { });
|
|
13
15
|
}
|
|
14
16
|
async function handleLoginCommand(args) {
|
|
15
|
-
let apiUrl = PRODUCTION_URL;
|
|
16
17
|
const apiUrlIdx = args.indexOf('--api-url');
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (stagingIdx !== -1) {
|
|
21
|
-
const stagingUrl = process.env.ORCHEX_STAGING_URL;
|
|
22
|
-
if (!stagingUrl) {
|
|
23
|
-
console.warn('⚠ --staging used but ORCHEX_STAGING_URL is not set. Using production URL.');
|
|
24
|
-
}
|
|
25
|
-
apiUrl = stagingUrl ?? PRODUCTION_URL;
|
|
18
|
+
const flagValue = apiUrlIdx !== -1 ? args[apiUrlIdx + 1] : undefined;
|
|
19
|
+
if (args.includes('--staging')) {
|
|
20
|
+
console.warn('⚠ orchex login --staging is no longer needed. Set ORCHEX_API_URL=<url> in your environment instead.');
|
|
26
21
|
}
|
|
22
|
+
const apiUrl = await resolveApiUrl(flagValue);
|
|
27
23
|
// Step 1: Start device auth
|
|
28
24
|
let startData;
|
|
29
25
|
try {
|
|
@@ -86,6 +82,15 @@ async function handleLoginCommand(args) {
|
|
|
86
82
|
const body = await meResp.json();
|
|
87
83
|
const me = body.user;
|
|
88
84
|
if (me) {
|
|
85
|
+
// Sync tier + trial info to local config for offline tier resolution
|
|
86
|
+
await saveConfig({
|
|
87
|
+
mode: 'cloud',
|
|
88
|
+
apiUrl,
|
|
89
|
+
apiKey: token,
|
|
90
|
+
tier: me.tier,
|
|
91
|
+
trialRunsRemaining: me.trialRunsRemaining,
|
|
92
|
+
accountCreatedAt: me.createdAt ?? undefined,
|
|
93
|
+
});
|
|
89
94
|
console.log(`\n✓ Logged in as ${me.email}`);
|
|
90
95
|
const expiryDays = me.tier === 'free' ? 30 : 90;
|
|
91
96
|
const expiryDate = new Date(Date.now() + expiryDays * 24 * 60 * 60 * 1000).toLocaleDateString();
|
|
@@ -96,7 +101,54 @@ async function handleLoginCommand(args) {
|
|
|
96
101
|
console.log(` Tier: ${me.tier.charAt(0).toUpperCase() + me.tier.slice(1)}`);
|
|
97
102
|
}
|
|
98
103
|
console.log(` Token expires: ${expiryDate}`);
|
|
99
|
-
console.log(` Config saved to ~/.orchex/config.json
|
|
104
|
+
console.log(` Config saved to ~/.orchex/config.json`);
|
|
105
|
+
// Sync BYOK keys to local cache
|
|
106
|
+
try {
|
|
107
|
+
const { writeKeyCache } = await import('./key-cache.js');
|
|
108
|
+
const keysResp = await fetch(`${apiUrl}/api/v1/keys/resolve`, {
|
|
109
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
110
|
+
});
|
|
111
|
+
if (keysResp.ok) {
|
|
112
|
+
const { keys } = (await keysResp.json());
|
|
113
|
+
if (keys.length > 0) {
|
|
114
|
+
await writeKeyCache({ keys, fetchedAt: new Date().toISOString() });
|
|
115
|
+
console.log(` Keys: ${keys.length} provider key(s) synced`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Non-fatal — keys can be fetched on-demand later
|
|
121
|
+
}
|
|
122
|
+
// Fetch entitlement JWT (non-fatal — login succeeds even if this fails)
|
|
123
|
+
try {
|
|
124
|
+
const entResp = await fetch(`${apiUrl}/api/v1/entitlements/me`, {
|
|
125
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
126
|
+
});
|
|
127
|
+
if (entResp.ok) {
|
|
128
|
+
const { jwt: entitlementJwt } = await entResp.json();
|
|
129
|
+
const { readKeyCacheRaw, writeKeyCache: writeKC } = await import('./key-cache.js');
|
|
130
|
+
// IMPORTANT: Use readKeyCacheRaw() — NOT readKeyCache() — to avoid
|
|
131
|
+
// the 1hr BYOK TTL wiping existing keys when cache is older than 1hr.
|
|
132
|
+
const existingCache = await readKeyCacheRaw();
|
|
133
|
+
await writeKC({
|
|
134
|
+
keys: existingCache?.keys ?? [],
|
|
135
|
+
fetchedAt: existingCache?.fetchedAt ?? new Date().toISOString(),
|
|
136
|
+
entitlementJwt,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Non-fatal — entitlement sync is best-effort
|
|
142
|
+
}
|
|
143
|
+
// Also cache model registry for offline use
|
|
144
|
+
try {
|
|
145
|
+
const { fetchAndCacheModels } = await import('./model-cache.js');
|
|
146
|
+
await fetchAndCacheModels(apiUrl);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Non-fatal — model registry can be fetched on-demand later
|
|
150
|
+
}
|
|
151
|
+
console.log('');
|
|
100
152
|
return;
|
|
101
153
|
}
|
|
102
154
|
}
|
|
@@ -112,8 +164,10 @@ async function handleLogoutCommand() {
|
|
|
112
164
|
console.log('Not logged in (no API key configured).');
|
|
113
165
|
return;
|
|
114
166
|
}
|
|
115
|
-
|
|
116
|
-
|
|
167
|
+
const { clearKeyCache } = await import('./key-cache.js');
|
|
168
|
+
await clearKeyCache();
|
|
169
|
+
await saveConfig({ mode: 'local', apiKey: undefined, tier: 'free' });
|
|
170
|
+
console.log('Logged out. Local mode enabled. Cached keys cleared.');
|
|
117
171
|
}
|
|
118
172
|
async function handleStatusCommand(args) {
|
|
119
173
|
const config = await loadConfig();
|
|
@@ -152,6 +206,26 @@ async function handleStatusCommand(args) {
|
|
|
152
206
|
}
|
|
153
207
|
else {
|
|
154
208
|
console.log(buildStatusMessage(me));
|
|
209
|
+
// Show entitlement status if JWT is cached
|
|
210
|
+
try {
|
|
211
|
+
const { getCachedEntitlement } = await import('./key-cache.js');
|
|
212
|
+
const { loadPublicKey } = await import('./entitlements/resolve.js');
|
|
213
|
+
const publicKeyPem = await loadPublicKey();
|
|
214
|
+
const claims = await getCachedEntitlement(publicKeyPem);
|
|
215
|
+
if (claims) {
|
|
216
|
+
const srcLabel = claims.src.charAt(0).toUpperCase() + claims.src.slice(1);
|
|
217
|
+
const unlimitedFields = Object.entries(claims.eff)
|
|
218
|
+
.filter(([, v]) => v === -1)
|
|
219
|
+
.map(([k]) => k);
|
|
220
|
+
const desc = unlimitedFields.length > 0
|
|
221
|
+
? `unlimited: ${unlimitedFields.join(', ')}`
|
|
222
|
+
: 'custom limits';
|
|
223
|
+
console.log(` Entitlement: ${srcLabel} (${desc})`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// No entitlement info — skip silently
|
|
228
|
+
}
|
|
155
229
|
}
|
|
156
230
|
}
|
|
157
231
|
catch (err) {
|
|
@@ -160,7 +234,14 @@ async function handleStatusCommand(args) {
|
|
|
160
234
|
}
|
|
161
235
|
function printHelp() {
|
|
162
236
|
console.log(`
|
|
163
|
-
orchex —
|
|
237
|
+
orchex — Autopilot AI orchestration with ownership enforcement
|
|
238
|
+
|
|
239
|
+
ORCHESTRATION:
|
|
240
|
+
orchex run "intent" Auto-plan and execute from natural language
|
|
241
|
+
orchex run "..." --yes Skip approval prompt (auto-approve)
|
|
242
|
+
orchex run "..." --dry-run Generate plan only, don't execute
|
|
243
|
+
orchex run "..." --provider openai --model gpt-4.1
|
|
244
|
+
orchex complete --archive Archive active orchestration and unblock future runs
|
|
164
245
|
|
|
165
246
|
CLOUD COMMANDS:
|
|
166
247
|
orchex login Authenticate with orchex cloud (opens browser)
|
|
@@ -168,10 +249,25 @@ CLOUD COMMANDS:
|
|
|
168
249
|
orchex status Show login state, tier, and trial runs
|
|
169
250
|
orchex status --json Output as JSON (for scripting)
|
|
170
251
|
|
|
252
|
+
LEARNING:
|
|
253
|
+
orchex reset-learning --confirm Reset all learning data
|
|
254
|
+
orchex reset-learning --confirm --patterns-only Reset detected patterns only
|
|
255
|
+
orchex reset-learning --confirm --reports-only Reset execution reports only
|
|
256
|
+
|
|
171
257
|
CONFIG:
|
|
172
258
|
orchex config Show current config (apiKey masked)
|
|
173
259
|
orchex config --api-key <token> Save API key manually
|
|
174
|
-
orchex config --staging Configure for staging server
|
|
260
|
+
orchex config --staging Configure for staging server (reads ORCHEX_API_URL)
|
|
261
|
+
|
|
262
|
+
ENVIRONMENT VARIABLES:
|
|
263
|
+
ORCHEX_API_URL Override API server URL (e.g. https://orchex.dev)
|
|
264
|
+
Priority: --api-url flag > ORCHEX_API_URL > config file > default
|
|
265
|
+
|
|
266
|
+
SETUP:
|
|
267
|
+
setup [options] Auto-configure MCP server for your IDE
|
|
268
|
+
--ide <name> Target specific IDE
|
|
269
|
+
--dry-run Preview changes
|
|
270
|
+
--force Overwrite existing
|
|
175
271
|
|
|
176
272
|
MCP SERVER (invoked by your AI assistant):
|
|
177
273
|
npx @wundam/orchex Start the MCP server
|
|
@@ -220,11 +316,19 @@ async function handleConfigCommand(args) {
|
|
|
220
316
|
updates.apiKey = value;
|
|
221
317
|
i++;
|
|
222
318
|
break;
|
|
223
|
-
case '--staging':
|
|
224
|
-
// Shorthand: --staging sets mode=cloud and apiUrl to staging
|
|
319
|
+
case '--staging': {
|
|
320
|
+
// Shorthand: --staging sets mode=cloud and apiUrl to staging env
|
|
225
321
|
updates.mode = 'cloud';
|
|
226
|
-
|
|
322
|
+
if (process.env.ORCHEX_STAGING_URL && !process.env.ORCHEX_API_URL) {
|
|
323
|
+
console.warn('⚠ ORCHEX_STAGING_URL is deprecated. Use ORCHEX_API_URL instead.');
|
|
324
|
+
}
|
|
325
|
+
const stagingTarget = process.env.ORCHEX_API_URL ?? process.env.ORCHEX_STAGING_URL;
|
|
326
|
+
if (!stagingTarget) {
|
|
327
|
+
console.warn('⚠ --staging used but ORCHEX_API_URL is not set. Defaulting to production URL.');
|
|
328
|
+
}
|
|
329
|
+
updates.apiUrl = stagingTarget ?? PRODUCTION_URL;
|
|
227
330
|
break;
|
|
331
|
+
}
|
|
228
332
|
case '--local':
|
|
229
333
|
// Shorthand: --local sets mode=local
|
|
230
334
|
updates.mode = 'local';
|
|
@@ -240,6 +344,13 @@ async function handleConfigCommand(args) {
|
|
|
240
344
|
process.exit(1);
|
|
241
345
|
}
|
|
242
346
|
updates.provider = value;
|
|
347
|
+
// Warn if setting non-Anthropic provider while in cloud mode
|
|
348
|
+
{
|
|
349
|
+
const currentConfig = await loadConfig();
|
|
350
|
+
if ((currentConfig.mode === 'cloud' || updates.mode === 'cloud') && value !== 'anthropic') {
|
|
351
|
+
console.warn(`\u26A0 Warning: Cloud mode currently only supports Anthropic. Provider "${value}" will be ignored in cloud execution.`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
243
354
|
i++;
|
|
244
355
|
break;
|
|
245
356
|
case '--model':
|
|
@@ -266,6 +377,283 @@ async function handleConfigCommand(args) {
|
|
|
266
377
|
console.log('Config updated:');
|
|
267
378
|
console.log(JSON.stringify(maskConfigForDisplay(config), null, 2));
|
|
268
379
|
}
|
|
380
|
+
async function handleRunCommand(args) {
|
|
381
|
+
// Parse flags
|
|
382
|
+
let providerFlag;
|
|
383
|
+
let modelFlag;
|
|
384
|
+
let autoApprove = false;
|
|
385
|
+
let dryRun = false;
|
|
386
|
+
let projectDir = process.cwd();
|
|
387
|
+
const positionalArgs = [];
|
|
388
|
+
for (let i = 0; i < args.length; i++) {
|
|
389
|
+
const flag = args[i];
|
|
390
|
+
switch (flag) {
|
|
391
|
+
case '--yes':
|
|
392
|
+
case '-y':
|
|
393
|
+
autoApprove = true;
|
|
394
|
+
break;
|
|
395
|
+
case '--dry-run':
|
|
396
|
+
dryRun = true;
|
|
397
|
+
break;
|
|
398
|
+
case '--provider':
|
|
399
|
+
providerFlag = args[++i];
|
|
400
|
+
break;
|
|
401
|
+
case '--model':
|
|
402
|
+
modelFlag = args[++i];
|
|
403
|
+
break;
|
|
404
|
+
case '--project-dir':
|
|
405
|
+
projectDir = args[++i];
|
|
406
|
+
break;
|
|
407
|
+
default:
|
|
408
|
+
if (!flag.startsWith('--')) {
|
|
409
|
+
positionalArgs.push(flag);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
console.error(`Unknown flag: ${flag}`);
|
|
413
|
+
console.error('Usage: orchex run "intent" [--yes] [--dry-run] [--provider NAME] [--model NAME]');
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const intent = positionalArgs.join(' ').trim();
|
|
419
|
+
if (!intent) {
|
|
420
|
+
console.error('Error: Missing intent. Usage: orchex run "Add user authentication"');
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
// Dynamic imports (these are local-only modules)
|
|
424
|
+
const { gatherProjectContext, generatePlan, parsePlanDocument, extractDeliverables, processDeliverables, buildDependencyGraph, generateStreams, toInitFormat, formatPlanPreview, formatPlanPreviewText, detectSequentialEdits, autoFixSequentialEdits, createDiagnostics, } = await import('./intelligence/index.js');
|
|
425
|
+
const { calculateWaves } = await import('./waves.js');
|
|
426
|
+
const { createExecutor } = await import('./executors/index.js');
|
|
427
|
+
const { initOrchestration, loadManifest, manifestExists } = await import('./manifest.js');
|
|
428
|
+
const { executeWave } = await import('./orchestrator.js');
|
|
429
|
+
const { generateReport, saveReportLocally, generateLearningSummary } = await import('./intelligence/index.js');
|
|
430
|
+
// First-run check
|
|
431
|
+
const { isFirstRun, getFirstRunSuggestion, markFirstRunComplete } = await import('./intelligence/index.js');
|
|
432
|
+
if (await isFirstRun(projectDir)) {
|
|
433
|
+
console.log('\n' + getFirstRunSuggestion() + '\n');
|
|
434
|
+
}
|
|
435
|
+
console.log(`\nGenerating plan for: "${intent}"\n`);
|
|
436
|
+
// Create executor — priority: env var > cached BYOK key > error
|
|
437
|
+
const config = await loadConfig();
|
|
438
|
+
const resolvedProvider = providerFlag
|
|
439
|
+
? providerFlag
|
|
440
|
+
: (await import('./config.js')).getConfiguredProvider(config);
|
|
441
|
+
// Try env var first (existing behavior)
|
|
442
|
+
const { getProviderApiKey } = await import('./config.js');
|
|
443
|
+
let apiKey = getProviderApiKey(resolvedProvider);
|
|
444
|
+
// Fallback: cached BYOK key from login sync
|
|
445
|
+
if (!apiKey && resolvedProvider !== 'ollama' && resolvedProvider !== 'bedrock') {
|
|
446
|
+
const { getCachedApiKey } = await import('./key-cache.js');
|
|
447
|
+
apiKey = await getCachedApiKey(resolvedProvider);
|
|
448
|
+
// Fallback: fetch fresh from cloud if logged in and cache miss/expired
|
|
449
|
+
if (!apiKey && config.mode === 'cloud' && config.apiKey) {
|
|
450
|
+
try {
|
|
451
|
+
const { writeKeyCache } = await import('./key-cache.js');
|
|
452
|
+
const resolvedApiUrl = await (await import('./config.js')).resolveApiUrl();
|
|
453
|
+
const resp = await fetch(`${resolvedApiUrl}/api/v1/keys/resolve`, {
|
|
454
|
+
headers: { 'Authorization': `Bearer ${config.apiKey}` },
|
|
455
|
+
});
|
|
456
|
+
if (resp.ok) {
|
|
457
|
+
const { keys } = (await resp.json());
|
|
458
|
+
if (keys.length > 0) {
|
|
459
|
+
await writeKeyCache({ keys, fetchedAt: new Date().toISOString() });
|
|
460
|
+
apiKey = keys.find(k => k.provider === resolvedProvider)?.apiKey;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
// Non-fatal — will error below if no key found
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (!apiKey && resolvedProvider !== 'ollama' && resolvedProvider !== 'bedrock') {
|
|
470
|
+
const envVarName = resolvedProvider === 'anthropic' ? 'ANTHROPIC_API_KEY'
|
|
471
|
+
: resolvedProvider === 'openai' ? 'OPENAI_API_KEY'
|
|
472
|
+
: resolvedProvider === 'gemini' ? 'GEMINI_API_KEY'
|
|
473
|
+
: 'DEEPSEEK_API_KEY';
|
|
474
|
+
console.error(`No API key found for provider "${resolvedProvider}".`);
|
|
475
|
+
console.error('Options:');
|
|
476
|
+
console.error(` 1. Set env var: export ${envVarName}=<key>`);
|
|
477
|
+
console.error(' 2. Store a key at https://orchex.dev/dashboard/keys and run `orchex login`');
|
|
478
|
+
process.exit(1);
|
|
479
|
+
}
|
|
480
|
+
const exec = createExecutor({ provider: resolvedProvider, ...(apiKey ? { apiKey } : {}) });
|
|
481
|
+
// Resolve tier BEFORE plan generation so we can pass stream budget to the LLM
|
|
482
|
+
const { checkTierLimits } = await import('./tier-gating.js');
|
|
483
|
+
let effectiveTier;
|
|
484
|
+
try {
|
|
485
|
+
const { resolveEntitledTier, loadPublicKey } = await import('./entitlements/resolve.js');
|
|
486
|
+
const publicKey = await loadPublicKey();
|
|
487
|
+
effectiveTier = await resolveEntitledTier(config, publicKey);
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
// Fallback if entitlement module not available
|
|
491
|
+
const { getEffectiveTier, getEffectiveLimits } = await import('./tiers.js');
|
|
492
|
+
const effectiveTierId = config.mode === 'cloud' && config.apiKey
|
|
493
|
+
? getEffectiveTier({
|
|
494
|
+
tier: config.tier ?? 'free',
|
|
495
|
+
createdAt: config.accountCreatedAt ?? new Date().toISOString(),
|
|
496
|
+
trialRunsRemaining: config.trialRunsRemaining ?? 0,
|
|
497
|
+
})
|
|
498
|
+
: (config.tier ?? 'free');
|
|
499
|
+
effectiveTier = getEffectiveLimits(effectiveTierId, null);
|
|
500
|
+
}
|
|
501
|
+
const maxStreams = effectiveTier.maxParallelAgents === -1 ? undefined : effectiveTier.maxParallelAgents;
|
|
502
|
+
// Generate plan
|
|
503
|
+
let planResult;
|
|
504
|
+
try {
|
|
505
|
+
planResult = await generatePlan(intent, projectDir, exec, { model: modelFlag, provider: resolvedProvider, maxStreams });
|
|
506
|
+
}
|
|
507
|
+
catch (err) {
|
|
508
|
+
console.error(`Plan generation failed: ${err.message}`);
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
511
|
+
// Run through learn pipeline
|
|
512
|
+
const diagnostics = createDiagnostics();
|
|
513
|
+
const plan = parsePlanDocument(planResult.planMarkdown);
|
|
514
|
+
const rawDeliverables = extractDeliverables(plan, { diagnostics });
|
|
515
|
+
const deliverables = processDeliverables(rawDeliverables);
|
|
516
|
+
if (deliverables.length === 0) {
|
|
517
|
+
console.error('Plan generated no deliverables. Try a more specific intent.');
|
|
518
|
+
process.exit(1);
|
|
519
|
+
}
|
|
520
|
+
const graph = buildDependencyGraph(deliverables, { diagnostics });
|
|
521
|
+
const result = generateStreams(deliverables, graph, {
|
|
522
|
+
validateAntiPatterns: true,
|
|
523
|
+
projectDir,
|
|
524
|
+
diagnostics,
|
|
525
|
+
});
|
|
526
|
+
// Sequential diagnostics
|
|
527
|
+
const seqDiag = detectSequentialEdits(result.streams);
|
|
528
|
+
const initStreams = toInitFormat(result);
|
|
529
|
+
const streamDefs = Object.fromEntries(Object.entries(initStreams).map(([id, s]) => [id, {
|
|
530
|
+
name: s.name, deps: s.deps || [], owns: s.owns || [],
|
|
531
|
+
reads: s.reads || [], setup: [], plan: s.plan,
|
|
532
|
+
verify: s.verify || [], status: 'pending', attempts: 0,
|
|
533
|
+
}]));
|
|
534
|
+
const { waves } = calculateWaves({
|
|
535
|
+
feature: plan.title, streams: streamDefs,
|
|
536
|
+
status: 'pending', created: new Date().toISOString(),
|
|
537
|
+
});
|
|
538
|
+
// Enforce tier limits on the generated plan (safety net)
|
|
539
|
+
const tierCheck = checkTierLimits({ tier: effectiveTier, streams: streamDefs, projectDir, mode: config.mode });
|
|
540
|
+
if (!tierCheck.allowed) {
|
|
541
|
+
console.error(`Tier limit exceeded: ${tierCheck.error}`);
|
|
542
|
+
if (tierCheck.suggestion)
|
|
543
|
+
console.error(tierCheck.suggestion);
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
const waveList = waves.map(w => ({ number: w.number, streams: w.streams.map(s => s.id) }));
|
|
547
|
+
const allWarnings = [...result.warnings, ...diagnostics.warnings];
|
|
548
|
+
// Validate models for each stream and build model decisions map
|
|
549
|
+
const { resolveModel } = await import('./model-validator.js');
|
|
550
|
+
const { resolveApiUrl, DEFAULT_MODELS } = await import('./config.js');
|
|
551
|
+
const { estimatePlannedCost } = await import('./intelligence/index.js');
|
|
552
|
+
const modelApiUrl = config.mode === 'cloud' && config.apiKey ? await resolveApiUrl() : undefined;
|
|
553
|
+
const modelDecisions = {};
|
|
554
|
+
for (const [id, stream] of Object.entries(initStreams)) {
|
|
555
|
+
const suggestedProvider = stream.suggestedProvider ?? resolvedProvider;
|
|
556
|
+
const suggestedModel = DEFAULT_MODELS[suggestedProvider] ?? modelFlag;
|
|
557
|
+
const validation = await resolveModel(suggestedProvider, suggestedModel, modelApiUrl);
|
|
558
|
+
// Estimate cost using cost-tracker
|
|
559
|
+
const ownedLines = (stream.owns ?? []).length * 100; // rough estimate
|
|
560
|
+
const costEstimate = estimatePlannedCost(ownedLines * 4, validation.resolvedModel); // ~4 tokens/line
|
|
561
|
+
modelDecisions[id] = {
|
|
562
|
+
provider: suggestedProvider,
|
|
563
|
+
model: validation.resolvedModel,
|
|
564
|
+
estimatedCostUsd: costEstimate.finalCost,
|
|
565
|
+
fallback: validation.wasFallback ? `${validation.originalModel} → ${validation.resolvedModel}` : undefined,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
const preview = formatPlanPreview(initStreams, waveList, allWarnings, seqDiag, modelDecisions);
|
|
569
|
+
// Show preview
|
|
570
|
+
console.log(formatPlanPreviewText(preview));
|
|
571
|
+
if (dryRun) {
|
|
572
|
+
console.log('--- Generated Plan Markdown ---\n');
|
|
573
|
+
console.log(planResult.planMarkdown);
|
|
574
|
+
console.log('\n(Dry run — no execution)');
|
|
575
|
+
process.exit(0);
|
|
576
|
+
}
|
|
577
|
+
// Approval
|
|
578
|
+
if (!autoApprove) {
|
|
579
|
+
const readline = await import('readline');
|
|
580
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
581
|
+
const answer = await new Promise(resolve => {
|
|
582
|
+
rl.question('Execute this plan? [y/N] ', resolve);
|
|
583
|
+
});
|
|
584
|
+
rl.close();
|
|
585
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
586
|
+
console.log('Aborted.');
|
|
587
|
+
process.exit(0);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
// Auto-fix sequential edits before execution
|
|
591
|
+
if (seqDiag.length > 0) {
|
|
592
|
+
autoFixSequentialEdits(result.streams);
|
|
593
|
+
console.log(`Auto-fixed ${seqDiag.length} sequential edit conflict(s).`);
|
|
594
|
+
}
|
|
595
|
+
// Check for existing orchestration
|
|
596
|
+
if (await manifestExists(projectDir)) {
|
|
597
|
+
console.error('An active orchestration already exists. Run `orchex complete --archive` to clear it.');
|
|
598
|
+
process.exit(1);
|
|
599
|
+
}
|
|
600
|
+
// Init and execute
|
|
601
|
+
console.log(`\nInitializing orchestration: "${plan.title}"\n`);
|
|
602
|
+
await initOrchestration(projectDir, plan.title, streamDefs);
|
|
603
|
+
const allResponses = [];
|
|
604
|
+
let executionError;
|
|
605
|
+
try {
|
|
606
|
+
let done = false;
|
|
607
|
+
let waveNum = 1;
|
|
608
|
+
while (!done) {
|
|
609
|
+
console.log(`Executing wave ${waveNum}...`);
|
|
610
|
+
const response = await executeWave(projectDir, exec, { model: modelFlag });
|
|
611
|
+
allResponses.push(response);
|
|
612
|
+
const completed = response.streams.filter(s => s.status === 'complete').length;
|
|
613
|
+
const failed = response.streams.filter(s => s.status === 'failed').length;
|
|
614
|
+
console.log(` Wave ${waveNum}: ${completed} complete, ${failed} failed`);
|
|
615
|
+
done = response.done;
|
|
616
|
+
waveNum++;
|
|
617
|
+
}
|
|
618
|
+
// Generate report
|
|
619
|
+
const manifest = await loadManifest(projectDir);
|
|
620
|
+
const report = generateReport(manifest, allResponses);
|
|
621
|
+
const reportPath = await saveReportLocally(projectDir, report);
|
|
622
|
+
// Sync report to cloud dashboard (fire-and-forget, only if logged in)
|
|
623
|
+
const { syncReportToCloud } = await import('./cloud-sync.js');
|
|
624
|
+
await syncReportToCloud(config, report);
|
|
625
|
+
// Learning summary
|
|
626
|
+
const summary = generateLearningSummary(report);
|
|
627
|
+
console.log('\n--- Orchex learned ---');
|
|
628
|
+
for (const insight of summary) {
|
|
629
|
+
console.log(` ${insight}`);
|
|
630
|
+
}
|
|
631
|
+
console.log(`\nReport saved: ${reportPath}`);
|
|
632
|
+
console.log(`Quality score: ${report.planQualityScore}/100\n`);
|
|
633
|
+
// Mark first-run complete after successful orchestration
|
|
634
|
+
if (await isFirstRun(projectDir)) {
|
|
635
|
+
await markFirstRunComplete(projectDir);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
catch (err) {
|
|
639
|
+
executionError = err;
|
|
640
|
+
console.error(`\nExecution error: ${executionError.message}`);
|
|
641
|
+
}
|
|
642
|
+
finally {
|
|
643
|
+
// Always archive to unblock future runs
|
|
644
|
+
try {
|
|
645
|
+
const { archiveOrchestration } = await import('./manifest.js');
|
|
646
|
+
const archiveName = await archiveOrchestration(projectDir);
|
|
647
|
+
console.log(`Orchestration archived: ${archiveName}`);
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
// Best-effort — report is already saved if execution succeeded
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (executionError) {
|
|
654
|
+
process.exit(1);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
269
657
|
async function main() {
|
|
270
658
|
const args = process.argv.slice(2);
|
|
271
659
|
// Handle CLI config command (not MCP)
|
|
@@ -285,6 +673,65 @@ async function main() {
|
|
|
285
673
|
await handleStatusCommand(args.slice(1));
|
|
286
674
|
return;
|
|
287
675
|
}
|
|
676
|
+
if (args[0] === 'run') {
|
|
677
|
+
await handleRunCommand(args.slice(1));
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (args[0] === 'reset-learning') {
|
|
681
|
+
const { resetLearning } = await import('./intelligence/index.js');
|
|
682
|
+
const subArgs = args.slice(1);
|
|
683
|
+
const confirm = subArgs.includes('--confirm');
|
|
684
|
+
const patternsOnly = subArgs.includes('--patterns-only');
|
|
685
|
+
const reportsOnly = subArgs.includes('--reports-only');
|
|
686
|
+
try {
|
|
687
|
+
const result = await resetLearning(process.cwd(), { confirm, patternsOnly, reportsOnly });
|
|
688
|
+
console.log(result.message);
|
|
689
|
+
for (const f of result.deleted) {
|
|
690
|
+
console.log(` - ${f}`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
catch (err) {
|
|
694
|
+
console.error(err.message);
|
|
695
|
+
process.exit(1);
|
|
696
|
+
}
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
if (args[0] === 'complete') {
|
|
700
|
+
const { manifestExists, archiveOrchestration } = await import('./manifest.js');
|
|
701
|
+
const subArgs = args.slice(1);
|
|
702
|
+
const pdFlag = subArgs.indexOf('--project-dir');
|
|
703
|
+
const projectDir = pdFlag !== -1 && subArgs[pdFlag + 1] ? subArgs[pdFlag + 1] : process.cwd();
|
|
704
|
+
if (!(await manifestExists(projectDir))) {
|
|
705
|
+
console.log('No active orchestration to archive.');
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (subArgs.includes('--archive')) {
|
|
709
|
+
try {
|
|
710
|
+
const archiveName = await archiveOrchestration(projectDir);
|
|
711
|
+
console.log(`Orchestration archived: ${archiveName}`);
|
|
712
|
+
}
|
|
713
|
+
catch (err) {
|
|
714
|
+
console.error(`Archive failed: ${err.message}`);
|
|
715
|
+
process.exit(1);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
console.error('Usage: orchex complete --archive');
|
|
720
|
+
}
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
if (args[0] === 'setup') {
|
|
724
|
+
const { handleSetupCommand } = await import('./setup/index.js');
|
|
725
|
+
await handleSetupCommand(args.slice(1));
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (args[0] === '--version' || args[0] === '-v') {
|
|
729
|
+
const { createRequire } = await import('module');
|
|
730
|
+
const require = createRequire(import.meta.url);
|
|
731
|
+
const pkg = require('../package.json');
|
|
732
|
+
console.log(pkg.version);
|
|
733
|
+
process.exit(0);
|
|
734
|
+
}
|
|
288
735
|
if (args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
|
|
289
736
|
printHelp();
|
|
290
737
|
process.exit(0);
|
|
@@ -295,11 +742,9 @@ async function main() {
|
|
|
295
742
|
setProjectDir(args[pdIdx + 1]);
|
|
296
743
|
}
|
|
297
744
|
// Start MCP server
|
|
298
|
-
const server = new McpServer({
|
|
299
|
-
name: 'orchex',
|
|
300
|
-
version: '1.0.0',
|
|
301
|
-
});
|
|
745
|
+
const server = new McpServer({ name: 'orchex', version: '1.0.0' }, { instructions: ORCHEX_INSTRUCTIONS, capabilities: { logging: {} } });
|
|
302
746
|
registerTools(server);
|
|
747
|
+
registerResources(server);
|
|
303
748
|
const transport = new StdioServerTransport();
|
|
304
749
|
await server.connect(transport);
|
|
305
750
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export { gatherProjectContext, generatePlan } from './auto-planner.js';
|
|
2
|
+
export { parsePlanDocument, getSectionsAtLevel, isUnpopulatedTemplate } from './plan-parser.js';
|
|
3
|
+
export { extractDeliverables, processDeliverables, formatDeliverablesReport } from './deliverable-extractor.js';
|
|
4
|
+
export type { Deliverable } from './deliverable-extractor.js';
|
|
5
|
+
export { buildDependencyGraph, formatDependencyReport } from './dependency-inferrer.js';
|
|
6
|
+
export { generateStreams, formatStreamsForReview, toInitFormat, extractPrerequisites } from './stream-generator.js';
|
|
7
|
+
export { formatPlanPreview, formatPlanPreviewText, type ModelDecision } from './plan-preview.js';
|
|
8
|
+
export { detectSequentialEdits, autoFixSequentialEdits } from './sequential-diagnostics.js';
|
|
9
|
+
export type { SequentialEditDiagnostic } from './sequential-diagnostics.js';
|
|
10
|
+
export { createDiagnostics, detectOwnershipConflicts } from './diagnostics.js';
|
|
11
|
+
export type { LearnDiagnostics } from './diagnostics.js';
|
|
12
|
+
export { generateReport, saveReportLocally } from './execution-report.js';
|
|
13
|
+
export type { ExecutionReport, ReportStreamResult } from './execution-report.js';
|
|
14
|
+
export { generateLearningSummary } from './learning-summary.js';
|
|
15
|
+
export { isFirstRun, getFirstRunSuggestion, markFirstRunComplete } from './first-run.js';
|
|
16
|
+
export { resetLearning } from './reset-learning.js';
|
|
17
|
+
export { analyzeError } from './error-analyzer.js';
|
|
18
|
+
export type { ErrorCategory, ErrorAnalysis } from './error-analyzer.js';
|
|
19
|
+
export { evaluateCompletion } from './iteration-evaluator.js';
|
|
20
|
+
export type { StreamResultSummary } from './iteration-evaluator.js';
|
|
21
|
+
export { DEFAULT_THRESHOLDS, categorizeStream, getRecommendedLimit, getThresholds, streamResultToTelemetryEvent, appendLocalEvents, runLearningCycle, } from './learning-engine.js';
|
|
22
|
+
export type { LearnedThresholds, LearningStreamResult } from './learning-engine.js';
|
|
23
|
+
export { createDetector } from './anti-pattern-detector.js';
|
|
24
|
+
export type { AntiPatternIssue, BatchAnalysisResult } from './anti-pattern-detector.js';
|
|
25
|
+
export { generateAllSuggestions, getSuggestionsSummary } from './split-suggester.js';
|
|
26
|
+
export type { InteractiveSuggestion } from './split-suggester.js';
|
|
27
|
+
export { autoMergeOwnershipConflicts } from './ownership-resolver.js';
|
|
28
|
+
export { suggestProvider, classifyTask } from './model-routing.js';
|
|
29
|
+
export { applyPartialApproval, rollbackStream } from './interactive-approval.js';
|
|
30
|
+
export { generateFixStream } from './self-healer.js';
|
|
31
|
+
export { cleanupOrphanFixStreams, getFixChainInfo, isFixStream, getOriginalStreamId, onStreamComplete } from './fix-stream-manager.js';
|
|
32
|
+
export { routeStream, loadRoutingRules } from './smart-router.js';
|
|
33
|
+
export type { RouteDecision, RouteOptions } from './smart-router.js';
|
|
34
|
+
export { detectPatterns, formatPatterns, patternsToPromptHints, savePatterns, loadPatterns, adaptReport } from './pattern-detector.js';
|
|
35
|
+
export type { DetectorInput } from './pattern-detector.js';
|
|
36
|
+
export { pruneUnusedFiles, estimateTokens, generateCachingHints } from './context-optimizer.js';
|
|
37
|
+
export { checkBudget, createBudgetConfig, ContextBudgetExceededError } from './budget-enforcer.js';
|
|
38
|
+
export type { BudgetCheckResult } from './budget-enforcer.js';
|
|
39
|
+
export { extractRelevantChunks } from './file-chunker.js';
|
|
40
|
+
export { getModelCosts, estimatePlannedCost } from './cost-tracker.js';
|
|
41
|
+
export { extractImports, resolveImportPath } from './heuristics.js';
|
|
42
|
+
export { findMatchingTemplate, applyTemplate, suggestSplit } from './slicing-templates.js';
|
|
43
|
+
export { optimizeTopology } from './topology-optimizer.js';
|
|
44
|
+
export type { TopologyInput, TopologyReport } from './topology-optimizer.js';
|