@trydecember/cli 0.2.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/dist/december.js +67965 -0
- package/package.json +31 -0
- package/src/index.ts +56 -0
- package/src/local-operations.ts +101 -0
- package/tsconfig.json +6 -0
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@trydecember/cli",
|
|
3
|
+
"module": "src/index.ts",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": false,
|
|
6
|
+
"version": "0.2.1",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18.0.0"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"december": "./dist/december.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "bun build ./src/index.ts --target=node --outfile=./dist/december.js"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@december/agent": "workspace:*",
|
|
18
|
+
"@december/tools": "workspace:*",
|
|
19
|
+
"@december/tui": "workspace:*",
|
|
20
|
+
"fast-glob": "3.3.3",
|
|
21
|
+
"ink": "7.1.0",
|
|
22
|
+
"react": "19.2.7"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/bun": "latest",
|
|
26
|
+
"react-devtools-core": "7.0.1"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"typescript": "^5"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { render } from 'ink'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { App } from '@december/tui'
|
|
5
|
+
import {
|
|
6
|
+
Agent,
|
|
7
|
+
createProvider,
|
|
8
|
+
ProviderConfig,
|
|
9
|
+
getProviderConfig,
|
|
10
|
+
OpenAIProvider,
|
|
11
|
+
} from '@december/agent'
|
|
12
|
+
import {
|
|
13
|
+
BashTool,
|
|
14
|
+
ReadFileTool,
|
|
15
|
+
WriteFileTool,
|
|
16
|
+
LsTool,
|
|
17
|
+
EditFileTool,
|
|
18
|
+
FindFilesTool,
|
|
19
|
+
GrepSearchTool,
|
|
20
|
+
SubagentTool,
|
|
21
|
+
} from '@december/tools'
|
|
22
|
+
import { localOperations } from './local-operations'
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
let providerConfig = await getProviderConfig()
|
|
26
|
+
|
|
27
|
+
// If not authenticated, we pass a dummy provider so the Agent can boot.
|
|
28
|
+
// The TUI will intercept prompts and force them to /login
|
|
29
|
+
const llm = providerConfig
|
|
30
|
+
? createProvider(providerConfig)
|
|
31
|
+
: new OpenAIProvider('dummy', undefined, 'gpt-4o')
|
|
32
|
+
const isAuthenticated = !!providerConfig
|
|
33
|
+
|
|
34
|
+
const agent = new Agent({
|
|
35
|
+
systemPrompt:
|
|
36
|
+
'You are December, an autonomous software engineer. You have access to tools. When executing code, please use JSON schemas for tool inputs.',
|
|
37
|
+
llm: llm,
|
|
38
|
+
tools: [
|
|
39
|
+
BashTool,
|
|
40
|
+
ReadFileTool,
|
|
41
|
+
WriteFileTool,
|
|
42
|
+
LsTool,
|
|
43
|
+
EditFileTool,
|
|
44
|
+
FindFilesTool,
|
|
45
|
+
GrepSearchTool,
|
|
46
|
+
SubagentTool,
|
|
47
|
+
],
|
|
48
|
+
operations: localOperations,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
await agent.loadContext()
|
|
52
|
+
|
|
53
|
+
render(React.createElement(App, { agent, isAuthenticated }), { exitOnCtrlC: false })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
main().catch(console.error)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { AgentOperations } from '@december/agent'
|
|
2
|
+
import { spawn, exec } from 'node:child_process'
|
|
3
|
+
import fs from 'node:fs/promises'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import fg from 'fast-glob'
|
|
6
|
+
import { promisify } from 'node:util'
|
|
7
|
+
|
|
8
|
+
const execAsync = promisify(exec)
|
|
9
|
+
|
|
10
|
+
export const localOperations: AgentOperations = {
|
|
11
|
+
bash: {
|
|
12
|
+
exec: async (command, onData) => {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
let stdout = ''
|
|
15
|
+
let stderr = ''
|
|
16
|
+
|
|
17
|
+
const child = spawn(command, {
|
|
18
|
+
shell: true,
|
|
19
|
+
detached: true,
|
|
20
|
+
stdio: 'pipe',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
child.stdout?.on('data', (data) => {
|
|
24
|
+
const chunk = data.toString()
|
|
25
|
+
stdout += chunk
|
|
26
|
+
onData(chunk)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
child.stderr?.on('data', (data) => {
|
|
30
|
+
const chunk = data.toString()
|
|
31
|
+
stderr += chunk
|
|
32
|
+
onData(chunk)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
let isFinished = false
|
|
36
|
+
|
|
37
|
+
child.on('close', (code) => {
|
|
38
|
+
if (isFinished) return
|
|
39
|
+
isFinished = true
|
|
40
|
+
resolve({ exitCode: code, output: stdout + '\n' + stderr })
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
child.on('error', (err) => {
|
|
44
|
+
if (isFinished) return
|
|
45
|
+
isFinished = true
|
|
46
|
+
resolve({ exitCode: 1, output: `Failed to start command: ${err.message}` })
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// Auto-detach logic
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
if (isFinished) return
|
|
52
|
+
isFinished = true
|
|
53
|
+
|
|
54
|
+
child.unref()
|
|
55
|
+
child.stdout?.unref()
|
|
56
|
+
child.stderr?.unref()
|
|
57
|
+
|
|
58
|
+
resolve({ exitCode: null, output: stdout + '\n' + stderr })
|
|
59
|
+
}, 3000)
|
|
60
|
+
})
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
fs: {
|
|
64
|
+
readFile: async (filepath) => {
|
|
65
|
+
return fs.readFile(path.resolve(process.cwd(), filepath), 'utf8')
|
|
66
|
+
},
|
|
67
|
+
writeFile: async (filepath, content) => {
|
|
68
|
+
const absolutePath = path.resolve(process.cwd(), filepath)
|
|
69
|
+
await fs.mkdir(path.dirname(absolutePath), { recursive: true })
|
|
70
|
+
await fs.writeFile(absolutePath, content, 'utf8')
|
|
71
|
+
},
|
|
72
|
+
readdir: async (dirPath) => {
|
|
73
|
+
const absolutePath = path.resolve(process.cwd(), dirPath)
|
|
74
|
+
const entries = await fs.readdir(absolutePath, { withFileTypes: true })
|
|
75
|
+
return entries.map((entry) => {
|
|
76
|
+
const type = entry.isDirectory() ? 'DIR ' : 'FILE'
|
|
77
|
+
return `[${type}] ${entry.name}`
|
|
78
|
+
})
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
search: {
|
|
82
|
+
find: async (dirPath, query) => {
|
|
83
|
+
const files = await fg([query], {
|
|
84
|
+
cwd: dirPath,
|
|
85
|
+
ignore: ['**/node_modules/**', '**/.git/**'],
|
|
86
|
+
dot: true,
|
|
87
|
+
})
|
|
88
|
+
return files.join('\n')
|
|
89
|
+
},
|
|
90
|
+
grep: async (dirPath, query) => {
|
|
91
|
+
try {
|
|
92
|
+
const cmd = `git grep -nI "${query}" ${dirPath} || grep -rnI --exclude-dir=node_modules --exclude-dir=.git "${query}" ${dirPath}`
|
|
93
|
+
const { stdout } = await execAsync(cmd)
|
|
94
|
+
return stdout
|
|
95
|
+
} catch (error: any) {
|
|
96
|
+
if (error.code === 1) return '' // grep returns 1 when no matches
|
|
97
|
+
throw error
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
}
|