ghost-dragon 4.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/.github/workflows/ci.yml +23 -0
- package/CHANGELOG.md +96 -0
- package/README.md +193 -0
- package/bootstrap.ps1 +83 -0
- package/bootstrap.sh +71 -0
- package/dist/agent/loop.d.ts +68 -0
- package/dist/agent/loop.d.ts.map +1 -0
- package/dist/agent/loop.js +135 -0
- package/dist/agent/mcp.d.ts +33 -0
- package/dist/agent/mcp.d.ts.map +1 -0
- package/dist/agent/mcp.js +107 -0
- package/dist/agent/session.d.ts +16 -0
- package/dist/agent/session.d.ts.map +1 -0
- package/dist/agent/session.js +55 -0
- package/dist/agent/skills.d.ts +36 -0
- package/dist/agent/skills.d.ts.map +1 -0
- package/dist/agent/skills.js +153 -0
- package/dist/agent/stack.d.ts +21 -0
- package/dist/agent/stack.d.ts.map +1 -0
- package/dist/agent/stack.js +158 -0
- package/dist/agent/task.d.ts +21 -0
- package/dist/agent/task.d.ts.map +1 -0
- package/dist/agent/task.js +45 -0
- package/dist/agent/tools.d.ts +44 -0
- package/dist/agent/tools.d.ts.map +1 -0
- package/dist/agent/tools.js +262 -0
- package/dist/agent/trace.d.ts +34 -0
- package/dist/agent/trace.d.ts.map +1 -0
- package/dist/agent/trace.js +72 -0
- package/dist/agent.d.ts +46 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +103 -0
- package/dist/auth.d.ts +74 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +116 -0
- package/dist/brain/anthropic.d.ts +19 -0
- package/dist/brain/anthropic.d.ts.map +1 -0
- package/dist/brain/anthropic.js +74 -0
- package/dist/brain/claude-cli.d.ts +20 -0
- package/dist/brain/claude-cli.d.ts.map +1 -0
- package/dist/brain/claude-cli.js +79 -0
- package/dist/brain/ghost-ember.d.ts +28 -0
- package/dist/brain/ghost-ember.d.ts.map +1 -0
- package/dist/brain/ghost-ember.js +97 -0
- package/dist/brain/index.d.ts +22 -0
- package/dist/brain/index.d.ts.map +1 -0
- package/dist/brain/index.js +95 -0
- package/dist/brain/openai-compat.d.ts +21 -0
- package/dist/brain/openai-compat.d.ts.map +1 -0
- package/dist/brain/openai-compat.js +119 -0
- package/dist/brain/router/classify.d.ts +23 -0
- package/dist/brain/router/classify.d.ts.map +1 -0
- package/dist/brain/router/classify.js +160 -0
- package/dist/brain/router/execute.d.ts +23 -0
- package/dist/brain/router/execute.d.ts.map +1 -0
- package/dist/brain/router/execute.js +84 -0
- package/dist/brain/router/index.d.ts +26 -0
- package/dist/brain/router/index.d.ts.map +1 -0
- package/dist/brain/router/index.js +118 -0
- package/dist/brain/router/routing-memory.d.ts +27 -0
- package/dist/brain/router/routing-memory.d.ts.map +1 -0
- package/dist/brain/router/routing-memory.js +77 -0
- package/dist/brain/router/select.d.ts +32 -0
- package/dist/brain/router/select.d.ts.map +1 -0
- package/dist/brain/router/select.js +146 -0
- package/dist/brain/router/two-hop.d.ts +23 -0
- package/dist/brain/router/two-hop.d.ts.map +1 -0
- package/dist/brain/router/two-hop.js +39 -0
- package/dist/brain/router/verify.d.ts +37 -0
- package/dist/brain/router/verify.d.ts.map +1 -0
- package/dist/brain/router/verify.js +111 -0
- package/dist/brain/types.d.ts +55 -0
- package/dist/brain/types.d.ts.map +1 -0
- package/dist/brain/types.js +16 -0
- package/dist/brain/worker.d.ts +27 -0
- package/dist/brain/worker.d.ts.map +1 -0
- package/dist/brain/worker.js +71 -0
- package/dist/commands/ai.d.ts +24 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +137 -0
- package/dist/commands/alerts.d.ts +19 -0
- package/dist/commands/alerts.d.ts.map +1 -0
- package/dist/commands/alerts.js +114 -0
- package/dist/commands/billing.d.ts +13 -0
- package/dist/commands/billing.d.ts.map +1 -0
- package/dist/commands/billing.js +55 -0
- package/dist/commands/chat.d.ts +22 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +422 -0
- package/dist/commands/config.d.ts +18 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +136 -0
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +73 -0
- package/dist/commands/global.d.ts +11 -0
- package/dist/commands/global.d.ts.map +1 -0
- package/dist/commands/global.js +253 -0
- package/dist/commands/keep.d.ts +12 -0
- package/dist/commands/keep.d.ts.map +1 -0
- package/dist/commands/keep.js +58 -0
- package/dist/commands/lifecycle.d.ts +17 -0
- package/dist/commands/lifecycle.d.ts.map +1 -0
- package/dist/commands/lifecycle.js +267 -0
- package/dist/commands/login.d.ts +16 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +234 -0
- package/dist/commands/maintenance.d.ts +12 -0
- package/dist/commands/maintenance.d.ts.map +1 -0
- package/dist/commands/maintenance.js +76 -0
- package/dist/commands/mcp.d.ts +16 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +56 -0
- package/dist/commands/memory.d.ts +13 -0
- package/dist/commands/memory.d.ts.map +1 -0
- package/dist/commands/memory.js +218 -0
- package/dist/commands/osint.d.ts +14 -0
- package/dist/commands/osint.d.ts.map +1 -0
- package/dist/commands/osint.js +161 -0
- package/dist/commands/pentest.d.ts +13 -0
- package/dist/commands/pentest.d.ts.map +1 -0
- package/dist/commands/pentest.js +131 -0
- package/dist/commands/scale.d.ts +14 -0
- package/dist/commands/scale.d.ts.map +1 -0
- package/dist/commands/scale.js +191 -0
- package/dist/commands/serve.d.ts +16 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +167 -0
- package/dist/commands/tui.d.ts +17 -0
- package/dist/commands/tui.d.ts.map +1 -0
- package/dist/commands/tui.js +138 -0
- package/dist/commands/wyrm.d.ts +20 -0
- package/dist/commands/wyrm.d.ts.map +1 -0
- package/dist/commands/wyrm.js +274 -0
- package/dist/config.d.ts +67 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +54 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/manifest.d.ts +31 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +83 -0
- package/dist/ui.d.ts +57 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +174 -0
- package/dist/utils.d.ts +33 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +155 -0
- package/dist/wyrm/mcp.d.ts +37 -0
- package/dist/wyrm/mcp.d.ts.map +1 -0
- package/dist/wyrm/mcp.js +137 -0
- package/docs/SYSTEM-PREMORTEM.md +397 -0
- package/dragon-manifest.toml +241 -0
- package/dragon.py +177 -0
- package/install/launchd/lk.ghosts.dragonkeep.plist +57 -0
- package/install/systemd/dragonkeep.service +40 -0
- package/media/dragon-silver-lockup.svg +931 -0
- package/media/dragon-silver-mark.svg +931 -0
- package/media/dragon-silver.png +0 -0
- package/package.json +45 -0
- package/specs/001-godmode/constitution.md +54 -0
- package/specs/001-godmode/plan.md +30 -0
- package/specs/001-godmode/spec.md +64 -0
- package/specs/001-godmode/tasks.md +35 -0
- package/specs/002-premortem-positioning/premortem.md +211 -0
- package/src/agent/loop.ts +165 -0
- package/src/agent/mcp.ts +92 -0
- package/src/agent/session.ts +48 -0
- package/src/agent/skills.ts +138 -0
- package/src/agent/stack.ts +154 -0
- package/src/agent/task.ts +55 -0
- package/src/agent/tools.ts +255 -0
- package/src/agent/trace.ts +76 -0
- package/src/agent.ts +114 -0
- package/src/auth.ts +133 -0
- package/src/brain/anthropic.ts +83 -0
- package/src/brain/claude-cli.ts +78 -0
- package/src/brain/ghost-ember.ts +94 -0
- package/src/brain/index.ts +99 -0
- package/src/brain/openai-compat.ts +115 -0
- package/src/brain/router/classify.ts +167 -0
- package/src/brain/router/execute.ts +80 -0
- package/src/brain/router/index.ts +125 -0
- package/src/brain/router/routing-memory.ts +71 -0
- package/src/brain/router/select.ts +156 -0
- package/src/brain/router/two-hop.ts +62 -0
- package/src/brain/router/verify.ts +123 -0
- package/src/brain/types.ts +61 -0
- package/src/brain/worker.ts +72 -0
- package/src/commands/ai.ts +144 -0
- package/src/commands/alerts.ts +131 -0
- package/src/commands/billing.ts +59 -0
- package/src/commands/chat.ts +318 -0
- package/src/commands/config.ts +137 -0
- package/src/commands/doctor.ts +71 -0
- package/src/commands/global.ts +256 -0
- package/src/commands/keep.ts +67 -0
- package/src/commands/lifecycle.ts +273 -0
- package/src/commands/login.ts +184 -0
- package/src/commands/maintenance.ts +54 -0
- package/src/commands/mcp.ts +57 -0
- package/src/commands/memory.ts +229 -0
- package/src/commands/osint.ts +171 -0
- package/src/commands/pentest.ts +140 -0
- package/src/commands/scale.ts +185 -0
- package/src/commands/serve.ts +171 -0
- package/src/commands/tui.ts +126 -0
- package/src/commands/wyrm.ts +269 -0
- package/src/config.ts +93 -0
- package/src/index.ts +92 -0
- package/src/manifest.ts +104 -0
- package/src/ui.ts +188 -0
- package/src/utils.ts +153 -0
- package/src/wyrm/mcp.ts +130 -0
- package/test/auth.test.ts +70 -0
- package/test/brain.test.ts +39 -0
- package/test/security.test.ts +104 -0
- package/test/skills.test.ts +38 -0
- package/test/ui.test.ts +46 -0
- package/tsconfig.json +19 -0
- package/worker/package-lock.json +1527 -0
- package/worker/package.json +17 -0
- package/worker/src/index.ts +76 -0
- package/worker/tsconfig.json +15 -0
- package/worker/wrangler.toml +26 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security-critical pure-logic tests (the adversarial layer). Run against the
|
|
3
|
+
* built output so there's no train/serve skew: `npm run build && vitest run`.
|
|
4
|
+
*
|
|
5
|
+
* Covers the guards that stand between an LLM (or a poisoned config) and the
|
|
6
|
+
* operator's machine: URL/SSRF guard, credential header-injection, secret
|
|
7
|
+
* redaction, and working-dir confinement.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect } from 'vitest'
|
|
10
|
+
import { isBrowsableHttpUrl, sanitizeApiBase, validCred, DEFAULT_API } from '../dist/auth.js'
|
|
11
|
+
import { redact } from '../dist/agent/trace.js'
|
|
12
|
+
import { guardPath } from '../dist/agent/tools.js'
|
|
13
|
+
import { resolveProvider } from '../dist/brain/index.js'
|
|
14
|
+
|
|
15
|
+
describe('isBrowsableHttpUrl — restricted-port / SSRF guard', () => {
|
|
16
|
+
it('accepts https on normal hosts', () => {
|
|
17
|
+
expect(isBrowsableHttpUrl('https://account.ghosts.lk')).toBe(true)
|
|
18
|
+
expect(isBrowsableHttpUrl('https://account.ghosts.lk/cli?code=X')).toBe(true)
|
|
19
|
+
})
|
|
20
|
+
it('accepts http only on loopback', () => {
|
|
21
|
+
expect(isBrowsableHttpUrl('http://localhost:8799')).toBe(true)
|
|
22
|
+
expect(isBrowsableHttpUrl('http://127.0.0.1:8787')).toBe(true)
|
|
23
|
+
expect(isBrowsableHttpUrl('http://evil.example.com')).toBe(false) // remote must be https
|
|
24
|
+
})
|
|
25
|
+
it('rejects restricted ports (the "address is restricted" bug)', () => {
|
|
26
|
+
for (const p of [1, 22, 25, 6000, 6667]) {
|
|
27
|
+
expect(isBrowsableHttpUrl(`http://127.0.0.1:${p}`)).toBe(false)
|
|
28
|
+
expect(isBrowsableHttpUrl(`https://x.test:${p}`)).toBe(false)
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
it('rejects non-http(s) + junk', () => {
|
|
32
|
+
expect(isBrowsableHttpUrl('ftp://x.test')).toBe(false)
|
|
33
|
+
expect(isBrowsableHttpUrl('file:///etc/passwd')).toBe(false)
|
|
34
|
+
expect(isBrowsableHttpUrl('not a url')).toBe(false)
|
|
35
|
+
expect(isBrowsableHttpUrl('javascript:alert(1)')).toBe(false)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('sanitizeApiBase — self-heal', () => {
|
|
40
|
+
it('keeps a valid base, strips trailing slash', () => {
|
|
41
|
+
expect(sanitizeApiBase('https://account.ghosts.lk/')).toBe('https://account.ghosts.lk')
|
|
42
|
+
})
|
|
43
|
+
it('falls back to default on junk / restricted port', () => {
|
|
44
|
+
expect(sanitizeApiBase('http://127.0.0.1:1')).toBe(DEFAULT_API)
|
|
45
|
+
expect(sanitizeApiBase('ftp://x')).toBe(DEFAULT_API)
|
|
46
|
+
expect(sanitizeApiBase(undefined)).toBe(DEFAULT_API)
|
|
47
|
+
expect(sanitizeApiBase('')).toBe(DEFAULT_API)
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('validCred — header-injection guard', () => {
|
|
52
|
+
it('accepts a clean token/session', () => {
|
|
53
|
+
expect(validCred('dgn_C2sMFq5KXYPBrku7kfL3')).toBeTruthy()
|
|
54
|
+
expect(validCred('abc123-_.')).toBeTruthy()
|
|
55
|
+
})
|
|
56
|
+
it('rejects CRLF / control chars / spaces (no header smuggling)', () => {
|
|
57
|
+
expect(validCred('tok\r\nX-Evil: 1')).toBeUndefined()
|
|
58
|
+
expect(validCred('tok\nfoo')).toBeUndefined()
|
|
59
|
+
expect(validCred('tok with space')).toBeUndefined()
|
|
60
|
+
expect(validCred('')).toBeUndefined()
|
|
61
|
+
expect(validCred(undefined)).toBeUndefined()
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe('redact — secret stripping for traces', () => {
|
|
66
|
+
it('redacts known key/token shapes', () => {
|
|
67
|
+
expect(redact('key sk-ant-abcdEFGH1234_- end')).not.toContain('abcdEFGH')
|
|
68
|
+
expect(redact('tok dgn_C2sMFq5KXYPBrku7kfL3S_Ao end')).not.toContain('C2sMFq5')
|
|
69
|
+
expect(redact('ghp_0123456789abcdef0123456789abcdef0123')).toContain('[redacted]')
|
|
70
|
+
expect(redact('Cookie: gp_session=deadbeef-cafe-1234')).not.toContain('deadbeef')
|
|
71
|
+
})
|
|
72
|
+
it('leaves ordinary text alone', () => {
|
|
73
|
+
expect(redact('the quick brown fox')).toBe('the quick brown fox')
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('guardPath — working-dir confinement', () => {
|
|
78
|
+
const cwd = process.cwd()
|
|
79
|
+
it('marks in-cwd paths as inside', () => {
|
|
80
|
+
expect(guardPath(cwd, 'src/index.ts').outside).toBe(false)
|
|
81
|
+
expect(guardPath(cwd, './package.json').outside).toBe(false)
|
|
82
|
+
})
|
|
83
|
+
it('marks outside-cwd absolute/relative paths as outside', () => {
|
|
84
|
+
expect(guardPath(cwd, '/etc/passwd').outside).toBe(true)
|
|
85
|
+
expect(guardPath(cwd, '../../../../etc/hosts').outside).toBe(true)
|
|
86
|
+
})
|
|
87
|
+
it('flags protected credential paths regardless of location', () => {
|
|
88
|
+
expect(guardPath(cwd, `${process.env.HOME}/.ssh/id_rsa`).protectedPath).toBe(true)
|
|
89
|
+
expect(guardPath(cwd, `${process.env.HOME}/.dragon/config.json`).protectedPath).toBe(true)
|
|
90
|
+
expect(guardPath(cwd, '.env').protectedPath).toBe(true)
|
|
91
|
+
expect(guardPath(cwd, 'secrets.pem').protectedPath).toBe(true)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('resolveProvider — brain selection', () => {
|
|
96
|
+
it('defaults to claude when unset', () => {
|
|
97
|
+
delete process.env.DRAGON_BRAIN
|
|
98
|
+
expect(['claude', 'worker', 'local', 'openai', 'ghost']).toContain(resolveProvider())
|
|
99
|
+
})
|
|
100
|
+
it('honors an explicit override', () => {
|
|
101
|
+
expect(resolveProvider('worker')).toBe('worker')
|
|
102
|
+
expect(resolveProvider('bogus')).toBe('claude') // unknown → safe default
|
|
103
|
+
})
|
|
104
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills-as-tools: frontmatter parsing + library behavior. Deterministic unit tests
|
|
3
|
+
* for the parser; the live-library checks stay environment-agnostic (the skill dir
|
|
4
|
+
* isn't in CI), asserting only sane types so the gate is portable.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from 'vitest'
|
|
7
|
+
import { parseFrontmatter, loadSkillLibrary } from '../dist/agent/skills.js'
|
|
8
|
+
|
|
9
|
+
describe('parseFrontmatter', () => {
|
|
10
|
+
it('parses inline name + description', () => {
|
|
11
|
+
const r = parseFrontmatter('---\nname: foo-bar\ndescription: A short description here\n---\nbody text')
|
|
12
|
+
expect(r.name).toBe('foo-bar')
|
|
13
|
+
expect(r.description).toBe('A short description here')
|
|
14
|
+
})
|
|
15
|
+
it('parses a folded (>-) multi-line description', () => {
|
|
16
|
+
const r = parseFrontmatter('---\nname: my-skill\ndescription: >-\n line one of the\n folded description\nuser-invocable: true\n---\n')
|
|
17
|
+
expect(r.name).toBe('my-skill')
|
|
18
|
+
expect(r.description).toBe('line one of the folded description')
|
|
19
|
+
})
|
|
20
|
+
it('strips quotes and tolerates no frontmatter', () => {
|
|
21
|
+
expect(parseFrontmatter('---\nname: "quoted"\n---').name).toBe('quoted')
|
|
22
|
+
expect(parseFrontmatter('no frontmatter at all').name).toBeUndefined()
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('SkillLibrary', () => {
|
|
27
|
+
const lib = loadSkillLibrary() // never throws; empty if no skill dirs
|
|
28
|
+
it('loads without throwing and reports a count', () => {
|
|
29
|
+
expect(lib.count).toBeGreaterThanOrEqual(0)
|
|
30
|
+
})
|
|
31
|
+
it('search returns an array (and is empty for nonsense)', () => {
|
|
32
|
+
expect(Array.isArray(lib.search('xyzzy-nonexistent-term'))).toBe(true)
|
|
33
|
+
expect(lib.search('').length).toBe(0)
|
|
34
|
+
})
|
|
35
|
+
it('read returns null for an unknown skill', () => {
|
|
36
|
+
expect(lib.read('definitely-not-a-real-skill-name-9000')).toBeNull()
|
|
37
|
+
})
|
|
38
|
+
})
|
package/test/ui.test.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sub-cell data-viz tests (the Braille/block terminal-graphics layer in ui.ts).
|
|
3
|
+
* Run against the built output: `npm run build && vitest run`. Colour is chalk-
|
|
4
|
+
* wrapped, so we strip ANSI and assert on the visible glyphs/geometry.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from 'vitest'
|
|
7
|
+
import { brailleChart, sparkline, gauge } from '../dist/ui.js'
|
|
8
|
+
|
|
9
|
+
const strip = (s: string) => s.replace(/\x1b\[[0-9;]*m/g, '')
|
|
10
|
+
const isBraille = /[⠀-⣿]/
|
|
11
|
+
|
|
12
|
+
describe('brailleChart', () => {
|
|
13
|
+
it('returns `height` rows, each `width` visible cells wide', () => {
|
|
14
|
+
const rows = brailleChart([0, 1, 2, 3, 4, 5, 6, 7, 8], { width: 20, height: 3 })
|
|
15
|
+
expect(rows).toHaveLength(3)
|
|
16
|
+
for (const r of rows) expect([...strip(r)].length).toBe(20)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('renders glyphs from the U+2800 Braille block for non-empty data', () => {
|
|
20
|
+
const rows = brailleChart([1, 5, 2, 8, 3, 9, 4], { width: 16, height: 3, area: true })
|
|
21
|
+
expect(isBraille.test(strip(rows.join('')))).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('area fill always anchors a baseline on the bottom row', () => {
|
|
25
|
+
const rows = brailleChart([0, 0, 9, 0, 0], { width: 8, height: 3, area: true, max: 9 })
|
|
26
|
+
expect(isBraille.test(strip(rows[rows.length - 1]))).toBe(true)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('does not throw on empty input', () => {
|
|
30
|
+
expect(() => brailleChart([], { width: 10, height: 2 })).not.toThrow()
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('sparkline / gauge', () => {
|
|
35
|
+
it('sparkline maps values onto the 8 block levels', () => {
|
|
36
|
+
const s = strip(sparkline([0, 1, 2, 3, 4, 5, 6, 7]))
|
|
37
|
+
expect(s).toHaveLength(8)
|
|
38
|
+
expect(/[▁-█]/.test(s)).toBe(true)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('gauge fills proportionally and pads to the requested width', () => {
|
|
42
|
+
const g = strip(gauge(0.5, 10))
|
|
43
|
+
expect([...g].length).toBe(10)
|
|
44
|
+
expect(g).toContain('█')
|
|
45
|
+
})
|
|
46
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|