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.
Files changed (42) hide show
  1. package/README.md +127 -140
  2. package/package.json +6 -3
  3. package/src/cli.ts +54 -369
  4. package/src/commands/init.ts +15 -29
  5. package/src/db/memory-db.test.ts +45 -0
  6. package/src/db/memory-db.ts +47 -21
  7. package/src/db/sqlite-setup.ts +26 -3
  8. package/src/db/workflow-db.ts +12 -5
  9. package/src/parser/config-schema.ts +17 -13
  10. package/src/parser/schema.ts +4 -2
  11. package/src/runner/__test__/llm-mock-setup.ts +173 -0
  12. package/src/runner/__test__/llm-test-setup.ts +271 -0
  13. package/src/runner/engine-executor.test.ts +25 -18
  14. package/src/runner/executors/blueprint-executor.ts +0 -1
  15. package/src/runner/executors/dynamic-executor.ts +11 -6
  16. package/src/runner/executors/engine-executor.ts +5 -1
  17. package/src/runner/executors/llm-executor.ts +502 -1033
  18. package/src/runner/executors/memory-executor.ts +35 -19
  19. package/src/runner/executors/plan-executor.ts +0 -1
  20. package/src/runner/executors/types.ts +4 -4
  21. package/src/runner/llm-adapter.integration.test.ts +151 -0
  22. package/src/runner/llm-adapter.ts +270 -1398
  23. package/src/runner/llm-clarification.test.ts +91 -106
  24. package/src/runner/llm-executor.test.ts +217 -1181
  25. package/src/runner/memoization.test.ts +0 -1
  26. package/src/runner/recovery-security.test.ts +51 -20
  27. package/src/runner/reflexion.test.ts +55 -18
  28. package/src/runner/standard-tools-integration.test.ts +137 -87
  29. package/src/runner/step-executor.test.ts +36 -80
  30. package/src/runner/step-executor.ts +0 -2
  31. package/src/runner/test-harness.ts +3 -29
  32. package/src/runner/tool-integration.test.ts +122 -73
  33. package/src/runner/workflow-runner.ts +110 -49
  34. package/src/runner/workflow-scheduler.ts +11 -1
  35. package/src/runner/workflow-summary.ts +144 -0
  36. package/src/utils/auth-manager.test.ts +10 -520
  37. package/src/utils/auth-manager.ts +3 -756
  38. package/src/utils/config-loader.ts +12 -0
  39. package/src/utils/constants.ts +0 -17
  40. package/src/utils/process-sandbox.ts +15 -3
  41. package/src/runner/llm-adapter-runtime.test.ts +0 -209
  42. 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', () => new 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
- const run = await db.getRun(runId);
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: runId,
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
- const run = await db.getRun(runId);
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: runId,
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 = (outfile: string): { copied: number; missing: string[] } => {
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 { copied, checked } = copyOnnxRuntimeLibs(outputPath);
890
- if (copied > 0) {
891
- console.log(`📦 Copied ${copied} ONNX Runtime library file(s) next to ${outputPath}`);
892
- } else if (checked) {
893
- console.log(
894
- 'ℹ️ ONNX Runtime library not found; local embeddings may require external setup.'
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
- const runtimeDeps = copyRuntimeDependencies(outputPath);
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
- auth)
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 auth completion"
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)
@@ -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
- import fullFeatureDemo from '../templates/basics/full-feature-demo.yaml' with { type: 'text' };
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
- type: openai
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
- type: anthropic
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
- groq:
69
- type: openai
70
- base_url: https://api.groq.com/openai/v1
71
- api_key_env: GROQ_API_KEY
72
- default_model: llama-3.3-70b-versatile
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/agent-handoff.yaml', content: agentHandoffWorkflow },
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. Add your API keys to .env');
190
- console.log(' 2. Create a workflow in .keystone/workflows/');
191
- console.log(' 3. Run: keystone run <workflow>');
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
  }
@@ -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' });