novacode 0.5.5 → 0.7.0

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.
@@ -1,86 +0,0 @@
1
- import chalk from "chalk"
2
- import type { Agent } from "../agent/agent.ts"
3
- import type { SessionStore } from "../session/store.ts"
4
- import type { Cmd, Prompts } from "../types.ts"
5
- import { checkForUpdate, runUpdate } from "../update.ts"
6
- import { handleCompact } from "./compact.ts"
7
- import { handleModels } from "./models.ts"
8
- import { handleProviders } from "./providers.ts"
9
-
10
- export const COMMANDS: Cmd[] = [
11
- { name: "models", desc: "Switch model", aliases: ["model"] },
12
- { name: "providers", desc: "Manage providers", aliases: ["prov", "config", "cfg"] },
13
- { name: "compact", desc: "Compact context" },
14
- { name: "update", desc: "Update novacode" },
15
- { name: "help", desc: "Show help" },
16
- { name: "clear", desc: "Clear screen" },
17
- { name: "quit", desc: "Exit (Ctrl+D)", aliases: ["exit"] },
18
- ]
19
-
20
- const HELP = `
21
- ${chalk.bold("Commands:")}
22
- ${COMMANDS.map((c) => ` /${c.name.padEnd(12)} ${c.desc}`).join("\n")}
23
-
24
- ${chalk.bold("CLI:")}
25
- nova update Update to latest version
26
- nova session ls List sessions
27
-
28
- ${chalk.dim("Keys:")}
29
- Esc Abort
30
- ↑ / ↓ History
31
- `
32
-
33
- export async function dispatch(
34
- input: string,
35
- agent: Agent,
36
- store?: SessionStore,
37
- sessionId?: string,
38
- prompts?: Prompts,
39
- ): Promise<string | null> {
40
- const [cmd, ...rest] = input.slice(1).split(" ")
41
- const args = rest.join(" ")
42
-
43
- switch (cmd) {
44
- case "models":
45
- case "model":
46
- return handleModels(args, agent, prompts)
47
- case "providers":
48
- case "prov":
49
- case "config":
50
- case "cfg":
51
- return handleProviders(agent, prompts)
52
- case "compact":
53
- if (!store || !sessionId) return chalk.red("Session store not available")
54
- return handleCompact(agent, store, sessionId)
55
- case "update":
56
- return handleUpdate()
57
- case "help":
58
- return HELP
59
- case "clear":
60
- console.clear()
61
- return ""
62
- case "quit":
63
- process.exit(0)
64
- return null
65
- case "exit":
66
- process.exit(0)
67
- return null
68
- default:
69
- return chalk.yellow(`Unknown: /${cmd}. Type /help`)
70
- }
71
- }
72
-
73
- async function handleUpdate(): Promise<string> {
74
- const info = await checkForUpdate()
75
- if (!info) return chalk.yellow("Could not check for updates.")
76
- if (!info.hasUpdate) return chalk.green(`✓ Already up to date (v${info.current})`)
77
-
78
- console.log(chalk.yellow(`\n⚡ Updating novacode to v${info.latest}...`))
79
- const success = await runUpdate(true)
80
- if (success) {
81
- return chalk.green(
82
- `✓ Successfully updated to v${info.latest}! Please restart nova to apply changes.`,
83
- )
84
- }
85
- return chalk.red("✗ Update failed. Please try running 'nova update' manually in your terminal.")
86
- }
@@ -1,85 +0,0 @@
1
- import chalk from "chalk"
2
- import type { Agent } from "../agent/agent.ts"
3
- import { getProvider, MODELS } from "../config/providers.ts"
4
- import { loadAuth, loadConfig, saveConfig } from "../config/store.ts"
5
- import type { Prompts } from "../types.ts"
6
-
7
- export async function handleModels(args: string, agent: Agent, prompts?: Prompts): Promise<string> {
8
- const config = await loadConfig()
9
- const auth = await loadAuth()
10
-
11
- if (args) return await switchDirect(args.trim(), agent)
12
-
13
- if (!prompts) return chalk.red("Prompts not available in this context")
14
-
15
- const options: Array<{ value: string; label: string; hint?: string }> = []
16
- for (const m of MODELS) {
17
- const cur = m.id === config.model && m.provider === config.provider
18
- const pDef = getProvider(m.provider)
19
- if (!pDef) continue
20
-
21
- const hasKey = !!auth.apiKeys[m.provider]
22
- if (!hasKey) continue
23
-
24
- options.push({
25
- value: `${m.provider}:${m.id}`,
26
- label: `${cur ? chalk.green("●") : "○"} ${m.id.padEnd(20)} ${fmt(m.contextWindow).padEnd(8)}`,
27
- hint: pDef.name,
28
- })
29
- }
30
-
31
- if (!options.length)
32
- return chalk.yellow("No models available. Use /providers to add a provider API key.")
33
-
34
- const pick = await prompts.select({ message: "Model", options })
35
- if (!pick) return ""
36
-
37
- const [pk, mid] = pick.split(":")
38
- const selectedModel = MODELS.find((m) => m.provider === pk && m.id === mid)
39
- const selectedProvider = getProvider(pk!)
40
-
41
- if (!selectedModel || !selectedProvider) return chalk.red("Error: Model or provider not found")
42
-
43
- config.provider = pk!
44
- config.model = mid!
45
- await saveConfig(config)
46
-
47
- agent.updateConfig({
48
- api: selectedProvider.api,
49
- model: selectedModel,
50
- apiKey: auth.apiKeys[pk!] ?? "",
51
- baseUrl: selectedProvider.baseUrl,
52
- })
53
- return chalk.green(`✓ Switched to ${mid}`)
54
- }
55
-
56
- async function switchDirect(id: string, agent: Agent): Promise<string> {
57
- const config = await loadConfig()
58
- const auth = await loadAuth()
59
-
60
- const m = MODELS.find((m) => m.id === id)
61
- if (!m) return chalk.yellow(`"${id}" not found. Use /models`)
62
-
63
- const pk = m.provider
64
- if (!auth.apiKeys[pk]) {
65
- return chalk.yellow(`No API key configured for ${pk}. Use /providers`)
66
- }
67
-
68
- const selectedProvider = getProvider(pk)
69
- if (!selectedProvider) return chalk.red("Error: Provider not found")
70
-
71
- config.provider = pk
72
- config.model = id
73
- await saveConfig(config)
74
-
75
- agent.updateConfig({
76
- api: selectedProvider.api,
77
- model: m,
78
- apiKey: auth.apiKeys[pk],
79
- baseUrl: selectedProvider.baseUrl,
80
- })
81
-
82
- return chalk.green(`✓ Switched to ${id}`)
83
- }
84
-
85
- const fmt = (n: number) => (n >= 1_000_000 ? `${n / 1_000_000}M` : `${n / 1000}K`)
@@ -1,213 +0,0 @@
1
- import chalk from "chalk"
2
- import type { Agent } from "../agent/agent.ts"
3
- import { getProvider, MODELS, PROVIDERS } from "../config/providers.ts"
4
- import { loadAuth, loadConfig, saveAuth, saveConfig } from "../config/store.ts"
5
- import type { Prompts } from "../types.ts"
6
-
7
- export async function handleProviders(agent: Agent, prompts?: Prompts): Promise<string> {
8
- if (!prompts) return chalk.red("Prompts not available in this context")
9
-
10
- const config = await loadConfig()
11
- const auth = await loadAuth()
12
- const configured = PROVIDERS.filter((p) => !!auth.apiKeys[p.id])
13
-
14
- const headerLines =
15
- configured.length === 0
16
- ? chalk.dim("No providers configured. Use 'Add Provider' below.")
17
- : configured
18
- .map((p) => {
19
- const isDefault = p.id === config.provider
20
- const active = isDefault ? chalk.green(" ●") : ""
21
- const currentModel = isDefault
22
- ? config.model
23
- : (MODELS.find((m) => m.provider === p.id)?.id ?? "")
24
- return ` ✅ ${p.name.padEnd(24)} ${currentModel}${active}`
25
- })
26
- .join("\n")
27
-
28
- const act = await prompts.select({
29
- message: "Action",
30
- header: headerLines,
31
- options: [
32
- { value: "add", label: "Add Provider" },
33
- { value: "update", label: "Update API Key" },
34
- { value: "remove", label: "Remove API Key" },
35
- { value: "default", label: "Set Default Provider" },
36
- { value: "back", label: "Back" },
37
- ],
38
- })
39
- if (!act || act === "back") return ""
40
-
41
- if (act === "add") return addProvider(agent, prompts)
42
- if (act === "update") return updateKey(agent, prompts)
43
- if (act === "remove") return removeKey(agent, prompts)
44
- if (act === "default") return setDefault(agent, prompts)
45
- return ""
46
- }
47
-
48
- async function addProvider(agent: Agent, prompts: Prompts): Promise<string> {
49
- const auth = await loadAuth()
50
- const config = await loadConfig()
51
-
52
- const available = PROVIDERS.filter((p) => !auth.apiKeys[p.id])
53
- if (available.length === 0) {
54
- return chalk.yellow("All providers already have API keys configured.")
55
- }
56
-
57
- const pick = await prompts.select({
58
- message: "Add Provider",
59
- options: available.map((p) => ({ value: p.id, label: p.name })),
60
- })
61
- if (!pick) return ""
62
-
63
- const pDef = getProvider(pick)
64
- if (!pDef) return chalk.red("Error: Provider not found")
65
-
66
- const key = await prompts.password({
67
- message: `${pDef.name} API Key`,
68
- validate: (v) => (!v || v.length < 8 ? "Enter a valid key" : undefined),
69
- })
70
- if (!key) return ""
71
-
72
- auth.apiKeys[pDef.id] = key
73
- await saveAuth(auth)
74
-
75
- if (!config.provider) {
76
- config.provider = pDef.id
77
- const mDef = MODELS.find((m) => m.provider === pDef.id)
78
- if (mDef) {
79
- config.model = mDef.id
80
- }
81
- await saveConfig(config)
82
- agent.updateConfig({
83
- api: pDef.api,
84
- model: MODELS.find((m) => m.id === config.model)!,
85
- apiKey: key,
86
- baseUrl: pDef.baseUrl,
87
- })
88
- }
89
-
90
- return chalk.green(`✓ ${pDef.name} configured`)
91
- }
92
-
93
- async function updateKey(agent: Agent, prompts: Prompts): Promise<string> {
94
- const auth = await loadAuth()
95
-
96
- const configured = PROVIDERS.filter((p) => !!auth.apiKeys[p.id])
97
- if (configured.length === 0) {
98
- return chalk.yellow("No providers configured. Use 'Add Provider' first.")
99
- }
100
-
101
- const pick = await prompts.select({
102
- message: "Update API Key",
103
- options: configured.map((p) => ({ value: p.id, label: p.name })),
104
- })
105
- if (!pick) return ""
106
-
107
- const pDef = getProvider(pick)
108
- if (!pDef) return chalk.red("Error: Provider not found")
109
-
110
- const key = await prompts.password({ message: `New key for ${pDef.name}` })
111
- if (!key) return ""
112
-
113
- auth.apiKeys[pDef.id] = key
114
- await saveAuth(auth)
115
-
116
- const config = await loadConfig()
117
- if (config.provider === pDef.id) {
118
- const currentModel = MODELS.find((m) => m.id === config.model && m.provider === config.provider)
119
- if (currentModel) {
120
- agent.updateConfig({
121
- api: pDef.api,
122
- model: currentModel,
123
- apiKey: key,
124
- baseUrl: pDef.baseUrl,
125
- })
126
- }
127
- }
128
-
129
- return chalk.green("✓ Key updated")
130
- }
131
-
132
- async function removeKey(agent: Agent, prompts: Prompts): Promise<string> {
133
- const auth = await loadAuth()
134
- const config = await loadConfig()
135
-
136
- const configured = PROVIDERS.filter((p) => !!auth.apiKeys[p.id])
137
- if (configured.length === 0) {
138
- return chalk.yellow("No configured providers to remove.")
139
- }
140
-
141
- const pick = await prompts.select({
142
- message: "Remove API Key",
143
- options: configured.map((p) => ({ value: p.id, label: p.name })),
144
- })
145
- if (!pick) return ""
146
-
147
- const confirm = await prompts.confirm({
148
- message: `Are you sure you want to remove the API key for ${pick}?`,
149
- })
150
- if (!confirm) return ""
151
-
152
- delete auth.apiKeys[pick]
153
- await saveAuth(auth)
154
-
155
- if (config.provider === pick) {
156
- config.provider = ""
157
- config.model = ""
158
- const next = Object.keys(auth.apiKeys)[0]
159
- if (next) {
160
- const pDef = getProvider(next)
161
- const mDef = MODELS.find((m) => m.provider === next)
162
- if (pDef && mDef) {
163
- config.provider = next
164
- config.model = mDef.id
165
- agent.updateConfig({
166
- api: pDef.api,
167
- model: mDef,
168
- apiKey: auth.apiKeys[next]!,
169
- baseUrl: pDef.baseUrl,
170
- })
171
- }
172
- }
173
- await saveConfig(config)
174
- }
175
-
176
- return chalk.green(`✓ Removed API key for ${pick}`)
177
- }
178
-
179
- async function setDefault(agent: Agent, prompts: Prompts): Promise<string> {
180
- const config = await loadConfig()
181
- const auth = await loadAuth()
182
-
183
- const pick = await prompts.select({
184
- message: "Default Provider",
185
- options: PROVIDERS.map((p) => ({
186
- value: p.id,
187
- label: `${auth.apiKeys[p.id] ? "✅" : "❌"} ${p.name}`,
188
- })),
189
- })
190
- if (!pick) return ""
191
-
192
- if (!auth.apiKeys[pick]) {
193
- return chalk.yellow(`No API key for ${pick}. Please set one first.`)
194
- }
195
-
196
- const pDef = getProvider(pick)
197
- const mDef = MODELS.find((m) => m.provider === pick)
198
-
199
- if (!pDef || !mDef) return chalk.red("Error: Provider or model not found")
200
-
201
- config.provider = pick
202
- config.model = mDef.id
203
- await saveConfig(config)
204
-
205
- agent.updateConfig({
206
- api: pDef.api,
207
- model: mDef,
208
- apiKey: auth.apiKeys[pick],
209
- baseUrl: pDef.baseUrl,
210
- })
211
-
212
- return chalk.green(`✓ Default set to ${pDef.name} (${mDef.id})`)
213
- }
@@ -1,40 +0,0 @@
1
- import { getSessionStore } from "../session/store.ts"
2
-
3
- export async function handleSessionCommand(args: string[]): Promise<void> {
4
- const store = getSessionStore()
5
- const [subcommand, id] = args
6
-
7
- if (subcommand === "list" || subcommand === "ls") {
8
- const sessions = store.list()
9
- if (sessions.length === 0) {
10
- console.log("No sessions found.")
11
- return
12
- }
13
-
14
- console.log("ID".padEnd(25), "MODEL".padEnd(20), "UPDATED")
15
- console.log("-".repeat(70))
16
- for (const s of sessions) {
17
- const date = new Date(s.updated).toLocaleString()
18
- console.log(s.id.padEnd(25), s.model.padEnd(20), date)
19
- }
20
- return
21
- }
22
-
23
- if (subcommand === "delete" || subcommand === "rm") {
24
- if (!id) {
25
- console.error("Usage: novacode session delete <id>")
26
- process.exit(1)
27
- }
28
- const success = store.delete(id)
29
- if (success) {
30
- console.log(`Deleted session: ${id}`)
31
- } else {
32
- console.error(`Session not found: ${id}`)
33
- process.exit(1)
34
- }
35
- return
36
- }
37
-
38
- console.error("Unknown session subcommand. Use 'list' or 'delete'.")
39
- process.exit(1)
40
- }
@@ -1,207 +0,0 @@
1
- import type { Model, ProviderDef } from "../types.ts"
2
-
3
- export const PROVIDERS: ProviderDef[] = [
4
- {
5
- id: "glm",
6
- name: "GLM (Z.AI)",
7
- api: "openai",
8
- baseUrl: "https://api.z.ai/api/coding/paas/v4",
9
- envKey: "GLM_API_KEY",
10
- },
11
- {
12
- id: "gemini",
13
- name: "Gemini (Google)",
14
- api: "gemini",
15
- baseUrl: "https://generativelanguage.googleapis.com",
16
- envKey: "GEMINI_API_KEY",
17
- },
18
- {
19
- id: "deepseek",
20
- name: "DeepSeek",
21
- api: "openai",
22
- baseUrl: "https://api.deepseek.com",
23
- envKey: "DEEPSEEK_API_KEY",
24
- },
25
- {
26
- id: "openai",
27
- name: "OpenAI",
28
- api: "openai",
29
- baseUrl: "https://api.openai.com/v1",
30
- envKey: "OPENAI_API_KEY",
31
- },
32
- ]
33
-
34
- export const MODELS: Model[] = [
35
- // GLM
36
- {
37
- id: "glm-5.1",
38
- name: "GLM-5.1",
39
- provider: "glm",
40
- contextWindow: 128_000,
41
- maxTokens: 4096,
42
- supportsThinking: false,
43
- },
44
- {
45
- id: "glm-5",
46
- name: "GLM-5",
47
- provider: "glm",
48
- contextWindow: 128_000,
49
- maxTokens: 4096,
50
- supportsThinking: false,
51
- },
52
- {
53
- id: "glm-5-turbo",
54
- name: "GLM-5 Turbo",
55
- provider: "glm",
56
- contextWindow: 128_000,
57
- maxTokens: 4096,
58
- supportsThinking: false,
59
- },
60
- {
61
- id: "glm-4.7",
62
- name: "GLM-4.7",
63
- provider: "glm",
64
- contextWindow: 128_000,
65
- maxTokens: 4096,
66
- supportsThinking: false,
67
- },
68
- {
69
- id: "glm-4.7-flash",
70
- name: "GLM-4.7 Flash (Free)",
71
- provider: "glm",
72
- contextWindow: 128_000,
73
- maxTokens: 4096,
74
- supportsThinking: false,
75
- },
76
- {
77
- id: "glm-4.5-flash",
78
- name: "GLM-4.5 Flash (Free)",
79
- provider: "glm",
80
- contextWindow: 128_000,
81
- maxTokens: 4096,
82
- supportsThinking: false,
83
- },
84
- // Gemini
85
- {
86
- id: "gemini-3.5-flash",
87
- name: "Gemini 3.5 Flash",
88
- provider: "gemini",
89
- contextWindow: 1_000_000,
90
- maxTokens: 65_536,
91
- supportsThinking: true,
92
- },
93
- {
94
- id: "gemini-3.1-pro-preview",
95
- name: "Gemini 3.1 Pro Preview",
96
- provider: "gemini",
97
- contextWindow: 2_000_000,
98
- maxTokens: 65_536,
99
- supportsThinking: true,
100
- },
101
- {
102
- id: "gemini-3.1-pro-preview-customtools",
103
- name: "Gemini 3.1 Pro (Custom Tools)",
104
- provider: "gemini",
105
- contextWindow: 2_000_000,
106
- maxTokens: 65_536,
107
- supportsThinking: true,
108
- },
109
- {
110
- id: "gemini-3.1-flash-lite",
111
- name: "Gemini 3.1 Flash-Lite",
112
- provider: "gemini",
113
- contextWindow: 1_000_000,
114
- maxTokens: 65_536,
115
- supportsThinking: true,
116
- },
117
- {
118
- id: "gemini-3.1-flash-lite-preview",
119
- name: "Gemini 3.1 Flash-Lite Preview",
120
- provider: "gemini",
121
- contextWindow: 1_000_000,
122
- maxTokens: 65_536,
123
- supportsThinking: true,
124
- },
125
- {
126
- id: "gemini-3-flash-preview",
127
- name: "Gemini 3 Flash Preview",
128
- provider: "gemini",
129
- contextWindow: 1_000_000,
130
- maxTokens: 65_536,
131
- supportsThinking: true,
132
- },
133
- {
134
- id: "gemini-2.5-pro",
135
- name: "Gemini 2.5 Pro",
136
- provider: "gemini",
137
- contextWindow: 2_000_000,
138
- maxTokens: 65_536,
139
- supportsThinking: false,
140
- },
141
- {
142
- id: "gemini-2.5-flash",
143
- name: "Gemini 2.5 Flash",
144
- provider: "gemini",
145
- contextWindow: 1_000_000,
146
- maxTokens: 65_536,
147
- supportsThinking: false,
148
- },
149
- {
150
- id: "gemini-2.5-flash-lite",
151
- name: "Gemini 2.5 Flash-Lite",
152
- provider: "gemini",
153
- contextWindow: 1_000_000,
154
- maxTokens: 65_536,
155
- supportsThinking: false,
156
- },
157
- {
158
- id: "gemini-2.5-computer-use-preview-10-2025",
159
- name: "Gemini 2.5 Computer Use",
160
- provider: "gemini",
161
- contextWindow: 1_000_000,
162
- maxTokens: 65_536,
163
- supportsThinking: false,
164
- },
165
- // DeepSeek
166
- {
167
- id: "deepseek-chat",
168
- name: "DeepSeek V3",
169
- provider: "deepseek",
170
- contextWindow: 64_000,
171
- maxTokens: 8_192,
172
- supportsThinking: false,
173
- },
174
- {
175
- id: "deepseek-reasoner",
176
- name: "DeepSeek R1",
177
- provider: "deepseek",
178
- contextWindow: 64_000,
179
- maxTokens: 8_192,
180
- supportsThinking: true,
181
- },
182
- // OpenAI
183
- {
184
- id: "gpt-4o",
185
- name: "GPT-4o",
186
- provider: "openai",
187
- contextWindow: 128_000,
188
- maxTokens: 16_384,
189
- supportsThinking: false,
190
- },
191
- {
192
- id: "o4-mini",
193
- name: "o4-mini",
194
- provider: "openai",
195
- contextWindow: 200_000,
196
- maxTokens: 100_000,
197
- supportsThinking: true,
198
- },
199
- ]
200
-
201
- export function getProvider(id: string): ProviderDef | undefined {
202
- return PROVIDERS.find((p) => p.id === id)
203
- }
204
-
205
- export function getModelsForProvider(providerId: string): Model[] {
206
- return MODELS.filter((m) => m.provider === providerId)
207
- }
@@ -1,66 +0,0 @@
1
- import { chmod, mkdir, readFile, stat, writeFile } from "node:fs/promises"
2
- import { join } from "node:path"
3
- import type { NovaAuth, NovaConfig } from "../types.ts"
4
-
5
- const NOVA_DIR = () => join(process.env.HOME ?? "~", ".novacode")
6
- const CONFIG_PATH = () => join(NOVA_DIR(), "config.json")
7
- const AUTH_PATH = () => join(NOVA_DIR(), "auth.json")
8
-
9
- const defaultConfig: NovaConfig = {
10
- provider: "",
11
- model: "",
12
- }
13
-
14
- const defaultAuth: NovaAuth = {
15
- apiKeys: {},
16
- }
17
-
18
- export async function configExists(): Promise<boolean> {
19
- try {
20
- await stat(CONFIG_PATH())
21
- return true
22
- } catch {
23
- return false
24
- }
25
- }
26
-
27
- export async function loadConfig(): Promise<NovaConfig> {
28
- try {
29
- const raw = JSON.parse(await readFile(CONFIG_PATH(), "utf-8"))
30
- return { ...defaultConfig, ...raw }
31
- } catch {
32
- return { ...defaultConfig }
33
- }
34
- }
35
-
36
- export async function loadAuth(): Promise<NovaAuth> {
37
- try {
38
- const raw = JSON.parse(await readFile(AUTH_PATH(), "utf-8"))
39
- return { ...defaultAuth, ...raw }
40
- } catch {
41
- return { ...defaultAuth }
42
- }
43
- }
44
-
45
- async function ensureDir(): Promise<void> {
46
- await mkdir(NOVA_DIR(), { recursive: true })
47
- }
48
-
49
- export async function saveConfig(config: NovaConfig): Promise<void> {
50
- await ensureDir()
51
- await writeFile(CONFIG_PATH(), JSON.stringify(config, null, 2))
52
- }
53
-
54
- export async function saveAuth(auth: NovaAuth): Promise<void> {
55
- await ensureDir()
56
- await writeFile(AUTH_PATH(), JSON.stringify(auth, null, 2))
57
- try {
58
- await chmod(AUTH_PATH(), 0o600)
59
- } catch {
60
- // chmod may fail on some platforms, non-fatal
61
- }
62
- }
63
-
64
- export function getNovaDir(): string {
65
- return NOVA_DIR()
66
- }