keystone-cli 1.3.0 → 2.0.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 +127 -140
- package/package.json +6 -3
- package/src/cli.ts +54 -369
- package/src/commands/init.ts +15 -29
- package/src/db/memory-db.test.ts +45 -0
- package/src/db/memory-db.ts +47 -21
- package/src/db/sqlite-setup.ts +26 -3
- package/src/db/workflow-db.ts +12 -5
- package/src/parser/config-schema.ts +17 -13
- package/src/parser/schema.ts +4 -2
- package/src/runner/__test__/llm-mock-setup.ts +173 -0
- package/src/runner/__test__/llm-test-setup.ts +271 -0
- package/src/runner/engine-executor.test.ts +25 -18
- package/src/runner/executors/blueprint-executor.ts +0 -1
- package/src/runner/executors/dynamic-executor.ts +11 -6
- package/src/runner/executors/engine-executor.ts +5 -1
- package/src/runner/executors/llm-executor.ts +502 -1033
- package/src/runner/executors/memory-executor.ts +35 -19
- package/src/runner/executors/plan-executor.ts +0 -1
- package/src/runner/executors/types.ts +4 -4
- package/src/runner/llm-adapter.integration.test.ts +151 -0
- package/src/runner/llm-adapter.ts +270 -1398
- package/src/runner/llm-clarification.test.ts +91 -106
- package/src/runner/llm-executor.test.ts +217 -1181
- package/src/runner/memoization.test.ts +0 -1
- package/src/runner/recovery-security.test.ts +51 -20
- package/src/runner/reflexion.test.ts +55 -18
- package/src/runner/standard-tools-integration.test.ts +137 -87
- package/src/runner/step-executor.test.ts +36 -80
- package/src/runner/step-executor.ts +0 -2
- package/src/runner/test-harness.ts +3 -29
- package/src/runner/tool-integration.test.ts +122 -73
- package/src/runner/workflow-runner.ts +110 -49
- package/src/runner/workflow-scheduler.ts +11 -1
- package/src/runner/workflow-summary.ts +144 -0
- package/src/utils/auth-manager.test.ts +10 -520
- package/src/utils/auth-manager.ts +3 -756
- package/src/utils/config-loader.ts +12 -0
- package/src/utils/constants.ts +0 -17
- package/src/utils/process-sandbox.ts +15 -3
- package/src/runner/llm-adapter-runtime.test.ts +0 -209
- package/src/runner/llm-adapter.test.ts +0 -1012
package/src/cli.ts
CHANGED
|
@@ -36,7 +36,11 @@ import pkg from '../package.json' with { type: 'json' };
|
|
|
36
36
|
// Bootstrap DI container with default services
|
|
37
37
|
container.factory('logger', () => new ConsoleLogger());
|
|
38
38
|
container.factory('db', () => new WorkflowDb());
|
|
39
|
-
container.factory('memoryDb', () =>
|
|
39
|
+
container.factory('memoryDb', () => {
|
|
40
|
+
const config = ConfigLoader.load();
|
|
41
|
+
const dimension = config.embedding_dimension || 384;
|
|
42
|
+
return new MemoryDb('.keystone/memory.db', dimension);
|
|
43
|
+
});
|
|
40
44
|
|
|
41
45
|
const program = new Command();
|
|
42
46
|
const defaultRetentionDays = ConfigLoader.load().storage?.retention_days ?? 30;
|
|
@@ -455,7 +459,16 @@ program
|
|
|
455
459
|
const eventsEnabled = !!options.events;
|
|
456
460
|
|
|
457
461
|
// Load run from database to get workflow name
|
|
458
|
-
|
|
462
|
+
let run = await db.getRun(runId);
|
|
463
|
+
|
|
464
|
+
if (!run) {
|
|
465
|
+
// Try searching by short ID
|
|
466
|
+
const allRuns = await db.listRuns(500);
|
|
467
|
+
const matching = allRuns.find((r) => r.id.startsWith(runId));
|
|
468
|
+
if (matching) {
|
|
469
|
+
run = await db.getRun(matching.id);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
459
472
|
|
|
460
473
|
if (!run) {
|
|
461
474
|
console.error(`✗ Run not found: ${runId}`);
|
|
@@ -502,7 +515,7 @@ program
|
|
|
502
515
|
: undefined;
|
|
503
516
|
const inputs = parseInputs(options.input);
|
|
504
517
|
const runner = new WorkflowRunner(workflow, {
|
|
505
|
-
resumeRunId:
|
|
518
|
+
resumeRunId: run.id,
|
|
506
519
|
resumeInputs: inputs,
|
|
507
520
|
workflowDir: dirname(workflowPath),
|
|
508
521
|
logger,
|
|
@@ -547,7 +560,16 @@ program
|
|
|
547
560
|
throw new Error(`No runs found for workflow "${workflow.name}"`);
|
|
548
561
|
})();
|
|
549
562
|
|
|
550
|
-
|
|
563
|
+
let run = await db.getRun(runId);
|
|
564
|
+
if (!run) {
|
|
565
|
+
// Try searching by short ID
|
|
566
|
+
const allRuns = await db.listRuns(500);
|
|
567
|
+
const matching = allRuns.find((r) => r.id.startsWith(runId));
|
|
568
|
+
if (matching) {
|
|
569
|
+
run = await db.getRun(matching.id);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
551
573
|
if (!run) {
|
|
552
574
|
throw new Error(`Run not found: ${runId}`);
|
|
553
575
|
}
|
|
@@ -587,7 +609,7 @@ program
|
|
|
587
609
|
}
|
|
588
610
|
: undefined;
|
|
589
611
|
const runner = new WorkflowRunner(workflow, {
|
|
590
|
-
resumeRunId:
|
|
612
|
+
resumeRunId: run.id,
|
|
591
613
|
resumeInputs: inputs,
|
|
592
614
|
workflowDir: dirname(resolvedPath),
|
|
593
615
|
logger,
|
|
@@ -737,32 +759,6 @@ program
|
|
|
737
759
|
buildArgs.push('--external', pkg);
|
|
738
760
|
}
|
|
739
761
|
|
|
740
|
-
const copyOnnxRuntimeLibs = (outfile: string): { copied: number; checked: boolean } => {
|
|
741
|
-
const runtimeDir = join(
|
|
742
|
-
projectDir,
|
|
743
|
-
'node_modules',
|
|
744
|
-
'onnxruntime-node',
|
|
745
|
-
'bin',
|
|
746
|
-
'napi-v3',
|
|
747
|
-
process.platform,
|
|
748
|
-
process.arch
|
|
749
|
-
);
|
|
750
|
-
if (!existsSync(runtimeDir)) return { copied: 0, checked: false };
|
|
751
|
-
|
|
752
|
-
const entries = readdirSync(runtimeDir, { withFileTypes: true });
|
|
753
|
-
const libPattern =
|
|
754
|
-
process.platform === 'win32' ? /^onnxruntime.*\.dll$/i : /^libonnxruntime/i;
|
|
755
|
-
let copied = 0;
|
|
756
|
-
|
|
757
|
-
for (const entry of entries) {
|
|
758
|
-
if (!entry.isFile() || !libPattern.test(entry.name)) continue;
|
|
759
|
-
copyFileSync(join(runtimeDir, entry.name), join(dirname(outfile), entry.name));
|
|
760
|
-
copied += 1;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
return { copied, checked: true };
|
|
764
|
-
};
|
|
765
|
-
|
|
766
762
|
const copyDir = (source: string, destination: string): void => {
|
|
767
763
|
const stats = lstatSync(source);
|
|
768
764
|
if (stats.isSymbolicLink()) {
|
|
@@ -784,21 +780,16 @@ program
|
|
|
784
780
|
}
|
|
785
781
|
};
|
|
786
782
|
|
|
787
|
-
const copyRuntimeDependencies = (
|
|
783
|
+
const copyRuntimeDependencies = (
|
|
784
|
+
outfile: string,
|
|
785
|
+
additionalPackages: string[] = []
|
|
786
|
+
): { copied: number; missing: string[] } => {
|
|
788
787
|
const runtimeDir = join(dirname(outfile), 'keystone-runtime');
|
|
789
788
|
const runtimeNodeModules = join(runtimeDir, 'node_modules');
|
|
790
789
|
rmSync(runtimeDir, { recursive: true, force: true });
|
|
791
790
|
mkdirSync(runtimeNodeModules, { recursive: true });
|
|
792
791
|
|
|
793
|
-
const roots = [
|
|
794
|
-
'@xenova/transformers',
|
|
795
|
-
'onnxruntime-node',
|
|
796
|
-
'onnxruntime-common',
|
|
797
|
-
'sharp',
|
|
798
|
-
'@huggingface/jinja',
|
|
799
|
-
'sqlite-vec',
|
|
800
|
-
`sqlite-vec-${osName}-${process.arch}`,
|
|
801
|
-
];
|
|
792
|
+
const roots = ['sqlite-vec', `sqlite-vec-${osName}-${process.arch}`, ...additionalPackages];
|
|
802
793
|
|
|
803
794
|
const require = createRequire(import.meta.url);
|
|
804
795
|
const resolvePackageDir = (pkg: string): string | null => {
|
|
@@ -886,15 +877,27 @@ program
|
|
|
886
877
|
});
|
|
887
878
|
|
|
888
879
|
if (result.status === 0) {
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
880
|
+
const keystoneConfigPath = join(keystoneDir, 'config.yaml');
|
|
881
|
+
const providerPackages: string[] = [];
|
|
882
|
+
|
|
883
|
+
if (existsSync(keystoneConfigPath)) {
|
|
884
|
+
try {
|
|
885
|
+
const configContent = readFileSync(keystoneConfigPath, 'utf8');
|
|
886
|
+
const config = parseYaml(configContent) as any;
|
|
887
|
+
if (config.providers) {
|
|
888
|
+
for (const key of Object.keys(config.providers)) {
|
|
889
|
+
const provider = config.providers[key];
|
|
890
|
+
if (provider.package && typeof provider.package === 'string') {
|
|
891
|
+
providerPackages.push(provider.package);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
} catch (e) {
|
|
896
|
+
console.warn('Warning: Failed to parse .keystone/config.yaml for providers');
|
|
897
|
+
}
|
|
896
898
|
}
|
|
897
|
-
|
|
899
|
+
|
|
900
|
+
const runtimeDeps = copyRuntimeDependencies(outputPath, providerPackages);
|
|
898
901
|
if (runtimeDeps.copied > 0) {
|
|
899
902
|
console.log(
|
|
900
903
|
`📦 Copied ${runtimeDeps.copied} runtime package(s) to ${join(
|
|
@@ -1450,315 +1453,6 @@ configCmd
|
|
|
1450
1453
|
});
|
|
1451
1454
|
|
|
1452
1455
|
// ===== keystone auth =====
|
|
1453
|
-
const auth = program.command('auth').description('Authentication management');
|
|
1454
|
-
|
|
1455
|
-
auth
|
|
1456
|
-
.command('login')
|
|
1457
|
-
.description('Login to an authentication provider')
|
|
1458
|
-
.argument('[provider]', 'Authentication provider', 'github')
|
|
1459
|
-
.option('-t, --token <token>', 'Personal Access Token (if not using interactive mode)')
|
|
1460
|
-
.option('--project <project_id>', 'Google Cloud project ID (Gemini OAuth)')
|
|
1461
|
-
.action(async (provider, options) => {
|
|
1462
|
-
const { AuthManager } = await import('./utils/auth-manager.ts');
|
|
1463
|
-
const providerName = provider.toLowerCase();
|
|
1464
|
-
|
|
1465
|
-
if (providerName === 'github') {
|
|
1466
|
-
let token = options.token;
|
|
1467
|
-
|
|
1468
|
-
if (!token) {
|
|
1469
|
-
try {
|
|
1470
|
-
const deviceLogin = await AuthManager.initGitHubDeviceLogin();
|
|
1471
|
-
|
|
1472
|
-
console.log('\nTo login with GitHub:');
|
|
1473
|
-
console.log(`1. Visit: ${deviceLogin.verification_uri}`);
|
|
1474
|
-
console.log(`2. Enter code: ${deviceLogin.user_code}\n`);
|
|
1475
|
-
|
|
1476
|
-
console.log('Waiting for authorization...');
|
|
1477
|
-
token = await AuthManager.pollGitHubDeviceLogin(deviceLogin.device_code);
|
|
1478
|
-
} catch (error) {
|
|
1479
|
-
console.error(
|
|
1480
|
-
'\n✗ Failed to login with GitHub device flow:',
|
|
1481
|
-
error instanceof Error ? error.message : error
|
|
1482
|
-
);
|
|
1483
|
-
console.log('\nFalling back to manual token entry...');
|
|
1484
|
-
|
|
1485
|
-
console.log('\nTo login with GitHub manually:');
|
|
1486
|
-
console.log(
|
|
1487
|
-
'1. Generate a Personal Access Token (Classic) with "copilot" scope (or full repo access).'
|
|
1488
|
-
);
|
|
1489
|
-
console.log(' https://github.com/settings/tokens/new');
|
|
1490
|
-
console.log('2. Paste the token below:\n');
|
|
1491
|
-
|
|
1492
|
-
const prompt = 'Token: ';
|
|
1493
|
-
process.stdout.write(prompt);
|
|
1494
|
-
for await (const line of console) {
|
|
1495
|
-
token = line.trim();
|
|
1496
|
-
break;
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
if (token) {
|
|
1502
|
-
AuthManager.save({ github_token: token });
|
|
1503
|
-
// Force refresh of Copilot token to verify
|
|
1504
|
-
try {
|
|
1505
|
-
const copilotToken = await AuthManager.getCopilotToken();
|
|
1506
|
-
if (copilotToken) {
|
|
1507
|
-
console.log('\n✓ Successfully logged in to GitHub and retrieved Copilot token.');
|
|
1508
|
-
} else {
|
|
1509
|
-
console.error(
|
|
1510
|
-
'\n✗ Saved GitHub token, but failed to retrieve Copilot token. Please check scopes.'
|
|
1511
|
-
);
|
|
1512
|
-
}
|
|
1513
|
-
} catch (e) {
|
|
1514
|
-
console.error('\n✗ Failed to verify token:', e instanceof Error ? e.message : e);
|
|
1515
|
-
}
|
|
1516
|
-
} else {
|
|
1517
|
-
console.error('✗ No token provided.');
|
|
1518
|
-
process.exit(1);
|
|
1519
|
-
}
|
|
1520
|
-
} else if (providerName === 'openai-chatgpt') {
|
|
1521
|
-
try {
|
|
1522
|
-
await AuthManager.loginOpenAIChatGPT();
|
|
1523
|
-
console.log('\n✓ Successfully logged in to OpenAI ChatGPT.');
|
|
1524
|
-
return;
|
|
1525
|
-
} catch (error) {
|
|
1526
|
-
console.error(
|
|
1527
|
-
'\n✗ Failed to login with OpenAI ChatGPT:',
|
|
1528
|
-
error instanceof Error ? error.message : error
|
|
1529
|
-
);
|
|
1530
|
-
process.exit(1);
|
|
1531
|
-
}
|
|
1532
|
-
} else if (providerName === 'anthropic-claude') {
|
|
1533
|
-
try {
|
|
1534
|
-
const { url, verifier } = AuthManager.createAnthropicClaudeAuth();
|
|
1535
|
-
|
|
1536
|
-
console.log('\nTo login with Anthropic Claude (Pro/Max):');
|
|
1537
|
-
console.log('1. Visit the following URL in your browser:');
|
|
1538
|
-
console.log(` ${url}\n`);
|
|
1539
|
-
console.log('2. Copy the authorization code and paste it below:\n');
|
|
1540
|
-
|
|
1541
|
-
try {
|
|
1542
|
-
const { platform } = process;
|
|
1543
|
-
const command =
|
|
1544
|
-
platform === 'win32' ? 'start' : platform === 'darwin' ? 'open' : 'xdg-open';
|
|
1545
|
-
const { spawn } = require('node:child_process');
|
|
1546
|
-
spawn(command, [url]);
|
|
1547
|
-
} catch (e) {
|
|
1548
|
-
// Ignore if we can't open the browser automatically
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
let code = options.token;
|
|
1552
|
-
if (!code) {
|
|
1553
|
-
const prompt = 'Authorization Code: ';
|
|
1554
|
-
process.stdout.write(prompt);
|
|
1555
|
-
for await (const line of console) {
|
|
1556
|
-
code = line.trim();
|
|
1557
|
-
break;
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
if (!code) {
|
|
1562
|
-
console.error('✗ No authorization code provided.');
|
|
1563
|
-
process.exit(1);
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
const data = await AuthManager.exchangeAnthropicClaudeCode(code, verifier);
|
|
1567
|
-
AuthManager.save({
|
|
1568
|
-
anthropic_claude: {
|
|
1569
|
-
access_token: data.access_token,
|
|
1570
|
-
refresh_token: data.refresh_token,
|
|
1571
|
-
expires_at: Math.floor(Date.now() / 1000) + data.expires_in,
|
|
1572
|
-
},
|
|
1573
|
-
});
|
|
1574
|
-
console.log('\n✓ Successfully logged in to Anthropic Claude.');
|
|
1575
|
-
return;
|
|
1576
|
-
} catch (error) {
|
|
1577
|
-
console.error(
|
|
1578
|
-
'\n✗ Failed to login with Anthropic Claude:',
|
|
1579
|
-
error instanceof Error ? error.message : error
|
|
1580
|
-
);
|
|
1581
|
-
process.exit(1);
|
|
1582
|
-
}
|
|
1583
|
-
} else if (providerName === 'gemini' || providerName === 'google-gemini') {
|
|
1584
|
-
try {
|
|
1585
|
-
await AuthManager.loginGoogleGemini(options.project);
|
|
1586
|
-
console.log('\n✓ Successfully logged in to Google Gemini.');
|
|
1587
|
-
return;
|
|
1588
|
-
} catch (error) {
|
|
1589
|
-
console.error(
|
|
1590
|
-
'\n✗ Failed to login with Google Gemini:',
|
|
1591
|
-
error instanceof Error ? error.message : error
|
|
1592
|
-
);
|
|
1593
|
-
process.exit(1);
|
|
1594
|
-
}
|
|
1595
|
-
} else if (providerName === 'openai' || providerName === 'anthropic') {
|
|
1596
|
-
let key = options.token; // Use --token if provided as the API key
|
|
1597
|
-
|
|
1598
|
-
if (!key) {
|
|
1599
|
-
console.log(`\n🔑 Login to ${providerName.toUpperCase()}`);
|
|
1600
|
-
console.log(` Please provide your ${providerName.toUpperCase()} API key.\n`);
|
|
1601
|
-
const prompt = 'API Key: ';
|
|
1602
|
-
process.stdout.write(prompt);
|
|
1603
|
-
for await (const line of console) {
|
|
1604
|
-
key = line.trim();
|
|
1605
|
-
break;
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
if (key) {
|
|
1610
|
-
if (providerName === 'openai') {
|
|
1611
|
-
AuthManager.save({ openai_api_key: key });
|
|
1612
|
-
} else {
|
|
1613
|
-
AuthManager.save({ anthropic_api_key: key });
|
|
1614
|
-
}
|
|
1615
|
-
console.log(`\n✓ Successfully saved ${providerName.toUpperCase()} API key.`);
|
|
1616
|
-
} else {
|
|
1617
|
-
console.error('✗ No API key provided.');
|
|
1618
|
-
process.exit(1);
|
|
1619
|
-
}
|
|
1620
|
-
} else {
|
|
1621
|
-
console.error(`✗ Unsupported provider: ${providerName}`);
|
|
1622
|
-
process.exit(1);
|
|
1623
|
-
}
|
|
1624
|
-
});
|
|
1625
|
-
|
|
1626
|
-
auth
|
|
1627
|
-
.command('status')
|
|
1628
|
-
.description('Show authentication status')
|
|
1629
|
-
.argument('[provider]', 'Authentication provider')
|
|
1630
|
-
.action(async (provider) => {
|
|
1631
|
-
const { AuthManager } = await import('./utils/auth-manager.ts');
|
|
1632
|
-
const auth = AuthManager.load();
|
|
1633
|
-
const providerName = provider?.toLowerCase();
|
|
1634
|
-
|
|
1635
|
-
console.log('\n🏛️ Authentication Status:');
|
|
1636
|
-
|
|
1637
|
-
if (!providerName || providerName === 'github' || providerName === 'copilot') {
|
|
1638
|
-
if (auth.github_token) {
|
|
1639
|
-
console.log(' ✓ Logged into GitHub');
|
|
1640
|
-
if (auth.copilot_expires_at) {
|
|
1641
|
-
const expires = new Date(auth.copilot_expires_at * 1000);
|
|
1642
|
-
console.log(` ✓ Copilot session expires: ${expires.toLocaleString()}`);
|
|
1643
|
-
}
|
|
1644
|
-
} else if (providerName) {
|
|
1645
|
-
console.log(
|
|
1646
|
-
` ⊘ Not logged into GitHub. Run "keystone auth login github" to authenticate.`
|
|
1647
|
-
);
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
if (!providerName || providerName === 'openai' || providerName === 'openai-chatgpt') {
|
|
1652
|
-
if (auth.openai_api_key) {
|
|
1653
|
-
console.log(' ✓ OpenAI API key configured');
|
|
1654
|
-
}
|
|
1655
|
-
if (auth.openai_chatgpt) {
|
|
1656
|
-
console.log(' ✓ OpenAI ChatGPT subscription (OAuth) authenticated');
|
|
1657
|
-
if (auth.openai_chatgpt.expires_at) {
|
|
1658
|
-
const expires = new Date(auth.openai_chatgpt.expires_at * 1000);
|
|
1659
|
-
console.log(` Session expires: ${expires.toLocaleString()}`);
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
if (providerName && !auth.openai_api_key && !auth.openai_chatgpt) {
|
|
1664
|
-
console.log(
|
|
1665
|
-
` ⊘ OpenAI authentication not configured. Run "keystone auth login openai" or "keystone auth login openai-chatgpt" to authenticate.`
|
|
1666
|
-
);
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
if (!providerName || providerName === 'anthropic' || providerName === 'anthropic-claude') {
|
|
1671
|
-
if (auth.anthropic_api_key) {
|
|
1672
|
-
console.log(' ✓ Anthropic API key configured');
|
|
1673
|
-
}
|
|
1674
|
-
if (auth.anthropic_claude) {
|
|
1675
|
-
console.log(' ✓ Anthropic Claude subscription (OAuth) authenticated');
|
|
1676
|
-
if (auth.anthropic_claude.expires_at) {
|
|
1677
|
-
const expires = new Date(auth.anthropic_claude.expires_at * 1000);
|
|
1678
|
-
console.log(` Session expires: ${expires.toLocaleString()}`);
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
if (providerName && !auth.anthropic_api_key && !auth.anthropic_claude) {
|
|
1683
|
-
console.log(
|
|
1684
|
-
` ⊘ Anthropic authentication not configured. Run "keystone auth login anthropic" or "keystone auth login anthropic-claude" to authenticate.`
|
|
1685
|
-
);
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
|
-
if (!providerName || providerName === 'gemini' || providerName === 'google-gemini') {
|
|
1690
|
-
if (auth.google_gemini) {
|
|
1691
|
-
console.log(' ✓ Google Gemini subscription (OAuth) authenticated');
|
|
1692
|
-
if (auth.google_gemini.email) {
|
|
1693
|
-
console.log(` Account: ${auth.google_gemini.email}`);
|
|
1694
|
-
}
|
|
1695
|
-
if (auth.google_gemini.expires_at) {
|
|
1696
|
-
const expires = new Date(auth.google_gemini.expires_at * 1000);
|
|
1697
|
-
console.log(` Session expires: ${expires.toLocaleString()}`);
|
|
1698
|
-
}
|
|
1699
|
-
} else if (providerName) {
|
|
1700
|
-
console.log(
|
|
1701
|
-
` ⊘ Google Gemini authentication not configured. Run "keystone auth login gemini" to authenticate.`
|
|
1702
|
-
);
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
|
|
1706
|
-
if (
|
|
1707
|
-
!auth.github_token &&
|
|
1708
|
-
!auth.openai_api_key &&
|
|
1709
|
-
!auth.openai_chatgpt &&
|
|
1710
|
-
!auth.anthropic_api_key &&
|
|
1711
|
-
!auth.anthropic_claude &&
|
|
1712
|
-
!auth.google_gemini &&
|
|
1713
|
-
!providerName
|
|
1714
|
-
) {
|
|
1715
|
-
console.log(' ⊘ No providers configured. Run "keystone auth login" to authenticate.');
|
|
1716
|
-
}
|
|
1717
|
-
});
|
|
1718
|
-
|
|
1719
|
-
auth
|
|
1720
|
-
.command('logout')
|
|
1721
|
-
.description('Logout and clear authentication tokens')
|
|
1722
|
-
.argument('[provider]', 'Authentication provider')
|
|
1723
|
-
.action(async (provider) => {
|
|
1724
|
-
const { AuthManager } = await import('./utils/auth-manager.ts');
|
|
1725
|
-
const providerName = provider?.toLowerCase();
|
|
1726
|
-
|
|
1727
|
-
const auth = AuthManager.load();
|
|
1728
|
-
|
|
1729
|
-
if (!providerName || providerName === 'github' || providerName === 'copilot') {
|
|
1730
|
-
AuthManager.save({
|
|
1731
|
-
github_token: undefined,
|
|
1732
|
-
copilot_token: undefined,
|
|
1733
|
-
copilot_expires_at: undefined,
|
|
1734
|
-
});
|
|
1735
|
-
console.log('✓ Successfully logged out of GitHub.');
|
|
1736
|
-
} else if (providerName === 'openai' || providerName === 'openai-chatgpt') {
|
|
1737
|
-
AuthManager.save({
|
|
1738
|
-
openai_api_key: providerName === 'openai' ? undefined : auth.openai_api_key,
|
|
1739
|
-
openai_chatgpt: undefined,
|
|
1740
|
-
});
|
|
1741
|
-
console.log(
|
|
1742
|
-
`✓ Successfully cleared ${providerName === 'openai' ? 'OpenAI API key and ' : ''}ChatGPT session.`
|
|
1743
|
-
);
|
|
1744
|
-
} else if (providerName === 'anthropic' || providerName === 'anthropic-claude') {
|
|
1745
|
-
AuthManager.save({
|
|
1746
|
-
anthropic_api_key: providerName === 'anthropic' ? undefined : auth.anthropic_api_key,
|
|
1747
|
-
anthropic_claude: undefined,
|
|
1748
|
-
});
|
|
1749
|
-
console.log(
|
|
1750
|
-
`✓ Successfully cleared ${providerName === 'anthropic' ? 'Anthropic API key and ' : ''}Claude session.`
|
|
1751
|
-
);
|
|
1752
|
-
} else if (providerName === 'gemini' || providerName === 'google-gemini') {
|
|
1753
|
-
AuthManager.save({
|
|
1754
|
-
google_gemini: undefined,
|
|
1755
|
-
});
|
|
1756
|
-
console.log('✓ Successfully cleared Google Gemini session.');
|
|
1757
|
-
} else {
|
|
1758
|
-
console.error(`✗ Unknown provider: ${providerName}`);
|
|
1759
|
-
process.exit(1);
|
|
1760
|
-
}
|
|
1761
|
-
});
|
|
1762
1456
|
|
|
1763
1457
|
// ===== Internal Helper Commands (Hidden) =====
|
|
1764
1458
|
program.command('_list-workflows', { hidden: true }).action(() => {
|
|
@@ -1819,7 +1513,6 @@ _keystone() {
|
|
|
1819
1513
|
'ui:Open the TUI dashboard'
|
|
1820
1514
|
'mcp:Start the Model Context Protocol server'
|
|
1821
1515
|
'config:Show current configuration'
|
|
1822
|
-
'auth:Authentication management'
|
|
1823
1516
|
'completion:Generate shell completion script'
|
|
1824
1517
|
)
|
|
1825
1518
|
_describe -t commands 'keystone command' commands
|
|
@@ -1851,15 +1544,7 @@ _keystone() {
|
|
|
1851
1544
|
logs)
|
|
1852
1545
|
_arguments ':run_id:__keystone_runs'
|
|
1853
1546
|
;;
|
|
1854
|
-
|
|
1855
|
-
local -a auth_commands
|
|
1856
|
-
auth_commands=(
|
|
1857
|
-
'login:Login to an authentication provider'
|
|
1858
|
-
'status:Show authentication status'
|
|
1859
|
-
'logout:Logout and clear authentication tokens'
|
|
1860
|
-
)
|
|
1861
|
-
_describe -t auth_commands 'auth command' auth_commands
|
|
1862
|
-
;;
|
|
1547
|
+
|
|
1863
1548
|
esac
|
|
1864
1549
|
;;
|
|
1865
1550
|
esac
|
|
@@ -1883,7 +1568,7 @@ __keystone_runs() {
|
|
|
1883
1568
|
COMPREPLY=()
|
|
1884
1569
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
1885
1570
|
prev="\${COMP_WORDS[COMP_CWORD - 1]}"
|
|
1886
|
-
opts="init validate lint graph run watch resume rerun workflows history logs prune ui mcp config
|
|
1571
|
+
opts="init validate lint graph run watch resume rerun workflows history logs prune ui mcp config completion"
|
|
1887
1572
|
|
|
1888
1573
|
case "\${prev}" in
|
|
1889
1574
|
run|graph|rerun)
|
package/src/commands/init.ts
CHANGED
|
@@ -17,15 +17,7 @@ import architectAgent from '../templates/agents/keystone-architect.md' with { ty
|
|
|
17
17
|
import softwareEngineerAgent from '../templates/agents/software-engineer.md' with { type: 'text' };
|
|
18
18
|
import summarizerAgent from '../templates/agents/summarizer.md' with { type: 'text' };
|
|
19
19
|
import testerAgent from '../templates/agents/tester.md' with { type: 'text' };
|
|
20
|
-
|
|
21
|
-
import idempotencyExample from '../templates/control-flow/idempotency-example.yaml' with {
|
|
22
|
-
type: 'text',
|
|
23
|
-
};
|
|
24
|
-
import dynamicDemo from '../templates/dynamic-demo.yaml' with { type: 'text' };
|
|
25
|
-
import artifactExample from '../templates/features/artifact-example.yaml' with { type: 'text' };
|
|
26
|
-
import scriptExample from '../templates/features/script-example.yaml' with { type: 'text' };
|
|
27
|
-
// Import templates
|
|
28
|
-
import agentHandoffWorkflow from '../templates/patterns/agent-handoff.yaml' with { type: 'text' };
|
|
20
|
+
|
|
29
21
|
import decomposeImplementWorkflow from '../templates/scaffolding/decompose-implement.yaml' with {
|
|
30
22
|
type: 'text',
|
|
31
23
|
};
|
|
@@ -55,27 +47,27 @@ const DEFAULT_CONFIG = `# Keystone Configuration
|
|
|
55
47
|
default_provider: openai
|
|
56
48
|
|
|
57
49
|
providers:
|
|
50
|
+
# Install with: npm install @ai-sdk/openai @ai-sdk/anthropic
|
|
58
51
|
openai:
|
|
59
|
-
|
|
60
|
-
base_url: https://api.openai.com/v1
|
|
52
|
+
package: "@ai-sdk/openai"
|
|
61
53
|
api_key_env: OPENAI_API_KEY
|
|
62
54
|
default_model: gpt-4o
|
|
63
55
|
anthropic:
|
|
64
|
-
|
|
65
|
-
base_url: https://api.anthropic.com/v1
|
|
56
|
+
package: "@ai-sdk/anthropic"
|
|
66
57
|
api_key_env: ANTHROPIC_API_KEY
|
|
67
58
|
default_model: claude-3-5-sonnet-20240620
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
|
|
60
|
+
# Example: OpenAI-compatible provider (Groq)
|
|
61
|
+
# groq:
|
|
62
|
+
# package: "@ai-sdk/openai"
|
|
63
|
+
# base_url: https://api.groq.com/openai/v1
|
|
64
|
+
# api_key_env: GROQ_API_KEY
|
|
65
|
+
# default_model: llama-3.3-70b-versatile
|
|
73
66
|
|
|
74
67
|
model_mappings:
|
|
75
68
|
"gpt-*": openai
|
|
76
69
|
"claude-*": anthropic
|
|
77
70
|
"o1-*": openai
|
|
78
|
-
"llama-*": groq
|
|
79
71
|
|
|
80
72
|
# mcp_servers:
|
|
81
73
|
# filesystem:
|
|
@@ -111,7 +103,7 @@ const SEEDS = [
|
|
|
111
103
|
{ path: '.keystone/workflows/decompose-implement.yaml', content: decomposeImplementWorkflow },
|
|
112
104
|
{ path: '.keystone/workflows/decompose-review.yaml', content: decomposeReviewWorkflow },
|
|
113
105
|
{ path: '.keystone/workflows/review-loop.yaml', content: reviewLoopWorkflow },
|
|
114
|
-
{ path: '.keystone/workflows/
|
|
106
|
+
{ path: '.keystone/workflows/dev.yaml', content: devWorkflow },
|
|
115
107
|
{ path: '.keystone/workflows/agents/keystone-architect.md', content: architectAgent },
|
|
116
108
|
{ path: '.keystone/workflows/agents/general.md', content: generalAgent },
|
|
117
109
|
{ path: '.keystone/workflows/agents/explore.md', content: exploreAgent },
|
|
@@ -119,13 +111,7 @@ const SEEDS = [
|
|
|
119
111
|
{ path: '.keystone/workflows/agents/summarizer.md', content: summarizerAgent },
|
|
120
112
|
{ path: '.keystone/workflows/agents/handoff-router.md', content: handoffRouterAgent },
|
|
121
113
|
{ path: '.keystone/workflows/agents/handoff-specialist.md', content: handoffSpecialistAgent },
|
|
122
|
-
{ path: '.keystone/workflows/dev.yaml', content: devWorkflow },
|
|
123
114
|
{ path: '.keystone/workflows/agents/tester.md', content: testerAgent },
|
|
124
|
-
{ path: '.keystone/workflows/script-example.yaml', content: scriptExample },
|
|
125
|
-
{ path: '.keystone/workflows/artifact-example.yaml', content: artifactExample },
|
|
126
|
-
{ path: '.keystone/workflows/idempotency-example.yaml', content: idempotencyExample },
|
|
127
|
-
{ path: '.keystone/workflows/full-feature-demo.yaml', content: fullFeatureDemo },
|
|
128
|
-
{ path: '.keystone/workflows/dynamic-demo.yaml', content: dynamicDemo },
|
|
129
115
|
];
|
|
130
116
|
|
|
131
117
|
export function registerInitCommand(program: Command): void {
|
|
@@ -186,8 +172,8 @@ export function registerInitCommand(program: Command): void {
|
|
|
186
172
|
|
|
187
173
|
console.log('\n✨ Keystone project initialized!');
|
|
188
174
|
console.log('\nNext steps:');
|
|
189
|
-
console.log(' 1.
|
|
190
|
-
console.log(' 2.
|
|
191
|
-
console.log(' 3. Run: keystone run
|
|
175
|
+
console.log(' 1. Install AI SDK providers: npm install @ai-sdk/openai @ai-sdk/anthropic');
|
|
176
|
+
console.log(' 2. Add your API keys to .env');
|
|
177
|
+
console.log(' 3. Run a workflow: keystone run scaffold-feature');
|
|
192
178
|
});
|
|
193
179
|
}
|
package/src/db/memory-db.test.ts
CHANGED
|
@@ -25,6 +25,51 @@ describe('MemoryDb', () => {
|
|
|
25
25
|
expect(typeof id).toBe('string');
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
+
test('should support dynamic dimensions', async () => {
|
|
29
|
+
const DIM_1536 = 1536;
|
|
30
|
+
const testDb1536 = '.keystone/test-memory-1536.db';
|
|
31
|
+
if (fs.existsSync(testDb1536)) fs.unlinkSync(testDb1536);
|
|
32
|
+
|
|
33
|
+
const db1536 = new MemoryDb(testDb1536, DIM_1536);
|
|
34
|
+
try {
|
|
35
|
+
const id = await db1536.store('hi', Array(DIM_1536).fill(0.5));
|
|
36
|
+
expect(id).toBeDefined();
|
|
37
|
+
|
|
38
|
+
const results = await db1536.search(Array(DIM_1536).fill(0.5), 1);
|
|
39
|
+
expect(results.length).toBe(1);
|
|
40
|
+
} finally {
|
|
41
|
+
db1536.close();
|
|
42
|
+
if (fs.existsSync(testDb1536)) fs.unlinkSync(testDb1536);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('should support dimension mismatch by creating new table', async () => {
|
|
47
|
+
const testDbMismatch = '.keystone/test-memory-mismatch.db';
|
|
48
|
+
if (fs.existsSync(testDbMismatch)) fs.unlinkSync(testDbMismatch);
|
|
49
|
+
|
|
50
|
+
// 1. Create with dimension 128 (default legacy table name 'vec_memory' if not specified?
|
|
51
|
+
// Actually the code uses vec_memory_128 unless it finds 'vec_memory' already.
|
|
52
|
+
// Wait, let's force the scenario where 'vec_memory' exists with wrong dim.
|
|
53
|
+
// To do that we'd need to simulate a legacy DB.
|
|
54
|
+
// But the current code creates `vec_memory_128` by default not `vec_memory`.
|
|
55
|
+
// The legacy logic only triggers IF `vec_memory` exists.
|
|
56
|
+
|
|
57
|
+
// Let's just test that we can use different dimensions on the same DB file.
|
|
58
|
+
const db1 = new MemoryDb(testDbMismatch, 128);
|
|
59
|
+
await db1.store('test128', Array(128).fill(0));
|
|
60
|
+
db1.close();
|
|
61
|
+
|
|
62
|
+
const db2 = new MemoryDb(testDbMismatch, 384);
|
|
63
|
+
// Should NOT throw
|
|
64
|
+
await db2.store('test384', Array(384).fill(0));
|
|
65
|
+
const results = await db2.search(Array(384).fill(0), 1);
|
|
66
|
+
expect(results.length).toBe(1);
|
|
67
|
+
expect(results[0].text).toBe('test384');
|
|
68
|
+
db2.close();
|
|
69
|
+
|
|
70
|
+
if (fs.existsSync(testDbMismatch)) fs.unlinkSync(testDbMismatch);
|
|
71
|
+
});
|
|
72
|
+
|
|
28
73
|
test('should search and retrieve result', async () => {
|
|
29
74
|
// Store another item to search for
|
|
30
75
|
await db.store('search target', Array(384).fill(0.9), { tag: 'target' });
|