bazilion 0.0.0 → 0.1.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.
- package/README.md +53 -152
- package/dist/cli.js +2730 -0
- package/dist/cli.js.map +1 -0
- package/dist/daemon.js +5888 -0
- package/dist/daemon.js.map +1 -0
- package/dist/worker.js +1830 -0
- package/dist/worker.js.map +1 -0
- package/package.json +57 -28
- package/.understand-anything/.understandignore +0 -25
- package/.understand-anything/fingerprints.json +0 -14267
- package/.understand-anything/knowledge-graph.json +0 -18128
- package/.understand-anything/meta.json +0 -6
- package/CLAUDE.md +0 -164
- package/apps/cli/package.json +0 -21
- package/apps/cli/src/auth-file.ts +0 -18
- package/apps/cli/src/client.ts +0 -37
- package/apps/cli/src/columnize.ts +0 -32
- package/apps/cli/src/commands/agent.ts +0 -574
- package/apps/cli/src/commands/auth.ts +0 -110
- package/apps/cli/src/commands/backup.ts +0 -135
- package/apps/cli/src/commands/completion.ts +0 -155
- package/apps/cli/src/commands/config.ts +0 -95
- package/apps/cli/src/commands/doctor.ts +0 -131
- package/apps/cli/src/commands/group.ts +0 -132
- package/apps/cli/src/commands/inbox.ts +0 -82
- package/apps/cli/src/commands/login.ts +0 -73
- package/apps/cli/src/commands/memory.ts +0 -106
- package/apps/cli/src/commands/profile.ts +0 -259
- package/apps/cli/src/commands/provider.ts +0 -170
- package/apps/cli/src/commands/send.ts +0 -24
- package/apps/cli/src/commands/serve.ts +0 -89
- package/apps/cli/src/commands/skill.ts +0 -120
- package/apps/cli/src/commands/token.ts +0 -148
- package/apps/cli/src/commands/trigger.ts +0 -129
- package/apps/cli/src/commands/uninstall.ts +0 -156
- package/apps/cli/src/index.ts +0 -196
- package/apps/cli/src/paths.ts +0 -12
- package/apps/cli/test/agent.test.ts +0 -213
- package/apps/cli/test/backup.test.ts +0 -95
- package/apps/cli/test/chat.test.ts +0 -539
- package/apps/cli/test/columnize.test.ts +0 -29
- package/apps/cli/test/completion.test.ts +0 -45
- package/apps/cli/test/config-page.test.ts +0 -151
- package/apps/cli/test/group.test.ts +0 -74
- package/apps/cli/test/helpers.ts +0 -70
- package/apps/cli/test/inbox-autodeliver.test.ts +0 -143
- package/apps/cli/test/inbox.test.ts +0 -147
- package/apps/cli/test/memory.test.ts +0 -69
- package/apps/cli/test/profile.test.ts +0 -110
- package/apps/cli/test/send.test.ts +0 -30
- package/apps/cli/test/server-fixture.ts +0 -212
- package/apps/cli/test/session-head.test.ts +0 -45
- package/apps/cli/test/skill.test.ts +0 -128
- package/apps/cli/test/token.test.ts +0 -109
- package/apps/cli/test/trigger.test.ts +0 -245
- package/apps/cli/tsconfig.json +0 -4
- package/apps/daemon/package.json +0 -31
- package/apps/daemon/src/app.ts +0 -41
- package/apps/daemon/src/core/agent/archive.ts +0 -8
- package/apps/daemon/src/core/agent/delete.ts +0 -30
- package/apps/daemon/src/core/agent/resolve.ts +0 -31
- package/apps/daemon/src/core/agent/spawn.ts +0 -95
- package/apps/daemon/src/core/agent/unarchive.ts +0 -11
- package/apps/daemon/src/core/availableModels.ts +0 -58
- package/apps/daemon/src/core/db/client.ts +0 -113
- package/apps/daemon/src/core/db/migrate.ts +0 -41
- package/apps/daemon/src/core/db/migrations/0001_init.sql +0 -179
- package/apps/daemon/src/core/group/delete.ts +0 -21
- package/apps/daemon/src/core/group/register.ts +0 -68
- package/apps/daemon/src/core/index.ts +0 -71
- package/apps/daemon/src/core/paths.ts +0 -56
- package/apps/daemon/src/core/profile/create.ts +0 -78
- package/apps/daemon/src/core/profile/delete.ts +0 -26
- package/apps/daemon/src/core/profile/identity.ts +0 -70
- package/apps/daemon/src/core/profile/load.ts +0 -45
- package/apps/daemon/src/core/profile/seed.ts +0 -74
- package/apps/daemon/src/core/profile/templates.ts +0 -60
- package/apps/daemon/src/core/profile/update.ts +0 -57
- package/apps/daemon/src/core/profile/validate.ts +0 -9
- package/apps/daemon/src/core/repos/agents.ts +0 -202
- package/apps/daemon/src/core/repos/config.ts +0 -78
- package/apps/daemon/src/core/repos/groups.ts +0 -55
- package/apps/daemon/src/core/repos/messages.ts +0 -127
- package/apps/daemon/src/core/repos/profiles.ts +0 -83
- package/apps/daemon/src/core/repos/providerModels.ts +0 -58
- package/apps/daemon/src/core/repos/providerState.ts +0 -37
- package/apps/daemon/src/core/repos/secrets.ts +0 -145
- package/apps/daemon/src/core/repos/skillMeta.ts +0 -49
- package/apps/daemon/src/core/repos/triggers.ts +0 -101
- package/apps/daemon/src/core/repos/webTokens.ts +0 -87
- package/apps/daemon/src/core/secrets.ts +0 -65
- package/apps/daemon/src/core/services.ts +0 -264
- package/apps/daemon/src/core/skills/discover.ts +0 -28
- package/apps/daemon/src/core/skills/import.ts +0 -136
- package/apps/daemon/src/core/skills/parse.ts +0 -52
- package/apps/daemon/src/core/skills/resolve.ts +0 -50
- package/apps/daemon/src/index.ts +0 -45
- package/apps/daemon/src/lib/agent-cancel.ts +0 -48
- package/apps/daemon/src/lib/agent-id.ts +0 -13
- package/apps/daemon/src/lib/agent-turn.ts +0 -56
- package/apps/daemon/src/lib/api-key.ts +0 -56
- package/apps/daemon/src/lib/auth.ts +0 -41
- package/apps/daemon/src/lib/cron.ts +0 -93
- package/apps/daemon/src/lib/ctx.ts +0 -80
- package/apps/daemon/src/lib/messaging-host.ts +0 -34
- package/apps/daemon/src/lib/middleware-auth.ts +0 -52
- package/apps/daemon/src/lib/scheduler.ts +0 -294
- package/apps/daemon/src/routes/agents.ts +0 -772
- package/apps/daemon/src/routes/auth-login.ts +0 -193
- package/apps/daemon/src/routes/config.ts +0 -267
- package/apps/daemon/src/routes/groups.ts +0 -133
- package/apps/daemon/src/routes/messages.ts +0 -29
- package/apps/daemon/src/routes/misc.ts +0 -239
- package/apps/daemon/src/routes/profiles.ts +0 -197
- package/apps/daemon/src/routes/skills.ts +0 -123
- package/apps/daemon/src/routes/triggers.ts +0 -29
- package/apps/daemon/src/runtime/auth/openai-codex.ts +0 -121
- package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +0 -31
- package/apps/daemon/src/runtime/index.ts +0 -77
- package/apps/daemon/src/runtime/memory/files.ts +0 -103
- package/apps/daemon/src/runtime/memory/qmd.ts +0 -152
- package/apps/daemon/src/runtime/memory/types.ts +0 -16
- package/apps/daemon/src/runtime/pi/events.ts +0 -173
- package/apps/daemon/src/runtime/pi/session.ts +0 -536
- package/apps/daemon/src/runtime/pi/tools.ts +0 -85
- package/apps/daemon/src/runtime/providers/catalog.ts +0 -145
- package/apps/daemon/src/runtime/providers/pi-adapter.ts +0 -272
- package/apps/daemon/src/runtime/providers/registry.ts +0 -374
- package/apps/daemon/src/runtime/providers/retry.ts +0 -176
- package/apps/daemon/src/runtime/providers/types.ts +0 -33
- package/apps/daemon/src/runtime/session/prompt.ts +0 -83
- package/apps/daemon/src/runtime/tools/bootstrap.ts +0 -22
- package/apps/daemon/src/runtime/tools/home.ts +0 -114
- package/apps/daemon/src/runtime/tools/memory.ts +0 -81
- package/apps/daemon/src/runtime/tools/messaging.ts +0 -127
- package/apps/daemon/src/runtime/tools/registry.ts +0 -29
- package/apps/daemon/src/runtime/tools/types.ts +0 -13
- package/apps/daemon/src/runtime/tools/web-extract.ts +0 -110
- package/apps/daemon/src/runtime/tools/web-ssrf.ts +0 -245
- package/apps/daemon/src/runtime/tools/web.ts +0 -273
- package/apps/daemon/src/runtime/worker/entry.ts +0 -221
- package/apps/daemon/src/runtime/worker/ipc-protocol.ts +0 -75
- package/apps/daemon/src/runtime/worker/spawn.ts +0 -249
- package/apps/daemon/test/core/agents.test.ts +0 -319
- package/apps/daemon/test/core/available-models.test.ts +0 -63
- package/apps/daemon/test/core/config.test.ts +0 -99
- package/apps/daemon/test/core/groups.test.ts +0 -63
- package/apps/daemon/test/core/helpers.ts +0 -50
- package/apps/daemon/test/core/identity.test.ts +0 -85
- package/apps/daemon/test/core/migrations.test.ts +0 -83
- package/apps/daemon/test/core/profiles.test.ts +0 -182
- package/apps/daemon/test/core/provider-models.test.ts +0 -57
- package/apps/daemon/test/core/provider-state.test.ts +0 -36
- package/apps/daemon/test/core/skill-meta.test.ts +0 -45
- package/apps/daemon/test/core/skills.test.ts +0 -271
- package/apps/daemon/test/core/triggers.test.ts +0 -182
- package/apps/daemon/test/core/web-tokens.test.ts +0 -79
- package/apps/daemon/test/cron.test.ts +0 -90
- package/apps/daemon/test/runtime/heartbeat.test.ts +0 -34
- package/apps/daemon/test/runtime/memory-qmd.test.ts +0 -97
- package/apps/daemon/test/runtime/memory.test.ts +0 -78
- package/apps/daemon/test/runtime/messaging.test.ts +0 -213
- package/apps/daemon/test/runtime/mock-server.ts +0 -65
- package/apps/daemon/test/runtime/openai-codex-auth.test.ts +0 -116
- package/apps/daemon/test/runtime/providers.test.ts +0 -306
- package/apps/daemon/test/runtime/retry.test.ts +0 -191
- package/apps/daemon/test/runtime/session-head.test.ts +0 -90
- package/apps/daemon/test/runtime/tools-home.test.ts +0 -102
- package/apps/daemon/test/runtime/tools-web.test.ts +0 -206
- package/apps/daemon/tsconfig.json +0 -4
- package/apps/mobile/README.md +0 -60
- package/apps/mobile/app/_layout.tsx +0 -58
- package/apps/mobile/app/agents/[id]/chat.tsx +0 -486
- package/apps/mobile/app/agents/[id]/index.tsx +0 -166
- package/apps/mobile/app/agents/index.tsx +0 -212
- package/apps/mobile/app/index.tsx +0 -21
- package/apps/mobile/app/pair.tsx +0 -226
- package/apps/mobile/app/settings.tsx +0 -419
- package/apps/mobile/app.json +0 -49
- package/apps/mobile/assets/adaptive-icon.png +0 -0
- package/apps/mobile/assets/favicon.png +0 -0
- package/apps/mobile/assets/icon.png +0 -0
- package/apps/mobile/assets/splash-icon.png +0 -0
- package/apps/mobile/babel.config.js +0 -6
- package/apps/mobile/metro.config.js +0 -28
- package/apps/mobile/package.json +0 -44
- package/apps/mobile/src/auth.ts +0 -66
- package/apps/mobile/src/pair-url.ts +0 -48
- package/apps/mobile/src/theme-context.tsx +0 -88
- package/apps/mobile/src/theme.ts +0 -135
- package/apps/mobile/test/pair-url.test.ts +0 -46
- package/apps/mobile/tsconfig.json +0 -23
- package/apps/web/components.json +0 -25
- package/apps/web/package.json +0 -39
- package/apps/web/public/baziu.svg +0 -8
- package/apps/web/src/components/AgentTabs.tsx +0 -45
- package/apps/web/src/components/BaziuLogo.tsx +0 -21
- package/apps/web/src/components/ChatPane.tsx +0 -1033
- package/apps/web/src/components/ConfigTabs.tsx +0 -29
- package/apps/web/src/components/CopyButton.tsx +0 -68
- package/apps/web/src/components/CreateGroupDialog.tsx +0 -127
- package/apps/web/src/components/FieldRow.tsx +0 -94
- package/apps/web/src/components/Footer.tsx +0 -10
- package/apps/web/src/components/PawIcon.tsx +0 -15
- package/apps/web/src/components/Sidebar.tsx +0 -287
- package/apps/web/src/components/SpawnDialog.tsx +0 -129
- package/apps/web/src/components/ThemeToggle.tsx +0 -75
- package/apps/web/src/components/TopNav.tsx +0 -34
- package/apps/web/src/components/ui/button.tsx +0 -67
- package/apps/web/src/components/ui/card.tsx +0 -103
- package/apps/web/src/components/ui/checkbox.tsx +0 -31
- package/apps/web/src/components/ui/dialog.tsx +0 -168
- package/apps/web/src/components/ui/input.tsx +0 -19
- package/apps/web/src/components/ui/label.tsx +0 -22
- package/apps/web/src/components/ui/radio-group.tsx +0 -44
- package/apps/web/src/components/ui/select.tsx +0 -192
- package/apps/web/src/components/ui/separator.tsx +0 -26
- package/apps/web/src/components/ui/table.tsx +0 -116
- package/apps/web/src/components/ui/tabs.tsx +0 -88
- package/apps/web/src/components/ui/textarea.tsx +0 -18
- package/apps/web/src/lib/auth.ts +0 -50
- package/apps/web/src/lib/daemon-client.ts +0 -34
- package/apps/web/src/lib/md.ts +0 -45
- package/apps/web/src/lib/utils.ts +0 -6
- package/apps/web/src/lib/wire-constants.ts +0 -27
- package/apps/web/src/routeTree.gen.ts +0 -408
- package/apps/web/src/router.tsx +0 -20
- package/apps/web/src/routes/__root.tsx +0 -123
- package/apps/web/src/routes/agents/$id/inbox.tsx +0 -207
- package/apps/web/src/routes/agents/$id/index.tsx +0 -527
- package/apps/web/src/routes/agents/$id/triggers.tsx +0 -239
- package/apps/web/src/routes/agents/index.tsx +0 -265
- package/apps/web/src/routes/api/$.ts +0 -88
- package/apps/web/src/routes/config/index.tsx +0 -315
- package/apps/web/src/routes/config/services.tsx +0 -49
- package/apps/web/src/routes/config/tokens.tsx +0 -192
- package/apps/web/src/routes/groups/$id/index.tsx +0 -153
- package/apps/web/src/routes/groups/$id/memory.tsx +0 -321
- package/apps/web/src/routes/groups/index.tsx +0 -191
- package/apps/web/src/routes/index.tsx +0 -133
- package/apps/web/src/routes/login.tsx +0 -63
- package/apps/web/src/routes/profiles/$id.tsx +0 -549
- package/apps/web/src/routes/profiles/index.tsx +0 -458
- package/apps/web/src/routes/skills/index.tsx +0 -297
- package/apps/web/src/routes/welcome.tsx +0 -61
- package/apps/web/src/styles.css +0 -449
- package/apps/web/tsconfig.json +0 -25
- package/apps/web/vite.config.ts +0 -25
- package/biome.json +0 -23
- package/docs/agent-engine.md +0 -219
- package/docs/architecture.md +0 -627
- package/docs/backlog/README.md +0 -42
- package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +0 -125
- package/docs/openclaw-reference.md +0 -210
- package/packages/api-types/package.json +0 -11
- package/packages/api-types/src/entities.ts +0 -146
- package/packages/api-types/src/events.ts +0 -55
- package/packages/api-types/src/index.ts +0 -488
- package/packages/api-types/src/memory.ts +0 -15
- package/packages/client/package.json +0 -13
- package/packages/client/src/index.ts +0 -117
- package/pnpm-workspace.yaml +0 -3
- package/tsconfig.base.json +0 -23
- package/tsconfig.json +0 -11
- package/vitest.config.ts +0 -22
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, expect, test } from 'vitest'
|
|
2
|
-
import { runMigrations } from '../../src/core/db/migrate.ts'
|
|
3
|
-
import { makeTestEnv, type TestEnv } from './helpers.ts'
|
|
4
|
-
|
|
5
|
-
let env: TestEnv
|
|
6
|
-
beforeAll(() => {
|
|
7
|
-
env = makeTestEnv()
|
|
8
|
-
})
|
|
9
|
-
afterAll(() => env.cleanup())
|
|
10
|
-
|
|
11
|
-
test('migrations create all expected tables', () => {
|
|
12
|
-
const tables = env.db.raw
|
|
13
|
-
.query<{ name: string }, []>(
|
|
14
|
-
"SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name",
|
|
15
|
-
)
|
|
16
|
-
.all()
|
|
17
|
-
.map((r) => r.name)
|
|
18
|
-
|
|
19
|
-
for (const t of [
|
|
20
|
-
'agent_skills',
|
|
21
|
-
'agents',
|
|
22
|
-
'groups',
|
|
23
|
-
'messages',
|
|
24
|
-
'profile_default_skills',
|
|
25
|
-
'profiles',
|
|
26
|
-
'schema_migrations',
|
|
27
|
-
]) {
|
|
28
|
-
expect(tables).toContain(t)
|
|
29
|
-
}
|
|
30
|
-
// Junction tables dropped in 0005; runs + events dropped in 0003.
|
|
31
|
-
expect(tables).not.toContain('agent_workspaces')
|
|
32
|
-
expect(tables).not.toContain('profile_workspaces')
|
|
33
|
-
expect(tables).not.toContain('workspaces')
|
|
34
|
-
expect(tables).not.toContain('runs')
|
|
35
|
-
expect(tables).not.toContain('events')
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test('profiles table has skills_mode and no default_workspace_id', () => {
|
|
39
|
-
const cols = env.db.raw
|
|
40
|
-
.query<{ name: string }, []>("SELECT name FROM pragma_table_info('profiles')")
|
|
41
|
-
.all()
|
|
42
|
-
.map((r) => r.name)
|
|
43
|
-
expect(cols).toContain('skills_mode')
|
|
44
|
-
expect(cols).not.toContain('default_workspace_id')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test('agents table has group_id; groups table has user_md', () => {
|
|
48
|
-
const agentCols = env.db.raw
|
|
49
|
-
.query<{ name: string }, []>("SELECT name FROM pragma_table_info('agents')")
|
|
50
|
-
.all()
|
|
51
|
-
.map((r) => r.name)
|
|
52
|
-
expect(agentCols).toContain('group_id')
|
|
53
|
-
|
|
54
|
-
const groupCols = env.db.raw
|
|
55
|
-
.query<{ name: string }, []>("SELECT name FROM pragma_table_info('groups')")
|
|
56
|
-
.all()
|
|
57
|
-
.map((r) => r.name)
|
|
58
|
-
expect(groupCols).toContain('user_md')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test('migrations are idempotent', () => {
|
|
62
|
-
const before = env.db.raw
|
|
63
|
-
.query<{ version: string }, []>('SELECT version FROM schema_migrations ORDER BY version')
|
|
64
|
-
.all()
|
|
65
|
-
runMigrations(env.db)
|
|
66
|
-
runMigrations(env.db)
|
|
67
|
-
const after = env.db.raw
|
|
68
|
-
.query<{ version: string }, []>('SELECT version FROM schema_migrations ORDER BY version')
|
|
69
|
-
.all()
|
|
70
|
-
expect(after).toEqual(before)
|
|
71
|
-
expect(after.length).toBeGreaterThan(0)
|
|
72
|
-
expect(after[0]?.version).toBe('0001_init')
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
test('foreign keys are enforced', () => {
|
|
76
|
-
expect(() =>
|
|
77
|
-
env.db.raw.run(
|
|
78
|
-
`INSERT INTO agents (id, profile_id, name, status, dir, group_id, created_at)
|
|
79
|
-
VALUES ('a1', 'nope', 'x', 'idle', '/tmp/x', 'test-group', ?)`,
|
|
80
|
-
[Date.now()],
|
|
81
|
-
),
|
|
82
|
-
).toThrow()
|
|
83
|
-
})
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
import { afterEach, beforeEach, expect, test } from 'vitest'
|
|
4
|
-
import { createProfile } from '../../src/core/profile/create.ts'
|
|
5
|
-
import { loadProfile } from '../../src/core/profile/load.ts'
|
|
6
|
-
import { updateProfile } from '../../src/core/profile/update.ts'
|
|
7
|
-
import * as profileRepo from '../../src/core/repos/profiles.ts'
|
|
8
|
-
import { makeTestEnv, type TestEnv } from './helpers.ts'
|
|
9
|
-
|
|
10
|
-
let env: TestEnv
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
env = makeTestEnv()
|
|
13
|
-
})
|
|
14
|
-
afterEach(() => env.cleanup())
|
|
15
|
-
|
|
16
|
-
test('createProfile scaffolds dir, files, and DB row', () => {
|
|
17
|
-
const profile = createProfile(env.db, env.paths, {
|
|
18
|
-
id: 'researcher',
|
|
19
|
-
name: 'Researcher',
|
|
20
|
-
defaultModel: 'anthropic:claude-opus-4-6',
|
|
21
|
-
defaultSkills: ['web-search', 'note-taking'],
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
expect(profile.id).toBe('researcher')
|
|
25
|
-
expect(profile.name).toBe('Researcher')
|
|
26
|
-
expect(profile.dir).toBe(env.paths.profileDir('researcher'))
|
|
27
|
-
expect(existsSync(join(profile.dir, 'SOUL.md'))).toBe(true)
|
|
28
|
-
expect(existsSync(join(profile.dir, 'IDENTITY.md'))).toBe(true)
|
|
29
|
-
expect(existsSync(join(profile.dir, 'BOOTSTRAP.md'))).toBe(true)
|
|
30
|
-
expect(existsSync(join(profile.dir, 'profile.json'))).toBe(true)
|
|
31
|
-
|
|
32
|
-
const fromDb = profileRepo.get(env.db, 'researcher')
|
|
33
|
-
expect(fromDb?.defaultModel).toBe('anthropic:claude-opus-4-6')
|
|
34
|
-
expect(fromDb?.skillsMode).toBe('selected')
|
|
35
|
-
|
|
36
|
-
const skills = profileRepo.getDefaultSkills(env.db, 'researcher')
|
|
37
|
-
expect(skills).toEqual(['note-taking', 'web-search'])
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test('createProfile rejects invalid slug', () => {
|
|
41
|
-
expect(() =>
|
|
42
|
-
createProfile(env.db, env.paths, {
|
|
43
|
-
id: 'NotASlug!',
|
|
44
|
-
defaultModel: 'x',
|
|
45
|
-
}),
|
|
46
|
-
).toThrow(/invalid slug/)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test('loadProfile returns profile, default skills, and file contents', () => {
|
|
50
|
-
createProfile(env.db, env.paths, {
|
|
51
|
-
id: 'p1',
|
|
52
|
-
defaultModel: 'm',
|
|
53
|
-
defaultSkills: ['a', 'b'],
|
|
54
|
-
})
|
|
55
|
-
const loaded = loadProfile(env.db, 'p1')
|
|
56
|
-
expect(loaded.profile.id).toBe('p1')
|
|
57
|
-
expect(loaded.defaultSkills).toEqual(['a', 'b'])
|
|
58
|
-
expect(loaded.files.soul).toContain('SOUL.md')
|
|
59
|
-
expect(loaded.files.identity).toContain('IDENTITY.md')
|
|
60
|
-
expect(loaded.files.bootstrap).toContain('BOOTSTRAP.md')
|
|
61
|
-
// Optional files default to absent — profiles opt in via templates.
|
|
62
|
-
expect(loaded.files.agents).toBeNull()
|
|
63
|
-
expect(loaded.files.tools).toBeNull()
|
|
64
|
-
expect(loaded.files.heartbeat).toBeNull()
|
|
65
|
-
expect(loaded.identity).toBeNull()
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
test('createProfile seeds AGENTS.md / TOOLS.md / HEARTBEAT.md when provided', () => {
|
|
69
|
-
const profile = createProfile(env.db, env.paths, {
|
|
70
|
-
id: 'felix',
|
|
71
|
-
defaultModel: 'm',
|
|
72
|
-
templates: {
|
|
73
|
-
identity: '# IDENTITY\n- Name: Felix\n- Emoji: 🐾\n',
|
|
74
|
-
agents: '# AGENTS\n- peer-a: does stuff\n',
|
|
75
|
-
tools: '# TOOLS\n- run_skill before shell\n',
|
|
76
|
-
heartbeat: '# HEARTBEAT\n- check inbox\n',
|
|
77
|
-
},
|
|
78
|
-
})
|
|
79
|
-
expect(existsSync(join(profile.dir, 'AGENTS.md'))).toBe(true)
|
|
80
|
-
expect(existsSync(join(profile.dir, 'TOOLS.md'))).toBe(true)
|
|
81
|
-
expect(existsSync(join(profile.dir, 'HEARTBEAT.md'))).toBe(true)
|
|
82
|
-
const loaded = loadProfile(env.db, 'felix')
|
|
83
|
-
expect(loaded.files.agents).toContain('peer-a')
|
|
84
|
-
expect(loaded.files.tools).toContain('run_skill')
|
|
85
|
-
expect(loaded.files.heartbeat).toContain('check inbox')
|
|
86
|
-
expect(loaded.identity).toEqual({ name: 'Felix', emoji: '🐾' })
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
test('createProfile skips AGENTS/TOOLS/HEARTBEAT when omitted', () => {
|
|
90
|
-
const profile = createProfile(env.db, env.paths, {
|
|
91
|
-
id: 'bare',
|
|
92
|
-
defaultModel: 'm',
|
|
93
|
-
})
|
|
94
|
-
expect(existsSync(join(profile.dir, 'AGENTS.md'))).toBe(false)
|
|
95
|
-
expect(existsSync(join(profile.dir, 'TOOLS.md'))).toBe(false)
|
|
96
|
-
expect(existsSync(join(profile.dir, 'HEARTBEAT.md'))).toBe(false)
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
test('loadProfile throws on missing profile', () => {
|
|
100
|
-
expect(() => loadProfile(env.db, 'ghost')).toThrow(/profile not found/)
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
test('createProfile writes custom soul/identity content when provided', () => {
|
|
104
|
-
const profile = createProfile(env.db, env.paths, {
|
|
105
|
-
id: 'custom',
|
|
106
|
-
defaultModel: 'm',
|
|
107
|
-
templates: {
|
|
108
|
-
soul: '# my soul\nbe bold',
|
|
109
|
-
identity: '# my identity\nname: bazyl',
|
|
110
|
-
},
|
|
111
|
-
})
|
|
112
|
-
const loaded = loadProfile(env.db, profile.id)
|
|
113
|
-
expect(loaded.files.soul).toBe('# my soul\nbe bold')
|
|
114
|
-
expect(loaded.files.identity).toBe('# my identity\nname: bazyl')
|
|
115
|
-
// Bootstrap unspecified → default kicks in.
|
|
116
|
-
expect(loaded.files.bootstrap).toContain('BOOTSTRAP.md')
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
test('createProfile with templates.bootstrap = null skips bootstrap file', () => {
|
|
120
|
-
const profile = createProfile(env.db, env.paths, {
|
|
121
|
-
id: 'no-bootstrap',
|
|
122
|
-
defaultModel: 'm',
|
|
123
|
-
templates: { bootstrap: null },
|
|
124
|
-
})
|
|
125
|
-
expect(existsSync(join(profile.dir, 'BOOTSTRAP.md'))).toBe(false)
|
|
126
|
-
const loaded = loadProfile(env.db, 'no-bootstrap')
|
|
127
|
-
expect(loaded.files.bootstrap).toBeNull()
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
test('updateProfile changes DB, profile.json, and skills in lockstep', () => {
|
|
131
|
-
createProfile(env.db, env.paths, {
|
|
132
|
-
id: 'p',
|
|
133
|
-
defaultModel: 'old:model',
|
|
134
|
-
defaultSkills: ['a'],
|
|
135
|
-
})
|
|
136
|
-
const updated = updateProfile(env.db, env.paths, 'p', {
|
|
137
|
-
name: 'Renamed',
|
|
138
|
-
defaultModel: 'new:model',
|
|
139
|
-
defaultSkills: ['b', 'c'],
|
|
140
|
-
})
|
|
141
|
-
expect(updated.name).toBe('Renamed')
|
|
142
|
-
expect(updated.defaultModel).toBe('new:model')
|
|
143
|
-
|
|
144
|
-
const fromDb = profileRepo.get(env.db, 'p')
|
|
145
|
-
expect(fromDb?.defaultModel).toBe('new:model')
|
|
146
|
-
expect(profileRepo.getDefaultSkills(env.db, 'p')).toEqual(['b', 'c'])
|
|
147
|
-
|
|
148
|
-
const json = JSON.parse(readFileSync(join(env.paths.profileDir('p'), 'profile.json'), 'utf8'))
|
|
149
|
-
expect(json.defaultModel).toBe('new:model')
|
|
150
|
-
expect(json.name).toBe('Renamed')
|
|
151
|
-
expect(json.defaultSkills).toEqual(['b', 'c'])
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
test('updateProfile skillsMode persists', () => {
|
|
155
|
-
createProfile(env.db, env.paths, {
|
|
156
|
-
id: 'r',
|
|
157
|
-
defaultModel: 'm',
|
|
158
|
-
defaultSkills: ['x'],
|
|
159
|
-
})
|
|
160
|
-
expect(profileRepo.get(env.db, 'r')?.skillsMode).toBe('selected')
|
|
161
|
-
updateProfile(env.db, env.paths, 'r', { skillsMode: 'all' })
|
|
162
|
-
expect(profileRepo.get(env.db, 'r')?.skillsMode).toBe('all')
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
test('updateProfile throws on missing profile', () => {
|
|
166
|
-
expect(() => updateProfile(env.db, env.paths, 'ghost', { name: 'x' })).toThrow(
|
|
167
|
-
/profile not found/,
|
|
168
|
-
)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
test('listing profiles returns insertion order', () => {
|
|
172
|
-
createProfile(env.db, env.paths, {
|
|
173
|
-
id: 'a',
|
|
174
|
-
defaultModel: 'm',
|
|
175
|
-
})
|
|
176
|
-
createProfile(env.db, env.paths, {
|
|
177
|
-
id: 'b',
|
|
178
|
-
defaultModel: 'm',
|
|
179
|
-
})
|
|
180
|
-
const ids = profileRepo.list(env.db).map((p) => p.id)
|
|
181
|
-
expect(ids).toEqual(['a', 'b'])
|
|
182
|
-
})
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, expect, test } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
type BazilionDb,
|
|
4
|
-
openInMemoryDb,
|
|
5
|
-
providerModelRepo,
|
|
6
|
-
runMigrations,
|
|
7
|
-
} from '../../src/core/index.ts'
|
|
8
|
-
|
|
9
|
-
let db: BazilionDb
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
db = openInMemoryDb()
|
|
13
|
-
runMigrations(db)
|
|
14
|
-
})
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
db.close()
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
test('replace seeds, round-trips, and overwrites', () => {
|
|
20
|
-
expect(providerModelRepo.list(db, 'openai')).toEqual([])
|
|
21
|
-
|
|
22
|
-
providerModelRepo.replace(db, 'openai', ['gpt-4o', 'gpt-4o-mini', 'o1'])
|
|
23
|
-
expect(providerModelRepo.list(db, 'openai')).toEqual(['gpt-4o', 'gpt-4o-mini', 'o1'])
|
|
24
|
-
|
|
25
|
-
providerModelRepo.replace(db, 'openai', ['gpt-5'])
|
|
26
|
-
expect(providerModelRepo.list(db, 'openai')).toEqual(['gpt-5'])
|
|
27
|
-
|
|
28
|
-
providerModelRepo.replace(db, 'openai', [])
|
|
29
|
-
expect(providerModelRepo.list(db, 'openai')).toEqual([])
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('replace trims whitespace and drops empties + duplicates', () => {
|
|
33
|
-
providerModelRepo.replace(db, 'anthropic', [
|
|
34
|
-
' claude-opus-4-6 ',
|
|
35
|
-
'',
|
|
36
|
-
'claude-sonnet-4-6',
|
|
37
|
-
'claude-opus-4-6',
|
|
38
|
-
])
|
|
39
|
-
expect(providerModelRepo.list(db, 'anthropic')).toEqual(['claude-opus-4-6', 'claude-sonnet-4-6'])
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
test('listAll groups by provider and omits empties', () => {
|
|
43
|
-
providerModelRepo.replace(db, 'openai', ['gpt-4o'])
|
|
44
|
-
providerModelRepo.replace(db, 'anthropic', ['claude-opus-4-6', 'claude-sonnet-4-6'])
|
|
45
|
-
expect(providerModelRepo.listAll(db)).toEqual({
|
|
46
|
-
anthropic: ['claude-opus-4-6', 'claude-sonnet-4-6'],
|
|
47
|
-
openai: ['gpt-4o'],
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
test('remove drops a single entry', () => {
|
|
52
|
-
providerModelRepo.replace(db, 'groq', ['llama-3.3-70b', 'mixtral-8x7b'])
|
|
53
|
-
providerModelRepo.remove(db, 'groq', 'mixtral-8x7b')
|
|
54
|
-
expect(providerModelRepo.list(db, 'groq')).toEqual(['llama-3.3-70b'])
|
|
55
|
-
providerModelRepo.remove(db, 'groq', 'not-present')
|
|
56
|
-
expect(providerModelRepo.list(db, 'groq')).toEqual(['llama-3.3-70b'])
|
|
57
|
-
})
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, expect, test } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
type BazilionDb,
|
|
4
|
-
openInMemoryDb,
|
|
5
|
-
providerStateRepo,
|
|
6
|
-
runMigrations,
|
|
7
|
-
} from '../../src/core/index.ts'
|
|
8
|
-
|
|
9
|
-
let db: BazilionDb
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
db = openInMemoryDb()
|
|
13
|
-
runMigrations(db)
|
|
14
|
-
})
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
db.close()
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
test('absent rows default to disabled', () => {
|
|
20
|
-
expect(providerStateRepo.isEnabled(db, 'anthropic')).toBe(false)
|
|
21
|
-
expect(providerStateRepo.listEnabled(db).size).toBe(0)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
test('setEnabled inserts on first call, updates on subsequent', () => {
|
|
25
|
-
providerStateRepo.setEnabled(db, 'anthropic', true)
|
|
26
|
-
expect(providerStateRepo.isEnabled(db, 'anthropic')).toBe(true)
|
|
27
|
-
|
|
28
|
-
providerStateRepo.setEnabled(db, 'anthropic', false)
|
|
29
|
-
expect(providerStateRepo.isEnabled(db, 'anthropic')).toBe(false)
|
|
30
|
-
|
|
31
|
-
providerStateRepo.setEnabled(db, 'anthropic', true)
|
|
32
|
-
providerStateRepo.setEnabled(db, 'openai', true)
|
|
33
|
-
providerStateRepo.setEnabled(db, 'groq', false)
|
|
34
|
-
|
|
35
|
-
expect(providerStateRepo.listEnabled(db)).toEqual(new Set(['anthropic', 'openai']))
|
|
36
|
-
})
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, test } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
type BazilionDb,
|
|
4
|
-
openInMemoryDb,
|
|
5
|
-
runMigrations,
|
|
6
|
-
skillMetaRepo,
|
|
7
|
-
} from '../../src/core/index.ts'
|
|
8
|
-
|
|
9
|
-
let db: BazilionDb
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
db = openInMemoryDb()
|
|
12
|
-
runMigrations(db)
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
describe('skillMetaRepo', () => {
|
|
16
|
-
test('get returns null when no row exists', () => {
|
|
17
|
-
expect(skillMetaRepo.get(db, 'missing')).toBe(null)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
test('upsert creates a row with defaults', () => {
|
|
21
|
-
const m = skillMetaRepo.upsert(db, { name: 'alpha' })
|
|
22
|
-
expect(m).toEqual({ name: 'alpha', source: null, importedAt: null })
|
|
23
|
-
expect(skillMetaRepo.get(db, 'alpha')).toEqual(m)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
test('upsert updates source + importedAt without clobbering the name', () => {
|
|
27
|
-
skillMetaRepo.upsert(db, { name: 'alpha' })
|
|
28
|
-
const m = skillMetaRepo.upsert(db, { name: 'alpha', source: 'openclaw', importedAt: 123 })
|
|
29
|
-
expect(m).toEqual({ name: 'alpha', source: 'openclaw', importedAt: 123 })
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('listAll returns rows sorted by name', () => {
|
|
33
|
-
skillMetaRepo.upsert(db, { name: 'zebra' })
|
|
34
|
-
skillMetaRepo.upsert(db, { name: 'alpha' })
|
|
35
|
-
skillMetaRepo.upsert(db, { name: 'mid', source: 'path' })
|
|
36
|
-
const rows = skillMetaRepo.listAll(db)
|
|
37
|
-
expect(rows.map((r) => r.name)).toEqual(['alpha', 'mid', 'zebra'])
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test('remove drops the row', () => {
|
|
41
|
-
skillMetaRepo.upsert(db, { name: 'tmp' })
|
|
42
|
-
skillMetaRepo.remove(db, 'tmp')
|
|
43
|
-
expect(skillMetaRepo.get(db, 'tmp')).toBe(null)
|
|
44
|
-
})
|
|
45
|
-
})
|
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
import AdmZip from 'adm-zip'
|
|
4
|
-
import { afterEach, beforeEach, expect, test } from 'vitest'
|
|
5
|
-
import { spawnAgent } from '../../src/core/agent/spawn.ts'
|
|
6
|
-
import { createProfile } from '../../src/core/profile/create.ts'
|
|
7
|
-
import * as agentRepo from '../../src/core/repos/agents.ts'
|
|
8
|
-
import { discoverSkills } from '../../src/core/skills/discover.ts'
|
|
9
|
-
import { importSkills } from '../../src/core/skills/import.ts'
|
|
10
|
-
import { parseSkillContent, parseSkillFile } from '../../src/core/skills/parse.ts'
|
|
11
|
-
import { resolveAgentSkills } from '../../src/core/skills/resolve.ts'
|
|
12
|
-
import { makeTestEnv, type TestEnv } from './helpers.ts'
|
|
13
|
-
|
|
14
|
-
function skillMd(name: string, body = 'body'): string {
|
|
15
|
-
return `---
|
|
16
|
-
name: ${name}
|
|
17
|
-
description: A test skill called ${name}.
|
|
18
|
-
allowed-tools: Bash(${name}:*)
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
${body}
|
|
22
|
-
`
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
let env: TestEnv
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
env = makeTestEnv()
|
|
28
|
-
})
|
|
29
|
-
afterEach(() => env.cleanup())
|
|
30
|
-
|
|
31
|
-
function writeSkill(parent: string, name: string, body = 'skill body'): string {
|
|
32
|
-
const dir = join(parent, name)
|
|
33
|
-
mkdirSync(dir, { recursive: true })
|
|
34
|
-
writeFileSync(
|
|
35
|
-
join(dir, 'SKILL.md'),
|
|
36
|
-
`---
|
|
37
|
-
name: ${name}
|
|
38
|
-
description: A test skill called ${name}.
|
|
39
|
-
allowed-tools: Bash(${name}:*)
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
${body}
|
|
43
|
-
`,
|
|
44
|
-
)
|
|
45
|
-
return dir
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// --- parse ---
|
|
49
|
-
|
|
50
|
-
test('parseSkillContent reads valid frontmatter and body', () => {
|
|
51
|
-
const raw = `---
|
|
52
|
-
name: foo
|
|
53
|
-
description: Does foo things.
|
|
54
|
-
allowed-tools: Bash(foo:*)
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
# Foo
|
|
58
|
-
|
|
59
|
-
body here
|
|
60
|
-
`
|
|
61
|
-
const p = parseSkillContent(raw)
|
|
62
|
-
expect(p.frontmatter.name).toBe('foo')
|
|
63
|
-
expect(p.frontmatter.description).toBe('Does foo things.')
|
|
64
|
-
expect(p.frontmatter['allowed-tools']).toBe('Bash(foo:*)')
|
|
65
|
-
expect(p.body).toContain('body here')
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
test('parseSkillContent rejects missing frontmatter', () => {
|
|
69
|
-
expect(() => parseSkillContent('# no frontmatter')).toThrow(/missing YAML frontmatter/)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
test('parseSkillContent rejects missing required fields', () => {
|
|
73
|
-
expect(() =>
|
|
74
|
-
parseSkillContent(`---
|
|
75
|
-
description: no name
|
|
76
|
-
---
|
|
77
|
-
body
|
|
78
|
-
`),
|
|
79
|
-
).toThrow(/missing required "name"/)
|
|
80
|
-
|
|
81
|
-
expect(() =>
|
|
82
|
-
parseSkillContent(`---
|
|
83
|
-
name: nodesc
|
|
84
|
-
---
|
|
85
|
-
body
|
|
86
|
-
`),
|
|
87
|
-
).toThrow(/missing required "description"/)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test('parseSkillContent rejects invalid YAML', () => {
|
|
91
|
-
expect(() =>
|
|
92
|
-
parseSkillContent(`---
|
|
93
|
-
name: x
|
|
94
|
-
bad: indent
|
|
95
|
-
---
|
|
96
|
-
body
|
|
97
|
-
`),
|
|
98
|
-
).toThrow(/not valid YAML|missing required/)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
// --- discover ---
|
|
102
|
-
|
|
103
|
-
test('discoverSkills returns empty when skills dir is missing', () => {
|
|
104
|
-
// env creates the dir, so list it then unlink? Easier: use a fresh tmp
|
|
105
|
-
const all = discoverSkills(env.paths)
|
|
106
|
-
expect(all).toEqual([])
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
test('discoverSkills lists every dir containing SKILL.md, sorted', () => {
|
|
110
|
-
writeSkill(env.paths.skillsDir, 'b-skill')
|
|
111
|
-
writeSkill(env.paths.skillsDir, 'a-skill')
|
|
112
|
-
// Decoy: dir without SKILL.md
|
|
113
|
-
mkdirSync(join(env.paths.skillsDir, 'not-a-skill'), { recursive: true })
|
|
114
|
-
writeFileSync(join(env.paths.skillsDir, 'not-a-skill', 'README.md'), '')
|
|
115
|
-
|
|
116
|
-
const found = discoverSkills(env.paths)
|
|
117
|
-
expect(found.map((s) => s.name)).toEqual(['a-skill', 'b-skill'])
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
// --- import ---
|
|
121
|
-
|
|
122
|
-
test('importSkills imports all skills from a parent dir', () => {
|
|
123
|
-
const source = join(env.home, 'src-skills')
|
|
124
|
-
mkdirSync(source, { recursive: true })
|
|
125
|
-
writeSkill(source, 'one')
|
|
126
|
-
writeSkill(source, 'two')
|
|
127
|
-
|
|
128
|
-
const result = importSkills(env.paths, { source })
|
|
129
|
-
expect(result.imported.sort()).toEqual(['one', 'two'])
|
|
130
|
-
expect(result.skipped).toEqual([])
|
|
131
|
-
|
|
132
|
-
expect(existsSync(join(env.paths.skillsDir, 'one', 'SKILL.md'))).toBe(true)
|
|
133
|
-
expect(existsSync(join(env.paths.skillsDir, 'two', 'SKILL.md'))).toBe(true)
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
test('importSkills accepts a single-skill dir as source', () => {
|
|
137
|
-
const source = join(env.home, 'standalone-skill')
|
|
138
|
-
writeSkill(env.home, 'standalone-skill')
|
|
139
|
-
|
|
140
|
-
const result = importSkills(env.paths, { source })
|
|
141
|
-
expect(result.imported).toEqual(['standalone-skill'])
|
|
142
|
-
const installed = parseSkillFile(join(env.paths.skillsDir, 'standalone-skill', 'SKILL.md'))
|
|
143
|
-
expect(installed.frontmatter.name).toBe('standalone-skill')
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
test('importSkills skips existing without --force, overwrites with --force', () => {
|
|
147
|
-
const source = join(env.home, 'src')
|
|
148
|
-
mkdirSync(source, { recursive: true })
|
|
149
|
-
writeSkill(source, 'shared', 'first version')
|
|
150
|
-
|
|
151
|
-
importSkills(env.paths, { source })
|
|
152
|
-
// Modify source body
|
|
153
|
-
writeSkill(source, 'shared', 'second version')
|
|
154
|
-
|
|
155
|
-
let result = importSkills(env.paths, { source })
|
|
156
|
-
expect(result.imported).toEqual([])
|
|
157
|
-
expect(result.skipped[0]?.name).toBe('shared')
|
|
158
|
-
let body = readFileSync(join(env.paths.skillsDir, 'shared', 'SKILL.md'), 'utf8')
|
|
159
|
-
expect(body).toContain('first version')
|
|
160
|
-
|
|
161
|
-
result = importSkills(env.paths, { source, force: true })
|
|
162
|
-
expect(result.imported).toEqual(['shared'])
|
|
163
|
-
body = readFileSync(join(env.paths.skillsDir, 'shared', 'SKILL.md'), 'utf8')
|
|
164
|
-
expect(body).toContain('second version')
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
test('importSkills refuses to import a broken skill', () => {
|
|
168
|
-
const source = join(env.home, 'broken-src')
|
|
169
|
-
mkdirSync(join(source, 'broken'), { recursive: true })
|
|
170
|
-
writeFileSync(join(source, 'broken', 'SKILL.md'), '---\nname: broken\n---\nno description\n')
|
|
171
|
-
expect(() => importSkills(env.paths, { source })).toThrow(/missing required "description"/)
|
|
172
|
-
expect(existsSync(join(env.paths.skillsDir, 'broken'))).toBe(false)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
test('importSkills throws when nothing matches', () => {
|
|
176
|
-
const source = join(env.home, 'empty-src')
|
|
177
|
-
mkdirSync(source, { recursive: true })
|
|
178
|
-
expect(() => importSkills(env.paths, { source })).toThrow(/no skills found/)
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
// --- import from zip ---
|
|
182
|
-
|
|
183
|
-
test('importSkills imports every skill from a flat .zip archive', () => {
|
|
184
|
-
const zip = new AdmZip()
|
|
185
|
-
zip.addFile('alpha/SKILL.md', Buffer.from(skillMd('alpha')))
|
|
186
|
-
zip.addFile('alpha/script.sh', Buffer.from('echo alpha\n'))
|
|
187
|
-
zip.addFile('beta/SKILL.md', Buffer.from(skillMd('beta')))
|
|
188
|
-
const zipPath = join(env.home, 'skills.zip')
|
|
189
|
-
zip.writeZip(zipPath)
|
|
190
|
-
|
|
191
|
-
const result = importSkills(env.paths, { source: zipPath })
|
|
192
|
-
expect(result.imported.sort()).toEqual(['alpha', 'beta'])
|
|
193
|
-
expect(existsSync(join(env.paths.skillsDir, 'alpha', 'SKILL.md'))).toBe(true)
|
|
194
|
-
expect(readFileSync(join(env.paths.skillsDir, 'alpha', 'script.sh'), 'utf8')).toContain(
|
|
195
|
-
'echo alpha',
|
|
196
|
-
)
|
|
197
|
-
expect(existsSync(join(env.paths.skillsDir, 'beta', 'SKILL.md'))).toBe(true)
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
test('importSkills unwraps a single top-level folder inside the zip', () => {
|
|
201
|
-
const zip = new AdmZip()
|
|
202
|
-
zip.addFile('wrapper/skill-a/SKILL.md', Buffer.from(skillMd('skill-a')))
|
|
203
|
-
zip.addFile('wrapper/skill-b/SKILL.md', Buffer.from(skillMd('skill-b')))
|
|
204
|
-
const zipPath = join(env.home, 'wrapped.zip')
|
|
205
|
-
zip.writeZip(zipPath)
|
|
206
|
-
|
|
207
|
-
const result = importSkills(env.paths, { source: zipPath })
|
|
208
|
-
expect(result.imported.sort()).toEqual(['skill-a', 'skill-b'])
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
test('importSkills rejects a zip with a zip-slip entry', () => {
|
|
212
|
-
// AdmZip's addFile sanitizes leading `../`, so to craft a genuinely hostile
|
|
213
|
-
// archive we add a placeholder and rewrite the entryName post-hoc — this
|
|
214
|
-
// survives the serialize/reparse round-trip.
|
|
215
|
-
const zip = new AdmZip()
|
|
216
|
-
zip.addFile('placeholder', Buffer.from(skillMd('escape')))
|
|
217
|
-
const entries = zip.getEntries()
|
|
218
|
-
const first = entries[0]
|
|
219
|
-
if (!first) throw new Error('zip had no entries')
|
|
220
|
-
first.entryName = '../escape/SKILL.md'
|
|
221
|
-
const zipPath = join(env.home, 'evil.zip')
|
|
222
|
-
zip.writeZip(zipPath)
|
|
223
|
-
|
|
224
|
-
expect(() => importSkills(env.paths, { source: zipPath })).toThrow(/escapes extraction root/)
|
|
225
|
-
expect(existsSync(join(env.paths.skillsDir, 'escape'))).toBe(false)
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
test('importSkills rejects a non-zip file', () => {
|
|
229
|
-
const bogus = join(env.home, 'not-a-zip.txt')
|
|
230
|
-
writeFileSync(bogus, 'hello')
|
|
231
|
-
expect(() => importSkills(env.paths, { source: bogus })).toThrow(/must be a .zip archive/)
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
test('importSkills rejects a zip with zero skills', () => {
|
|
235
|
-
const zip = new AdmZip()
|
|
236
|
-
zip.addFile('readme.md', Buffer.from('no skills here'))
|
|
237
|
-
const zipPath = join(env.home, 'empty.zip')
|
|
238
|
-
zip.writeZip(zipPath)
|
|
239
|
-
|
|
240
|
-
expect(() => importSkills(env.paths, { source: zipPath })).toThrow(/no skills found/)
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
// --- resolve ---
|
|
244
|
-
|
|
245
|
-
test('resolveAgentSkills returns parsed skills attached to an agent', () => {
|
|
246
|
-
writeSkill(env.paths.skillsDir, 'web-search')
|
|
247
|
-
writeSkill(env.paths.skillsDir, 'note-taking')
|
|
248
|
-
|
|
249
|
-
createProfile(env.db, env.paths, {
|
|
250
|
-
id: 'p',
|
|
251
|
-
defaultModel: 'm',
|
|
252
|
-
defaultSkills: ['web-search', 'note-taking'],
|
|
253
|
-
})
|
|
254
|
-
const agent = spawnAgent(env.db, env.paths, { profileId: 'p', groupId: env.groupId })
|
|
255
|
-
|
|
256
|
-
const set = resolveAgentSkills(env.db, env.paths, agent.id)
|
|
257
|
-
expect(set.resolved.map((s) => s.name).sort()).toEqual(['note-taking', 'web-search'])
|
|
258
|
-
expect(set.missing).toEqual([])
|
|
259
|
-
expect(set.resolved[0]?.parsed.frontmatter.description).toContain('test skill')
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
test('resolveAgentSkills reports skills attached but not installed', () => {
|
|
263
|
-
createProfile(env.db, env.paths, { id: 'p', defaultModel: 'm' })
|
|
264
|
-
const agent = spawnAgent(env.db, env.paths, { profileId: 'p', groupId: env.groupId })
|
|
265
|
-
agentRepo.attachSkill(env.db, agent.id, 'ghost')
|
|
266
|
-
|
|
267
|
-
const set = resolveAgentSkills(env.db, env.paths, agent.id)
|
|
268
|
-
expect(set.resolved).toEqual([])
|
|
269
|
-
expect(set.missing).toHaveLength(1)
|
|
270
|
-
expect(set.missing[0]?.name).toBe('ghost')
|
|
271
|
-
})
|