@antseed/cli 0.1.0 → 0.1.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 (43) hide show
  1. package/dist/cli/commands/dashboard.js +1 -1
  2. package/dist/cli/commands/dashboard.js.map +1 -1
  3. package/dist/proxy/buyer-proxy.d.ts.map +1 -1
  4. package/dist/proxy/buyer-proxy.js +95 -16
  5. package/dist/proxy/buyer-proxy.js.map +1 -1
  6. package/package.json +19 -17
  7. package/.env.example +0 -15
  8. package/src/cli/commands/balance.ts +0 -77
  9. package/src/cli/commands/browse.ts +0 -113
  10. package/src/cli/commands/config.ts +0 -271
  11. package/src/cli/commands/connect.test.ts +0 -69
  12. package/src/cli/commands/connect.ts +0 -342
  13. package/src/cli/commands/dashboard.ts +0 -59
  14. package/src/cli/commands/deposit.ts +0 -61
  15. package/src/cli/commands/dev.ts +0 -107
  16. package/src/cli/commands/init.ts +0 -99
  17. package/src/cli/commands/plugin-create.test.ts +0 -60
  18. package/src/cli/commands/plugin-create.ts +0 -230
  19. package/src/cli/commands/plugin.test.ts +0 -55
  20. package/src/cli/commands/plugin.ts +0 -295
  21. package/src/cli/commands/profile.ts +0 -95
  22. package/src/cli/commands/seed.test.ts +0 -70
  23. package/src/cli/commands/seed.ts +0 -447
  24. package/src/cli/commands/status.ts +0 -73
  25. package/src/cli/commands/types.ts +0 -56
  26. package/src/cli/commands/withdraw.ts +0 -61
  27. package/src/cli/formatters.ts +0 -64
  28. package/src/cli/index.ts +0 -46
  29. package/src/cli/shutdown.ts +0 -38
  30. package/src/config/defaults.ts +0 -49
  31. package/src/config/effective.test.ts +0 -80
  32. package/src/config/effective.ts +0 -119
  33. package/src/config/loader.test.ts +0 -95
  34. package/src/config/loader.ts +0 -251
  35. package/src/config/types.ts +0 -139
  36. package/src/config/validation.ts +0 -78
  37. package/src/env/load-env.ts +0 -20
  38. package/src/plugins/loader.ts +0 -96
  39. package/src/plugins/manager.ts +0 -66
  40. package/src/plugins/registry.ts +0 -45
  41. package/src/proxy/buyer-proxy.ts +0 -604
  42. package/src/status/node-status.ts +0 -105
  43. package/tsconfig.json +0 -9
