traw 0.2.2
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/.github/workflows/release.yml +89 -0
- package/.gitmodules +6 -0
- package/LICENSE +28 -0
- package/assets/auth.gif +0 -0
- package/assets/expirience.png +0 -0
- package/assets/logo.png +0 -0
- package/bun.lock +41 -0
- package/package.json +24 -0
- package/readme.md +63 -0
- package/src/agent/agent.ts +207 -0
- package/src/agent/prompts.ts +42 -0
- package/src/api/mo-client.ts +62 -0
- package/src/browser/controller.ts +188 -0
- package/src/cli/help.ts +37 -0
- package/src/cli/index.ts +281 -0
- package/src/index.ts +3 -0
- package/src/markdownly.d.ts +3 -0
- package/src/types.ts +47 -0
- package/src/utils/first-run.ts +27 -0
- package/src/utils/log.ts +222 -0
- package/src/utils/mo-manager.ts +153 -0
- package/src/utils/notify.ts +31 -0
- package/src/utils/version.ts +39 -0
- package/tsconfig.json +29 -0
package/src/utils/log.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { markdown } from "markdownly.js"
|
|
2
|
+
|
|
3
|
+
let silent = false
|
|
4
|
+
|
|
5
|
+
export function setSilent(value: boolean) {
|
|
6
|
+
silent = value
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function renderMd(text: string): string {
|
|
10
|
+
try {
|
|
11
|
+
return markdown(text).trim()
|
|
12
|
+
} catch {
|
|
13
|
+
return text
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const c = {
|
|
18
|
+
reset: "\x1b[0m",
|
|
19
|
+
dim: "\x1b[2m",
|
|
20
|
+
bold: "\x1b[1m",
|
|
21
|
+
cyan: "\x1b[36m",
|
|
22
|
+
green: "\x1b[32m",
|
|
23
|
+
yellow: "\x1b[33m",
|
|
24
|
+
red: "\x1b[31m",
|
|
25
|
+
magenta: "\x1b[35m",
|
|
26
|
+
blue: "\x1b[34m",
|
|
27
|
+
gray: "\x1b[90m",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const icons = {
|
|
31
|
+
arrow: "→",
|
|
32
|
+
check: "✓",
|
|
33
|
+
cross: "✗",
|
|
34
|
+
dot: "(0)",
|
|
35
|
+
circle: "○",
|
|
36
|
+
brain: "!",
|
|
37
|
+
play: ">",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class Spinner {
|
|
41
|
+
private frames = [".", "..", "..."]
|
|
42
|
+
private idx = 0
|
|
43
|
+
private interval: ReturnType<typeof setInterval> | null = null
|
|
44
|
+
private label: string
|
|
45
|
+
|
|
46
|
+
constructor(label: string) {
|
|
47
|
+
this.label = label
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
start() {
|
|
51
|
+
process.stdout.write("\x1b[?25l")
|
|
52
|
+
process.stdout.write(` ${c.dim}${this.label}...${c.reset}`)
|
|
53
|
+
this.interval = setInterval(() => {
|
|
54
|
+
this.idx = (this.idx + 1) % this.frames.length
|
|
55
|
+
process.stdout.write(`\r\x1b[K ${c.dim}${this.label}${this.frames[this.idx]}${c.reset}`)
|
|
56
|
+
}, 250)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
stop() {
|
|
60
|
+
if (this.interval) {
|
|
61
|
+
clearInterval(this.interval)
|
|
62
|
+
this.interval = null
|
|
63
|
+
}
|
|
64
|
+
process.stdout.write(`\r\x1b[K`)
|
|
65
|
+
process.stdout.write("\x1b[?25h")
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let loadSpinner: Spinner | null = null
|
|
70
|
+
let receiveSpinner: Spinner | null = null
|
|
71
|
+
let openSpinner: Spinner | null = null
|
|
72
|
+
|
|
73
|
+
export const log = {
|
|
74
|
+
header: (goal: string) => {
|
|
75
|
+
if (silent) return
|
|
76
|
+
console.log()
|
|
77
|
+
console.log(`${c.yellow}${icons.play}${c.reset} ${goal}`)
|
|
78
|
+
console.log()
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
config: (opts: { mo: string; model: string; headless: boolean; video: boolean; vision: boolean; steps: number }) => {
|
|
82
|
+
if (silent) return
|
|
83
|
+
const parts = [
|
|
84
|
+
`${c.dim}${opts.model}${c.reset}`,
|
|
85
|
+
opts.headless ? `${c.dim}headless${c.reset}` : null,
|
|
86
|
+
opts.video ? `${c.dim}video${c.reset}` : null,
|
|
87
|
+
opts.vision ? `${c.dim}vision${c.reset}` : null,
|
|
88
|
+
`${c.dim}steps:${c.reset} ${opts.steps}`,
|
|
89
|
+
].filter(Boolean)
|
|
90
|
+
console.log(` ${parts.join(" ")}`)
|
|
91
|
+
console.log()
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
plan: (text: string) => {
|
|
95
|
+
if (silent) return
|
|
96
|
+
console.log(renderMd(text))
|
|
97
|
+
console.log()
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
step: (n: number, total: number, url: string) => {
|
|
101
|
+
if (silent) return
|
|
102
|
+
const shortUrl = url.length > 50 ? url.slice(0, 47) + "..." : url
|
|
103
|
+
console.log(`${c.magenta}${icons.dot}${c.reset} ${c.bold}${n}/${total}${c.reset} ${c.dim}${shortUrl}${c.reset}`)
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
thought: (msg: string) => {
|
|
107
|
+
if (silent) return
|
|
108
|
+
const short = msg.length > 80 ? msg.slice(0, 77) + "..." : msg
|
|
109
|
+
console.log(` ${c.bold}${c.yellow}${icons.brain}${c.reset} ${c.gray}${short}${c.reset}`)
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
action: (type: string, target?: string) => {
|
|
113
|
+
if (silent) return
|
|
114
|
+
const t = target ? ` ${c.dim}${target}${c.reset}` : ""
|
|
115
|
+
console.log(` ${c.blue}${icons.arrow}${c.reset} ${type}${t}`)
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
ok: (msg?: string) => {
|
|
119
|
+
if (silent) return
|
|
120
|
+
if (msg) {
|
|
121
|
+
const short = msg.length > 60 ? msg.slice(0, 57) + "..." : msg
|
|
122
|
+
console.log(` ${c.green}${icons.check}${c.reset} ${c.dim}${short}${c.reset}`)
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
fail: (msg: string) => {
|
|
127
|
+
if (silent) return
|
|
128
|
+
const short = msg.length > 60 ? msg.slice(0, 57) + "..." : msg
|
|
129
|
+
console.log(` ${c.red}${icons.cross}${c.reset} ${short}`)
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
done: (steps: number, reason?: string) => {
|
|
133
|
+
if (silent) return
|
|
134
|
+
console.log()
|
|
135
|
+
console.log(`${c.green}${icons.check} done${c.reset} ${c.dim}in ${steps} steps${c.reset}`)
|
|
136
|
+
if (reason) {
|
|
137
|
+
console.log()
|
|
138
|
+
console.log(renderMd(reason))
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
stats: (totalMs: number, aiMs: number, browserMs: number) => {
|
|
143
|
+
if (silent) return
|
|
144
|
+
const fmt = (ms: number) => (ms / 1000).toFixed(1) + "s"
|
|
145
|
+
console.log()
|
|
146
|
+
console.log(`${c.dim}total:${c.reset} ${fmt(totalMs)}`)
|
|
147
|
+
console.log(`${c.dim}neuro:${c.reset} ${fmt(aiMs)} ${c.dim}(${Math.round(aiMs / totalMs * 100)}%)${c.reset}`)
|
|
148
|
+
console.log(`${c.dim}browser:${c.reset} ${fmt(browserMs)} ${c.dim}(${Math.round(browserMs / totalMs * 100)}%)${c.reset}`)
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
video: (path: string) => {
|
|
152
|
+
if (silent) return
|
|
153
|
+
console.log(`${c.dim}${icons.circle} video: ${path}${c.reset}`)
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
error: (msg: string) => {
|
|
157
|
+
// errors always print, even in silent mode
|
|
158
|
+
console.error(`${c.red}${icons.cross} ${msg}${c.reset}`)
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
info: (msg: string) => {
|
|
162
|
+
if (silent) return
|
|
163
|
+
console.log(`${c.cyan}${icons.arrow}${c.reset} ${msg}`)
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
success: (msg: string) => {
|
|
167
|
+
if (silent) return
|
|
168
|
+
console.log(`${c.green}${icons.check}${c.reset} ${msg}`)
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
planning: () => {
|
|
172
|
+
if (silent) return
|
|
173
|
+
process.stdout.write(`${c.dim}planning...${c.reset}`)
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
planDone: () => {
|
|
177
|
+
if (silent) return
|
|
178
|
+
process.stdout.write(`\r${c.dim}planning... done${c.reset}\n\n`)
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
loadStart: () => {
|
|
182
|
+
if (silent) return
|
|
183
|
+
loadSpinner = new Spinner("load")
|
|
184
|
+
loadSpinner.start()
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
loadStop: () => {
|
|
188
|
+
if (silent) return
|
|
189
|
+
if (loadSpinner) {
|
|
190
|
+
loadSpinner.stop()
|
|
191
|
+
loadSpinner = null
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
receiveStart: () => {
|
|
196
|
+
if (silent) return
|
|
197
|
+
receiveSpinner = new Spinner("receive")
|
|
198
|
+
receiveSpinner.start()
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
receiveStop: () => {
|
|
202
|
+
if (silent) return
|
|
203
|
+
if (receiveSpinner) {
|
|
204
|
+
receiveSpinner.stop()
|
|
205
|
+
receiveSpinner = null
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
openStart: () => {
|
|
210
|
+
if (silent) return
|
|
211
|
+
openSpinner = new Spinner("opening")
|
|
212
|
+
openSpinner.start()
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
openStop: () => {
|
|
216
|
+
if (silent) return
|
|
217
|
+
if (openSpinner) {
|
|
218
|
+
openSpinner.stop()
|
|
219
|
+
openSpinner = null
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { spawn, type Subprocess } from "bun"
|
|
2
|
+
import { homedir } from "os"
|
|
3
|
+
import { join } from "path"
|
|
4
|
+
import { log } from "./log"
|
|
5
|
+
|
|
6
|
+
const MO_REPO = "zarazaex69/mo"
|
|
7
|
+
const CONFIG_DIR = join(homedir(), ".config", "traw")
|
|
8
|
+
const MO_BIN = join(CONFIG_DIR, "mo")
|
|
9
|
+
const MO_CONFIG_DIR = join(CONFIG_DIR, "configs")
|
|
10
|
+
const MO_CONFIG = join(MO_CONFIG_DIR, "config.yaml")
|
|
11
|
+
const CONFIG_URL = "https://raw.githubusercontent.com/zarazaex69/mo/main/configs/config.yaml"
|
|
12
|
+
|
|
13
|
+
let moProcess: Subprocess | null = null
|
|
14
|
+
|
|
15
|
+
function getPlatformAsset(): string {
|
|
16
|
+
const platform = process.platform
|
|
17
|
+
const arch = process.arch
|
|
18
|
+
|
|
19
|
+
if (platform === "linux" && arch === "x64") return "mo-linux-amd64"
|
|
20
|
+
if (platform === "linux" && arch === "arm64") return "mo-linux-arm64"
|
|
21
|
+
if (platform === "darwin" && arch === "x64") return "mo-darwin-amd64"
|
|
22
|
+
if (platform === "darwin" && arch === "arm64") return "mo-darwin-arm64"
|
|
23
|
+
|
|
24
|
+
throw new Error(`unsupported platform: ${platform}-${arch}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function getLatestRelease(): Promise<{ tag: string; url: string }> {
|
|
28
|
+
const resp = await fetch(`https://api.github.com/repos/${MO_REPO}/releases/latest`)
|
|
29
|
+
if (!resp.ok) throw new Error("failed to fetch latest release")
|
|
30
|
+
|
|
31
|
+
const data = await resp.json() as { tag_name: string; assets: { name: string; browser_download_url: string }[] }
|
|
32
|
+
const asset = getPlatformAsset()
|
|
33
|
+
const tarball = data.assets.find(a => a.name === `${asset}.tar.gz`)
|
|
34
|
+
|
|
35
|
+
if (!tarball) throw new Error(`no binary for ${asset}`)
|
|
36
|
+
|
|
37
|
+
return { tag: data.tag_name, url: tarball.browser_download_url }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function pingMo(url: string): Promise<boolean> {
|
|
41
|
+
try {
|
|
42
|
+
const resp = await fetch(`${url}/health`, { signal: AbortSignal.timeout(2000) })
|
|
43
|
+
return resp.ok
|
|
44
|
+
} catch {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function isMoInstalled(): Promise<boolean> {
|
|
50
|
+
return await Bun.file(MO_BIN).exists()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function downloadMo(): Promise<void> {
|
|
54
|
+
log.info("fetching latest mo release...")
|
|
55
|
+
|
|
56
|
+
const { tag, url } = await getLatestRelease()
|
|
57
|
+
log.info(`downloading mo ${tag}...`)
|
|
58
|
+
|
|
59
|
+
// ensure config dir exists
|
|
60
|
+
await Bun.write(join(CONFIG_DIR, ".keep"), "")
|
|
61
|
+
await Bun.write(join(MO_CONFIG_DIR, ".keep"), "")
|
|
62
|
+
|
|
63
|
+
const resp = await fetch(url)
|
|
64
|
+
if (!resp.ok) throw new Error("download failed")
|
|
65
|
+
|
|
66
|
+
const tarPath = join(CONFIG_DIR, "mo.tar.gz")
|
|
67
|
+
await Bun.write(tarPath, resp)
|
|
68
|
+
|
|
69
|
+
// extract tarball
|
|
70
|
+
const proc = spawn(["tar", "-xzf", tarPath, "-C", CONFIG_DIR], { stdout: "ignore", stderr: "pipe" })
|
|
71
|
+
await proc.exited
|
|
72
|
+
|
|
73
|
+
if (proc.exitCode !== 0) {
|
|
74
|
+
throw new Error("failed to extract mo")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// rename extracted binary to just "mo"
|
|
78
|
+
const asset = getPlatformAsset()
|
|
79
|
+
const extractedPath = join(CONFIG_DIR, asset)
|
|
80
|
+
|
|
81
|
+
if (await Bun.file(extractedPath).exists()) {
|
|
82
|
+
const content = await Bun.file(extractedPath).arrayBuffer()
|
|
83
|
+
await Bun.write(MO_BIN, content)
|
|
84
|
+
await Bun.spawn(["rm", extractedPath]).exited
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// make executable
|
|
88
|
+
await Bun.spawn(["chmod", "+x", MO_BIN]).exited
|
|
89
|
+
|
|
90
|
+
// cleanup
|
|
91
|
+
await Bun.spawn(["rm", tarPath]).exited
|
|
92
|
+
|
|
93
|
+
// download config if not exists
|
|
94
|
+
if (!await Bun.file(MO_CONFIG).exists()) {
|
|
95
|
+
log.info("downloading config...")
|
|
96
|
+
const cfgResp = await fetch(CONFIG_URL)
|
|
97
|
+
if (cfgResp.ok) {
|
|
98
|
+
await Bun.write(MO_CONFIG, cfgResp)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
log.success(`mo ${tag} installed`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function startMo(port: number): Promise<void> {
|
|
106
|
+
if (!await isMoInstalled()) {
|
|
107
|
+
throw new Error("mo not installed")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
log.info("starting mo server...")
|
|
111
|
+
|
|
112
|
+
const configExists = await Bun.file(MO_CONFIG).exists()
|
|
113
|
+
if (!configExists) {
|
|
114
|
+
throw new Error(`config not found: ${MO_CONFIG}`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
moProcess = spawn([MO_BIN, "--config", MO_CONFIG, "--port", String(port)], {
|
|
118
|
+
stdout: "ignore",
|
|
119
|
+
stderr: "ignore",
|
|
120
|
+
env: { ...process.env, MO_DATA_PATH: CONFIG_DIR },
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
// wait for server to be ready
|
|
124
|
+
const maxWait = 10000
|
|
125
|
+
const start = Date.now()
|
|
126
|
+
|
|
127
|
+
while (Date.now() - start < maxWait) {
|
|
128
|
+
// check if process died
|
|
129
|
+
if (moProcess.exitCode !== null) {
|
|
130
|
+
throw new Error(`mo exited with code ${moProcess.exitCode}`)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (await pingMo(`http://localhost:${port}`)) {
|
|
134
|
+
log.success("mo server ready")
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
await Bun.sleep(300)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
throw new Error("mo server failed to start (timeout)")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function stopMo(): void {
|
|
144
|
+
if (moProcess) {
|
|
145
|
+
moProcess.kill()
|
|
146
|
+
moProcess = null
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// cleanup on exit
|
|
151
|
+
process.on("exit", stopMo)
|
|
152
|
+
process.on("SIGINT", () => { stopMo(); process.exit(0) })
|
|
153
|
+
process.on("SIGTERM", () => { stopMo(); process.exit(0) })
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// desktop notifications via notify-send (linux)
|
|
2
|
+
import { $ } from "bun"
|
|
3
|
+
|
|
4
|
+
let hasNotifySend: boolean | null = null
|
|
5
|
+
|
|
6
|
+
export async function checkNotify(): Promise<boolean> {
|
|
7
|
+
if (hasNotifySend !== null) return hasNotifySend
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
await $`which notify-send`.quiet()
|
|
11
|
+
hasNotifySend = true
|
|
12
|
+
} catch {
|
|
13
|
+
hasNotifySend = false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return hasNotifySend
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function notify(title: string, body?: string): Promise<void> {
|
|
20
|
+
if (!hasNotifySend) return
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
if (body) {
|
|
24
|
+
await $`notify-send ${title} ${body}`.quiet()
|
|
25
|
+
} else {
|
|
26
|
+
await $`notify-send ${title}`.quiet()
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
// ignore notification errors
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import pkg from "../../package.json"
|
|
2
|
+
|
|
3
|
+
export const VERSION = pkg.version
|
|
4
|
+
|
|
5
|
+
const GITHUB_REPO = "zarazaex69/traw"
|
|
6
|
+
const RELEASES_API = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`
|
|
7
|
+
|
|
8
|
+
interface GithubRelease {
|
|
9
|
+
tag_name: string
|
|
10
|
+
html_url: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function checkForUpdates(): Promise<{
|
|
14
|
+
hasUpdate: boolean
|
|
15
|
+
current: string
|
|
16
|
+
latest: string
|
|
17
|
+
url: string
|
|
18
|
+
} | null> {
|
|
19
|
+
try {
|
|
20
|
+
const resp = await fetch(RELEASES_API, {
|
|
21
|
+
headers: { "User-Agent": "traw-cli" },
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
if (!resp.ok) return null
|
|
25
|
+
|
|
26
|
+
const release = await resp.json() as GithubRelease
|
|
27
|
+
const latest = release.tag_name.replace(/^v/, "")
|
|
28
|
+
const current = VERSION.replace(/^v/, "")
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
hasUpdate: latest !== current,
|
|
32
|
+
current: VERSION,
|
|
33
|
+
latest: release.tag_name,
|
|
34
|
+
url: release.html_url,
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext", "DOM"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": false,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
|
|
24
|
+
// Some stricter flags (disabled by default)
|
|
25
|
+
"noUnusedLocals": false,
|
|
26
|
+
"noUnusedParameters": false,
|
|
27
|
+
"noPropertyAccessFromIndexSignature": false
|
|
28
|
+
}
|
|
29
|
+
}
|