@@ -1,59 +0,0 @@
1
- import type { Command } from 'commander';
2
- import chalk from 'chalk';
3
- import ora from 'ora';
4
- import open from 'open';
5
- import { getGlobalOptions } from './types.js';
6
- import { loadConfig } from '../../config/loader.js';
7
- import { createDashboardServer } from 'antseed-dashboard';
8
- import type { DashboardServer } from 'antseed-dashboard';
9
- import { setupShutdownHandler } from '../shutdown.js';
10
-
11
- const DEFAULT_DASHBOARD_PORT = 3117;
12
-
13
- /**
14
- * Register the `antseed dashboard` command on the Commander program.
15
- */
16
- export function registerDashboardCommand(program: Command): void {
17
- program
18
- .command('dashboard')
19
- .description('Start the web dashboard for monitoring and configuration')
20
- .option('-p, --port <number>', 'dashboard port', (v) => parseInt(v, 10), DEFAULT_DASHBOARD_PORT)
21
- .option('--no-open', 'do not open browser automatically')
22
- .action(async (options) => {
23
- const globalOpts = getGlobalOptions(program);
24
- const config = await loadConfig(globalOpts.config);
25
-
26
- const port = (options.port ?? DEFAULT_DASHBOARD_PORT) as number;
27
- const spinner = ora('Starting dashboard server...').start();
28
-
29
- let server: DashboardServer;
30
- try {
31
- server = await createDashboardServer(
32
- config as unknown as Parameters<typeof createDashboardServer>[0],
33
- port,
34
- { configPath: globalOpts.config }
35
- );
36
- await server.start();
37
- spinner.succeed(chalk.green('Dashboard running'));
38
- } catch (err) {
39
- spinner.fail(chalk.red(`Failed to start dashboard: ${(err as Error).message}`));
40
- process.exit(1);
41
- }
42
-
43
- const url = `http://localhost:${port}`;
44
- console.log('');
45
- console.log(chalk.bold('Dashboard: ') + chalk.cyan(url));
46
- console.log(chalk.dim('Press Ctrl+C to stop'));
47
- console.log('');
48
-
49
- if (options.open !== false) {
50
- await open(url);
51
- }
52
-
53
- setupShutdownHandler(async () => {
54
- spinner.start('Stopping dashboard server...');
55
- await server.stop();
56
- spinner.succeed('Dashboard stopped.');
57
- });
58
- });
59
- }
@@ -1,61 +0,0 @@
1
- import type { Command } from 'commander';
2
- import chalk from 'chalk';
3
- import ora from 'ora';
4
- import { getGlobalOptions } from './types.js';
5
- import { loadConfig } from '../../config/loader.js';
6
- import {
7
- loadOrCreateIdentity,
8
- BaseEscrowClient,
9
- identityToEvmWallet,
10
- identityToEvmAddress,
11
- } from '@antseed/node';
12
-
13
- export function registerDepositCommand(program: Command): void {
14
- program
15
- .command('deposit <amount>')
16
- .description('Deposit USDC into the escrow contract (amount in human-readable USDC, e.g. "5" = 5 USDC)')
17
- .action(async (amount: string) => {
18
- const globalOpts = getGlobalOptions(program);
19
- const config = await loadConfig(globalOpts.config);
20
-
21
- const payments = config.payments;
22
- if (!payments?.crypto) {
23
- console.error(chalk.red('Error: No crypto payment configuration found.'));
24
- console.error(chalk.dim('Configure payments.crypto in your config file or run: antseed init'));
25
- process.exit(1);
26
- }
27
-
28
- const amountFloat = parseFloat(amount);
29
- if (isNaN(amountFloat) || amountFloat <= 0) {
30
- console.error(chalk.red('Error: Amount must be a positive number.'));
31
- process.exit(1);
32
- }
33
-
34
- // Convert human-readable USDC to base units (6 decimals)
35
- const amountBaseUnits = BigInt(Math.round(amountFloat * 1_000_000));
36
-
37
- const identity = await loadOrCreateIdentity(globalOpts.dataDir);
38
- const wallet = identityToEvmWallet(identity);
39
- const address = identityToEvmAddress(identity);
40
-
41
- const escrowClient = new BaseEscrowClient({
42
- rpcUrl: payments.crypto.rpcUrl,
43
- contractAddress: payments.crypto.escrowContractAddress,
44
- usdcAddress: payments.crypto.usdcContractAddress,
45
- });
46
-
47
- console.log(chalk.dim(`Wallet: ${address}`));
48
- console.log(chalk.dim(`Amount: ${amountFloat} USDC (${amountBaseUnits} base units)`));
49
-
50
- const spinner = ora('Depositing USDC into escrow...').start();
51
-
52
- try {
53
- const txHash = await escrowClient.deposit(wallet, amountBaseUnits);
54
- spinner.succeed(chalk.green(`Deposited ${amountFloat} USDC into escrow`));
55
- console.log(chalk.dim(`Transaction: ${txHash}`));
56
- } catch (err) {
57
- spinner.fail(chalk.red(`Deposit failed: ${(err as Error).message}`));
58
- process.exit(1);
59
- }
60
- });
61
- }
@@ -1,107 +0,0 @@
1
- import type { Command } from 'commander';
2
- import chalk from 'chalk';
3
- import ora from 'ora';
4
- import crypto from 'node:crypto';
5
- import { getGlobalOptions } from './types.js';
6
- import { loadConfig } from '../../config/loader.js';
7
- import { AntseedNode } from '@antseed/node';
8
- import { DHTNode } from '@antseed/node/discovery';
9
- import { toPeerId } from '@antseed/node';
10
- import { parseBootstrapList, toBootstrapConfig } from '@antseed/node/discovery';
11
- import { setupShutdownHandler } from '../shutdown.js';
12
-
13
- export function registerDevCommand(program: Command): void {
14
- program
15
- .command('dev')
16
- .description('Run seller + buyer locally for development and testing')
17
- .option('-p, --port <number>', 'buyer proxy port', (v) => parseInt(v, 10))
18
- .action(async (options) => {
19
- const globalOpts = getGlobalOptions(program);
20
- const config = await loadConfig(globalOpts.config);
21
-
22
- if (options.port !== undefined) {
23
- config.buyer.proxyPort = options.port as number;
24
- }
25
-
26
- const spinner = ora('Starting local network...').start();
27
-
28
- // 1. Create and start local bootstrap DHT node
29
- const bootstrapPeerId = toPeerId(crypto.randomBytes(32).toString('hex'));
30
- const bootstrapDht = new DHTNode({
31
- peerId: bootstrapPeerId,
32
- port: 0,
33
- bootstrapNodes: [],
34
- reannounceIntervalMs: 60_000,
35
- operationTimeoutMs: 10_000,
36
- });
37
-
38
- try {
39
- await bootstrapDht.start();
40
- } catch (err) {
41
- spinner.fail(chalk.red(`Failed to start local DHT: ${(err as Error).message}`));
42
- process.exit(1);
43
- }
44
-
45
- const bootstrapPort = bootstrapDht.getPort();
46
- spinner.text = 'Starting seller node...';
47
-
48
- // 2. Bootstrap config pointing at local node
49
- const localBootstrap = toBootstrapConfig(parseBootstrapList([`127.0.0.1:${bootstrapPort}`]));
50
-
51
- // 3. Start seller node
52
- const seller = new AntseedNode({
53
- role: 'seller',
54
- bootstrapNodes: localBootstrap,
55
- });
56
-
57
- try {
58
- await seller.start();
59
- } catch (err) {
60
- await bootstrapDht.stop();
61
- spinner.fail(chalk.red(`Failed to start seller: ${(err as Error).message}`));
62
- process.exit(1);
63
- }
64
-
65
- spinner.text = 'Starting buyer node...';
66
-
67
- // 4. Start buyer node
68
- const buyer = new AntseedNode({
69
- role: 'buyer',
70
- bootstrapNodes: localBootstrap,
71
- });
72
-
73
- try {
74
- await buyer.start();
75
- } catch (err) {
76
- await seller.stop();
77
- await bootstrapDht.stop();
78
- spinner.fail(chalk.red(`Failed to start buyer proxy: ${(err as Error).message}`));
79
- process.exit(1);
80
- }
81
-
82
- spinner.succeed(chalk.green('Dev mode running (local network)'));
83
-
84
- const proxyUrl = `http://localhost:${config.buyer.proxyPort}`;
85
-
86
- console.log('');
87
- console.log(chalk.bold('Proxy running at: ') + chalk.cyan(proxyUrl));
88
- console.log(chalk.dim(`Local DHT bootstrap on port ${bootstrapPort}`));
89
- console.log('');
90
- console.log(chalk.bold('Configure your CLI tools:'));
91
- console.log(chalk.dim(' # Anthropic (Claude Code)'));
92
- console.log(` export ANTHROPIC_BASE_URL=${proxyUrl}`);
93
- console.log(chalk.dim(' # OpenAI (Codex)'));
94
- console.log(` export OPENAI_BASE_URL=${proxyUrl}`);
95
- console.log(chalk.dim(' # Google'));
96
- console.log(` export GOOGLE_API_BASE_URL=${proxyUrl}`);
97
- console.log('');
98
-
99
- setupShutdownHandler(async () => {
100
- spinner.start('Shutting down dev environment...');
101
- await buyer.stop();
102
- await seller.stop();
103
- await bootstrapDht.stop();
104
- spinner.succeed('Dev environment stopped.');
105
- });
106
- });
107
- }
@@ -1,99 +0,0 @@
1
- import type { Command } from 'commander'
2
- import chalk from 'chalk'
3
- import ora from 'ora'
4
- import { getGlobalOptions } from './types.js'
5
- import { loadConfig, saveConfig } from '../../config/loader.js'
6
- import { TRUSTED_PLUGINS } from '../../plugins/registry.js'
7
- import { installPlugin } from '../../plugins/manager.js'
8
- import type { CLIProviderConfig } from '../../config/types.js'
9
-
10
- export function registerInitCommand(program: Command): void {
11
- program
12
- .command('init')
13
- .description('Interactive setup — install trusted plugins and configure providers')
14
- .option('--auth-type <type>', 'auth type for providers: apikey, oauth, or claude-code', 'claude-code')
15
- .option('--api-key <key>', 'API key (required when auth-type is apikey)')
16
- .action(async (options) => {
17
- const globalOpts = getGlobalOptions(program)
18
- const config = await loadConfig(globalOpts.config)
19
-
20
- console.log(chalk.bold('\nAntseed — Setup\n'))
21
-
22
- // 1. Install plugins
23
- const providers = TRUSTED_PLUGINS.filter((p) => p.type === 'provider')
24
- const routers = TRUSTED_PLUGINS.filter((p) => p.type === 'router')
25
-
26
- console.log(chalk.bold('Installing plugins:\n'))
27
-
28
- if (providers.length > 0) {
29
- console.log(chalk.dim(' PROVIDERS (for: antseed seed)'))
30
- for (const p of providers) {
31
- console.log(` ${chalk.cyan(p.name.padEnd(16))} ${p.description}`)
32
- }
33
- console.log('')
34
- }
35
-
36
- if (routers.length > 0) {
37
- console.log(chalk.dim(' ROUTERS (for: antseed connect)'))
38
- for (const r of routers) {
39
- console.log(` ${chalk.cyan(r.name.padEnd(16))} ${r.description}`)
40
- }
41
- console.log('')
42
- }
43
-
44
- for (const plugin of TRUSTED_PLUGINS) {
45
- const spinner = ora(`Installing ${plugin.package}...`).start()
46
- try {
47
- await installPlugin(plugin.package)
48
- spinner.succeed(chalk.green(`Installed ${plugin.package}`))
49
- } catch (err) {
50
- spinner.fail(chalk.red(`Failed to install ${plugin.package}: ${(err as Error).message}`))
51
- }
52
- }
53
-
54
- // 2. Configure providers in the config file
55
- const authType = options.authType as 'apikey' | 'oauth' | 'claude-code'
56
- const apiKey = options.apiKey as string | undefined
57
-
58
- if (authType === 'apikey' && !apiKey) {
59
- console.log('')
60
- console.log(chalk.yellow('Skipping provider config — no --api-key provided.'))
61
- console.log(chalk.dim(' Add later: antseed config add-provider -t anthropic -k <key>'))
62
- } else {
63
- for (const plugin of providers) {
64
- const existing = config.providers.find(p => p.type === plugin.name)
65
- if (!existing) {
66
- const provider: CLIProviderConfig = {
67
- type: plugin.name,
68
- endpoint: 'https://api.anthropic.com',
69
- authHeaderName: authType === 'claude-code' ? 'authorization' : 'x-api-key',
70
- authValue: apiKey ?? '',
71
- authType,
72
- }
73
- config.providers.push(provider)
74
- config.seller.enabledProviders.push(plugin.name)
75
- } else {
76
- existing.authType = authType
77
- if (apiKey) existing.authValue = apiKey
78
- }
79
- }
80
-
81
- await saveConfig(globalOpts.config, config)
82
- console.log('')
83
- console.log(chalk.green(`Config saved with auth type: ${authType}`))
84
- }
85
-
86
- console.log('')
87
- console.log(chalk.bold('Next steps:'))
88
- console.log(` ${chalk.cyan('antseed config seller set pricing.defaults.inputUsdPerMillion 12')}`)
89
- console.log(` ${chalk.cyan('antseed config seller set pricing.defaults.outputUsdPerMillion 36')}`)
90
- console.log(` ${chalk.cyan('antseed config buyer set preferredProviders \'["anthropic","openai"]\'')}`)
91
- console.log(` ${chalk.cyan('antseed config buyer set maxPricing.defaults.inputUsdPerMillion 25')}`)
92
- console.log(` ${chalk.cyan('antseed config buyer set maxPricing.defaults.outputUsdPerMillion 75')}`)
93
- console.log(` ${chalk.cyan('antseed seed --provider anthropic')}`)
94
- console.log(` ${chalk.cyan('antseed connect --router claude-code')}`)
95
- console.log('')
96
- console.log(chalk.dim('Seller settings live under seller.* and buyer preferences live under buyer.* in your config file.'))
97
- console.log('')
98
- })
99
- }
@@ -1,60 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import test from 'node:test'
3
- import { mkdtemp, readFile, rm } from 'node:fs/promises'
4
- import { join } from 'node:path'
5
- import { tmpdir } from 'node:os'
6
- import { scaffoldPlugin } from './plugin-create.js'
7
-
8
- test('scaffoldPlugin creates provider project structure', async () => {
9
- const dir = await mkdtemp(join(tmpdir(), 'antseed-test-'))
10
- try {
11
- await scaffoldPlugin(dir, {
12
- name: 'test-provider',
13
- type: 'provider',
14
- displayName: 'Test Provider',
15
- description: 'A test provider plugin',
16
- })
17
-
18
- const pkg = JSON.parse(await readFile(join(dir, 'package.json'), 'utf-8')) as { name: string }
19
- assert.equal(pkg.name, '@antseed/provider-test-provider')
20
-
21
- const tsconfig = await readFile(join(dir, 'tsconfig.json'), 'utf-8')
22
- assert.ok(tsconfig.includes('NodeNext'))
23
-
24
- const index = await readFile(join(dir, 'src', 'index.ts'), 'utf-8')
25
- assert.ok(index.includes("type: 'provider'"))
26
- assert.ok(index.includes('AntseedProviderPlugin'))
27
-
28
- const provider = await readFile(join(dir, 'src', 'provider.ts'), 'utf-8')
29
- assert.ok(provider.includes('createProvider'))
30
-
31
- const testFile = await readFile(join(dir, 'src', 'index.test.ts'), 'utf-8')
32
- assert.ok(testFile.includes("'provider'"))
33
-
34
- const readme = await readFile(join(dir, 'README.md'), 'utf-8')
35
- assert.ok(readme.includes('test-provider'))
36
- } finally {
37
- await rm(dir, { recursive: true, force: true })
38
- }
39
- })
40
-
41
- test('scaffoldPlugin creates router project structure', async () => {
42
- const dir = await mkdtemp(join(tmpdir(), 'antseed-test-'))
43
- try {
44
- await scaffoldPlugin(dir, {
45
- name: 'test-router',
46
- type: 'router',
47
- displayName: 'Test Router',
48
- description: 'A test router plugin',
49
- })
50
-
51
- const index = await readFile(join(dir, 'src', 'index.ts'), 'utf-8')
52
- assert.ok(index.includes("type: 'router'"))
53
- assert.ok(index.includes('AntseedRouterPlugin'))
54
-
55
- const router = await readFile(join(dir, 'src', 'router.ts'), 'utf-8')
56
- assert.ok(router.includes('createRouter'))
57
- } finally {
58
- await rm(dir, { recursive: true, force: true })
59
- }
60
- })
@@ -1,230 +0,0 @@
1
- import type { Command } from 'commander'
2
- import chalk from 'chalk'
3
- import { createInterface } from 'node:readline/promises'
4
- import { mkdir, writeFile } from 'node:fs/promises'
5
- import { join } from 'node:path'
6
-
7
- interface PluginScaffoldOptions {
8
- name: string
9
- type: 'provider' | 'router'
10
- displayName: string
11
- description: string
12
- }
13
-
14
- function generatePackageJson(opts: PluginScaffoldOptions): string {
15
- return JSON.stringify(
16
- {
17
- name: `@antseed/${opts.type}-${opts.name}`,
18
- version: '0.1.0',
19
- description: opts.description,
20
- type: 'module',
21
- main: 'dist/index.js',
22
- types: 'dist/index.d.ts',
23
- scripts: {
24
- build: 'tsc',
25
- test: 'node --test dist/**/*.test.js',
26
- prepublishOnly: 'npm run build',
27
- },
28
- keywords: ['antseed', 'plugin', opts.type],
29
- peerDependencies: {
30
- '@antseed/node': '>=0.1.0',
31
- },
32
- devDependencies: {
33
- '@antseed/node': 'workspace:*',
34
- '@types/node': '^20.11.0',
35
- typescript: '^5.3.0',
36
- },
37
- },
38
- null,
39
- 2,
40
- )
41
- }
42
-
43
- function generateTsconfig(): string {
44
- return JSON.stringify(
45
- {
46
- compilerOptions: {
47
- target: 'ES2022',
48
- module: 'NodeNext',
49
- moduleResolution: 'NodeNext',
50
- outDir: 'dist',
51
- rootDir: 'src',
52
- strict: true,
53
- esModuleInterop: true,
54
- skipLibCheck: true,
55
- declaration: true,
56
- sourceMap: true,
57
- },
58
- include: ['src/**/*.ts'],
59
- exclude: ['node_modules', 'dist'],
60
- },
61
- null,
62
- 2,
63
- )
64
- }
65
-
66
- function generateIndexTs(opts: PluginScaffoldOptions): string {
67
- const importType = opts.type === 'provider' ? 'AntseedProviderPlugin' : 'AntseedRouterPlugin'
68
- const methodImport = opts.type === 'provider'
69
- ? `import { createProvider } from './${opts.type}.js'`
70
- : `import { createRouter } from './${opts.type}.js'`
71
- const method = opts.type === 'provider' ? 'createProvider' : 'createRouter'
72
-
73
- return `import type { ${importType}, ConfigField } from '@antseed/node'
74
- ${methodImport}
75
-
76
- const configSchema: ConfigField[] = [
77
- // Define your plugin's configuration fields here
78
- // { key: 'API_KEY', label: 'API Key', type: 'secret', required: true, description: 'Your API key' },
79
- ]
80
-
81
- const plugin: ${importType} = {
82
- name: '${opts.name}',
83
- displayName: '${opts.displayName}',
84
- version: '0.1.0',
85
- description: '${opts.description}',
86
- type: '${opts.type}',
87
- configSchema,
88
- ${method},
89
- }
90
-
91
- export default plugin
92
- `
93
- }
94
-
95
- function generateProviderTs(opts: PluginScaffoldOptions): string {
96
- return `import type { Provider, SerializedHttpRequest, SerializedHttpResponse } from '@antseed/node'
97
-
98
- export function createProvider(config: Record<string, string>): Provider {
99
- return {
100
- name: '${opts.name}',
101
- models: [],
102
- pricing: {
103
- defaults: { inputUsdPerMillion: 0, outputUsdPerMillion: 0 },
104
- },
105
- maxConcurrency: 1,
106
- async handleRequest(req: SerializedHttpRequest): Promise<SerializedHttpResponse> {
107
- // Implement your provider logic here
108
- throw new Error('Not implemented')
109
- },
110
- getCapacity() {
111
- return { current: 0, max: 1 }
112
- },
113
- }
114
- }
115
- `
116
- }
117
-
118
- function generateRouterTs(_opts: PluginScaffoldOptions): string {
119
- return `import type { Router, PeerInfo, SerializedHttpRequest } from '@antseed/node'
120
-
121
- export function createRouter(config: Record<string, string>): Router {
122
- return {
123
- selectPeer(req: SerializedHttpRequest, peers: PeerInfo[]): PeerInfo | null {
124
- // Implement your router logic here
125
- return peers[0] ?? null
126
- },
127
- onResult(peer: PeerInfo, result: { success: boolean; latencyMs: number; tokens: number }): void {
128
- // Track peer performance here
129
- },
130
- }
131
- }
132
- `
133
- }
134
-
135
- function generateTestTs(opts: PluginScaffoldOptions): string {
136
- return `import assert from 'node:assert/strict'
137
- import test from 'node:test'
138
-
139
- test('${opts.name} plugin exports valid manifest', async () => {
140
- const { default: plugin } = await import('./index.js')
141
- assert.equal(plugin.type, '${opts.type}')
142
- assert.equal(plugin.name, '${opts.name}')
143
- assert.ok(plugin.displayName)
144
- assert.ok(plugin.version)
145
- assert.ok(Array.isArray(plugin.configSchema))
146
- })
147
- `
148
- }
149
-
150
- function generateReadme(opts: PluginScaffoldOptions): string {
151
- return `# antseed-${opts.type}-${opts.name}
152
-
153
- ${opts.description}
154
-
155
- ## Installation
156
-
157
- \`\`\`bash
158
- antseed plugin add antseed-${opts.type}-${opts.name}
159
- \`\`\`
160
-
161
- ## Configuration
162
-
163
- Configure via interactive prompt:
164
-
165
- \`\`\`bash
166
- antseed plugin config <instance-id>
167
- \`\`\`
168
-
169
- ## Development
170
-
171
- \`\`\`bash
172
- npm install
173
- npm run build
174
- npm test
175
- \`\`\`
176
- `
177
- }
178
-
179
- export async function scaffoldPlugin(dir: string, opts: PluginScaffoldOptions): Promise<void> {
180
- await mkdir(join(dir, 'src'), { recursive: true })
181
- await writeFile(join(dir, 'package.json'), generatePackageJson(opts), 'utf-8')
182
- await writeFile(join(dir, 'tsconfig.json'), generateTsconfig(), 'utf-8')
183
- await writeFile(join(dir, 'src', 'index.ts'), generateIndexTs(opts), 'utf-8')
184
-
185
- if (opts.type === 'provider') {
186
- await writeFile(join(dir, 'src', 'provider.ts'), generateProviderTs(opts), 'utf-8')
187
- } else {
188
- await writeFile(join(dir, 'src', 'router.ts'), generateRouterTs(opts), 'utf-8')
189
- }
190
-
191
- await writeFile(join(dir, 'src', 'index.test.ts'), generateTestTs(opts), 'utf-8')
192
- await writeFile(join(dir, 'README.md'), generateReadme(opts), 'utf-8')
193
- }
194
-
195
- export function registerPluginCreateCommand(pluginCmd: Command): void {
196
- pluginCmd
197
- .command('create')
198
- .description('Scaffold a new plugin project')
199
- .action(async () => {
200
- const rl = createInterface({ input: process.stdin, output: process.stdout })
201
- try {
202
- const name = (await rl.question('Plugin name (lowercase, no spaces): ')).trim()
203
- if (!name || !/^[a-z][a-z0-9-]*$/.test(name)) {
204
- console.log(chalk.red('Invalid plugin name. Use lowercase letters, numbers, and hyphens.'))
205
- process.exit(1)
206
- }
207
-
208
- const typeAnswer = (await rl.question('Plugin type (provider/router): ')).trim()
209
- if (typeAnswer !== 'provider' && typeAnswer !== 'router') {
210
- console.log(chalk.red('Plugin type must be "provider" or "router".'))
211
- process.exit(1)
212
- }
213
- const type = typeAnswer as 'provider' | 'router'
214
-
215
- const displayName = (await rl.question('Display name: ')).trim() || name
216
- const description = (await rl.question('Description: ')).trim() || `Antseed ${type} plugin`
217
-
218
- const dir = join(process.cwd(), `antseed-${type}-${name}`)
219
- await scaffoldPlugin(dir, { name, type, displayName, description })
220
-
221
- console.log(chalk.green(`\nScaffolded plugin at: ${dir}`))
222
- console.log(chalk.dim('\nNext steps:'))
223
- console.log(chalk.dim(` cd antseed-${type}-${name}`))
224
- console.log(chalk.dim(' npm install'))
225
- console.log(chalk.dim(' npm run build'))
226
- } finally {
227
- rl.close()
228
- }
229
- })
230
- }
@@ -1,55 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import test from 'node:test'
3
- import { TRUSTED_PLUGINS } from '../../plugins/registry.js'
4
- import { buildPluginConfig, LEGACY_PACKAGE_MAP } from '../../plugins/loader.js'
5
-
6
- test('TRUSTED_PLUGINS contains 6 official plugins', () => {
7
- assert.equal(TRUSTED_PLUGINS.length, 6)
8
- const names = TRUSTED_PLUGINS.map(p => p.name)
9
- assert.ok(names.includes('anthropic'))
10
- assert.ok(names.includes('claude-code'))
11
- assert.ok(names.includes('openrouter'))
12
- assert.ok(names.includes('local-llm'))
13
- assert.ok(names.includes('local-proxy'))
14
- assert.ok(names.includes('local-chat'))
15
- })
16
-
17
- test('TRUSTED_PLUGINS all have scoped package names', () => {
18
- for (const plugin of TRUSTED_PLUGINS) {
19
- assert.ok(plugin.package.startsWith('@antseed/'), `${plugin.name} package should be scoped`)
20
- }
21
- })
22
-
23
- test('buildPluginConfig uses priority: instanceConfig < env < overrides', () => {
24
- const keys = [
25
- { key: 'API_KEY', label: 'API Key', type: 'string' as const },
26
- { key: 'MODEL', label: 'Model', type: 'string' as const },
27
- ]
28
- const instanceConfig = { API_KEY: 'from-instance', MODEL: 'from-instance' }
29
- const original = process.env['API_KEY']
30
- process.env['API_KEY'] = 'from-env'
31
- try {
32
- const result = buildPluginConfig(keys, { API_KEY: 'from-override' }, instanceConfig)
33
- assert.equal(result['API_KEY'], 'from-override')
34
- assert.equal(result['MODEL'], 'from-instance')
35
- } finally {
36
- if (original === undefined) {
37
- delete process.env['API_KEY']
38
- } else {
39
- process.env['API_KEY'] = original
40
- }
41
- }
42
- })
43
-
44
- test('buildPluginConfig works without instanceConfig (backwards compatible)', () => {
45
- const keys = [
46
- { key: 'TEST_KEY_COMPAT', label: 'Test', type: 'string' as const },
47
- ]
48
- const result = buildPluginConfig(keys, { TEST_KEY_COMPAT: 'override' })
49
- assert.equal(result['TEST_KEY_COMPAT'], 'override')
50
- })
51
-
52
- test('LEGACY_PACKAGE_MAP maps old names to scoped names', () => {
53
- assert.equal(LEGACY_PACKAGE_MAP['antseed-provider-anthropic'], '@antseed/provider-claude-code')
54
- assert.equal(LEGACY_PACKAGE_MAP['antseed-router-claude-code'], '@antseed/router-local-proxy')
55
- })