@dot-agent/cli 1.0.0 → 1.0.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/src/cli.ts DELETED
@@ -1,97 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { init, pack, unpack, run } from './index.js'
4
-
5
- const args = process.argv.slice(2)
6
- const command = args[0]
7
-
8
- function formatError(msg: string) {
9
- const lines = msg.split('\n')
10
- console.error(`\x1b[31m✗\x1b[0m ${lines[0]}`)
11
- if (lines.length > 1) {
12
- lines.slice(1).forEach(line => console.error(` ${line}`))
13
- }
14
- }
15
-
16
- function formatSuccess(msg: string) {
17
- console.log(`\x1b[32m✓\x1b[0m ${msg}`)
18
- }
19
-
20
- function formatWarning(msg: string) {
21
- console.warn(`\x1b[33m⚠\x1b[0m ${msg}`)
22
- }
23
-
24
- async function main() {
25
- try {
26
- if (command === 'init') {
27
- const options: any = {}
28
- for (let i = 1; i < args.length; i++) {
29
- if (args[i] === '--name' && i + 1 < args.length) options.name = args[++i]
30
- if (args[i] === '--domain' && i + 1 < args.length) options.domain = args[++i]
31
- if (args[i] === '--dir' && i + 1 < args.length) options.dir = args[++i]
32
- }
33
-
34
- const result = await init(options)
35
- formatSuccess(`Scaffolded agent project in ${result.dir}`)
36
- console.log(` Files: ${result.files.join(', ')}`)
37
- } else if (command === 'pack') {
38
- const options: any = {}
39
- for (let i = 1; i < args.length; i++) {
40
- if (args[i] === '--dir' && i + 1 < args.length) options.dir = args[++i]
41
- if (args[i] === '--out' && i + 1 < args.length) options.out = args[++i]
42
- if (args[i] === '--commit' && i + 1 < args.length) options.commit = args[++i]
43
- if (args[i] === '--version' && i + 1 < args.length) options.version = args[++i]
44
- }
45
-
46
- const result = await pack(options)
47
- formatSuccess(`Packed → ${result.path}`)
48
- console.log(` ID: ${result.id}`)
49
- if (result.warnings.length > 0) {
50
- console.log(` Warnings: ${result.warnings.length}`)
51
- result.warnings.forEach(w => {
52
- formatWarning(`${w.file}:${w.line}:${w.col} ${w.code} ${w.message}`)
53
- })
54
- }
55
- } else if (command === 'unpack') {
56
- const file = args[1]
57
- if (!file) {
58
- formatError('Usage: dot-agent unpack <file.agent> [--out <dir>] [--force]')
59
- process.exit(1)
60
- }
61
-
62
- const options: any = { file }
63
- for (let i = 2; i < args.length; i++) {
64
- if (args[i] === '--out' && i + 1 < args.length) options.out = args[++i]
65
- if (args[i] === '--force') options.force = true
66
- }
67
-
68
- const result = await unpack(options)
69
- formatSuccess(`Unpacked to ${result.dir}`)
70
- console.log(` ID: ${result.id}`)
71
- console.log(` Files: ${result.files.length}`)
72
- } else if (command === 'run') {
73
- const source = args[1]
74
- if (!source) {
75
- formatError('Usage: dot-agent run <file.agent | dir>')
76
- process.exit(1)
77
- }
78
-
79
- const context = await run({ source })
80
- formatSuccess(`Agent loaded: ${context.id}`)
81
- } else {
82
- console.log(`dot-agent CLI v1.0.0
83
-
84
- Usage:
85
- dot-agent init [--name <name>] [--domain <domain>] [--dir <dir>]
86
- dot-agent pack [--dir <dir>] [--out <file>] [--commit <hash>] [--version <tag>]
87
- dot-agent unpack <file.agent> [--out <dir>] [--force]
88
- dot-agent run <file.agent | dir>
89
- `)
90
- }
91
- } catch (err: any) {
92
- formatError(err.message)
93
- process.exit(1)
94
- }
95
- }
96
-
97
- main()
@@ -1,103 +0,0 @@
1
- import { mkdir, writeFile, stat } from 'fs/promises'
2
- import { join, basename } from 'path'
3
- import { InitOptions, InitResult } from '../types.js'
4
-
5
- const AGENT_DESCRIPTION_TEMPLATE = (name: string, domain: string) => `agent ${name}
6
- domain ${domain}
7
- license Apache-2.0
8
-
9
- description
10
- Describe what this agent does.
11
-
12
- behavior agent.behavior
13
-
14
- capabilities
15
- ActionName "Describe this capability"
16
- `
17
-
18
- const AGENT_BEHAVIOR_TEMPLATE = `state init
19
- transition to responsive
20
-
21
- state responsive
22
- goal "Help the user with their task."
23
- interact
24
- on intent "start" transition to responsive
25
- `
26
-
27
- const SOUL_TEMPLATE = (name: string) => `# ${name} — Persona
28
-
29
- ## Voice and Tone
30
-
31
- Describe the agent's voice, personality, and communication style.
32
- `
33
-
34
- const README_TEMPLATE = (name: string) => `# ${name}
35
-
36
- Brief description of what this agent does.
37
-
38
- ## Usage
39
-
40
- Example of how to use this agent.
41
- `
42
-
43
- const LICENSE = `Apache License
44
- Version 2.0, January 2004
45
-
46
- http://www.apache.org/licenses/
47
-
48
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
49
- `
50
-
51
- export async function init(options: InitOptions = {}): Promise<InitResult> {
52
- const dir = options.dir || process.cwd()
53
- const name = options.name || basename(dir)
54
- const domain = options.domain || 'example.com'
55
-
56
- try {
57
- await stat(dir)
58
- } catch {
59
- await mkdir(dir, { recursive: true })
60
- }
61
-
62
- const agentDescriptionPath = join(dir, 'agent.description')
63
- try {
64
- await stat(agentDescriptionPath)
65
- throw new Error(`agent.description already exists at ${agentDescriptionPath}`)
66
- } catch (err: any) {
67
- if (err.code !== 'ENOENT') throw err
68
- }
69
-
70
- const files: string[] = []
71
-
72
- await writeFile(agentDescriptionPath, AGENT_DESCRIPTION_TEMPLATE(name, domain))
73
- files.push('agent.description')
74
-
75
- await writeFile(join(dir, 'agent.behavior'), AGENT_BEHAVIOR_TEMPLATE)
76
- files.push('agent.behavior')
77
-
78
- await writeFile(join(dir, 'SOUL.md'), SOUL_TEMPLATE(name))
79
- files.push('SOUL.md')
80
-
81
- await writeFile(join(dir, 'README.md'), README_TEMPLATE(name))
82
- files.push('README.md')
83
-
84
- await writeFile(join(dir, 'LICENSE'), LICENSE)
85
- files.push('LICENSE')
86
-
87
- await mkdir(join(dir, 'behaviors'), { recursive: true })
88
- await writeFile(join(dir, 'behaviors', '.gitkeep'), '')
89
- files.push('behaviors/.gitkeep')
90
-
91
- await mkdir(join(dir, 'guides'), { recursive: true })
92
- await writeFile(join(dir, 'guides', '.gitkeep'), '')
93
- files.push('guides/.gitkeep')
94
-
95
- await mkdir(join(dir, 'knowledge'), { recursive: true })
96
- await writeFile(join(dir, 'knowledge', '.gitkeep'), '')
97
- files.push('knowledge/.gitkeep')
98
-
99
- await writeFile(join(dir, 'AGENTS.md'), '')
100
- files.push('AGENTS.md')
101
-
102
- return { dir, files }
103
- }
@@ -1,216 +0,0 @@
1
- import { readFile, stat, readdir } from 'fs/promises'
2
- import { join, basename, dirname } from 'path'
3
- import { execSync } from 'child_process'
4
- import { createHash } from 'crypto'
5
- import JSZip from 'jszip'
6
- import { PackOptions, PackResult, LintMessage } from '../types.js'
7
- import { createLinter } from '../core/lint.js'
8
- import { buildId } from '../core/id.js'
9
- import { buildAboutme, aboutmeToJson } from '../core/envelope.js'
10
- import { buildTypesJson } from '../core/types.js'
11
- import { writeZip } from '../core/zip.js'
12
-
13
- function gitDescribeTags(): string | null {
14
- try {
15
- return execSync('git describe --tags --abbrev=0', {
16
- stdio: 'pipe',
17
- encoding: 'utf-8',
18
- }).trim()
19
- } catch {
20
- return null
21
- }
22
- }
23
-
24
- function gitRevParseShort(): string | null {
25
- try {
26
- return execSync('git rev-parse --short HEAD', {
27
- stdio: 'pipe',
28
- encoding: 'utf-8',
29
- }).trim()
30
- } catch {
31
- return null
32
- }
33
- }
34
-
35
- async function resolveVersion(explicit?: string): Promise<string> {
36
- if (explicit) return explicit
37
-
38
- const gitTag = gitDescribeTags()
39
- if (gitTag) return gitTag
40
-
41
- return 'v1.0.0'
42
- }
43
-
44
- async function resolveCommit(explicit?: string): Promise<string | undefined> {
45
- if (explicit) return explicit
46
- return gitRevParseShort() || undefined
47
- }
48
-
49
- async function collectFiles(dir: string): Promise<Map<string, string>> {
50
- const files = new Map<string, string>()
51
-
52
- async function walk(subdir: string, prefix: string = '') {
53
- try {
54
- const entries = await readdir(subdir)
55
- for (const entry of entries) {
56
- if (entry === '.gitkeep' || entry.startsWith('.')) continue
57
-
58
- const fullPath = join(subdir, entry)
59
- const stats = await stat(fullPath)
60
- const relativePath = prefix ? `${prefix}/${entry}` : entry
61
-
62
- if (stats.isDirectory()) {
63
- await walk(fullPath, relativePath)
64
- } else {
65
- const content = await readFile(fullPath, 'utf-8')
66
- files.set(relativePath, content)
67
- }
68
- }
69
- } catch {
70
- // directory doesn't exist or can't be read
71
- }
72
- }
73
-
74
- const description = await readFile(join(dir, 'agent.description'), 'utf-8')
75
- files.set('agent.description', description)
76
-
77
- const behavior = await readFile(join(dir, 'agent.behavior'), 'utf-8')
78
- files.set('agent.behavior', behavior)
79
-
80
- try {
81
- const soul = await readFile(join(dir, 'SOUL.md'), 'utf-8')
82
- files.set('SOUL.md', soul)
83
- } catch {
84
- // optional
85
- }
86
-
87
- await walk(join(dir, 'behaviors'), 'behaviors')
88
- await walk(join(dir, 'guides'), 'guides')
89
- await walk(join(dir, 'knowledge'), 'knowledge')
90
-
91
- return files
92
- }
93
-
94
- function parseDescription(text: string): any {
95
- const domain = text.match(/domain\s+([^\n]+)/)?.[1] || ''
96
- const name = text.match(/agent\s+([^\n]+)/)?.[1] || ''
97
- const description = text.match(/description\s+(.+?)(?=\n\n|\n[a-z])/s)?.[1] || ''
98
-
99
- return {
100
- domain: domain.trim(),
101
- name: name.trim(),
102
- description: description.trim(),
103
- capabilities: [],
104
- }
105
- }
106
-
107
- export async function pack(options: PackOptions = {}): Promise<PackResult> {
108
- const dir = options.dir || process.cwd()
109
- const outPath = options.out || join(dir, `${basename(dir)}.agent`)
110
-
111
- // Read files
112
- let descriptionText: string
113
- let behaviorText: string
114
-
115
- try {
116
- descriptionText = await readFile(join(dir, 'agent.description'), 'utf-8')
117
- } catch {
118
- throw new Error('E003: File agent.description not found')
119
- }
120
-
121
- try {
122
- behaviorText = await readFile(join(dir, 'agent.behavior'), 'utf-8')
123
- } catch {
124
- throw new Error('E007: File agent.behavior not found')
125
- }
126
-
127
- // Lint
128
- const linter = await createLinter()
129
- const descriptionMessages = await linter.lintDescription(descriptionText)
130
- const behaviorMessages = await linter.lintBehavior(behaviorText)
131
-
132
- const allMessages = [...descriptionMessages, ...behaviorMessages]
133
- const errors = allMessages.filter(m => m.severity === 'error')
134
- const warnings = allMessages.filter(m => m.severity === 'warning')
135
-
136
- if (errors.length > 0) {
137
- throw new Error(`Lint failed: ${errors.map(e => `${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join('\n')}`)
138
- }
139
-
140
- // Parse description
141
- const description = parseDescription(descriptionText)
142
-
143
- // Resolve version & commit
144
- const version = await resolveVersion(options.version)
145
- const commit = await resolveCommit(options.commit)
146
-
147
- // Collect all files
148
- const allFiles = await collectFiles(dir)
149
-
150
- // Build ID
151
- const contentForHash = Array.from(allFiles.values()).join('')
152
- const digest = createHash('sha256').update(contentForHash).digest('hex').substring(0, 8)
153
- const id = buildId({
154
- namespace: description.domain,
155
- name: description.name,
156
- version,
157
- digest,
158
- })
159
-
160
- // Build aboutme
161
- const aboutme = buildAboutme({
162
- id,
163
- name: description.name,
164
- description: description.description,
165
- version,
166
- domain: description.domain,
167
- license: 'Apache-2.0',
168
- persona: 'SOUL.md',
169
- compiler: 'dot-agent/1.0.0',
170
- commit,
171
- skills: [],
172
- requires: [],
173
- integrity: {
174
- sha256: createHash('sha256').update(contentForHash).digest('hex'),
175
- files: '.agent/files.json',
176
- },
177
- })
178
-
179
- // Build ZIP
180
- const zip = new JSZip()
181
-
182
- zip.folder('.agent')!.file('aboutme.json', aboutmeToJson(aboutme))
183
-
184
- // Build files.json
185
- const filesJson = {
186
- description: 'agent.description',
187
- behavior: 'agent.behavior',
188
- behaviors: Array.from(allFiles.keys())
189
- .filter(f => f.startsWith('behaviors/') && f !== 'behaviors/.gitkeep')
190
- .map(f => f),
191
- guides: Array.from(allFiles.keys())
192
- .filter(f => f.startsWith('guides/') && f !== 'guides/.gitkeep')
193
- .map(f => f),
194
- knowledge: Array.from(allFiles.keys())
195
- .filter(f => f.startsWith('knowledge/') && f !== 'knowledge/.gitkeep')
196
- .map(f => f),
197
- }
198
-
199
- zip.folder('.agent')!.file('files.json', JSON.stringify(filesJson, null, 2))
200
-
201
- // Add all files to ZIP root
202
- for (const [path, content] of allFiles) {
203
- if (path !== 'behaviors/.gitkeep' && path !== 'guides/.gitkeep' && path !== 'knowledge/.gitkeep') {
204
- zip.file(path, content)
205
- }
206
- }
207
-
208
- // Write ZIP
209
- await writeZip(zip, outPath)
210
-
211
- return {
212
- path: outPath,
213
- id,
214
- warnings,
215
- }
216
- }
@@ -1,194 +0,0 @@
1
- import { EventEmitter } from 'events'
2
- import { readFile, stat } from 'fs/promises'
3
- import { AgentDSLKernel, init as initKernel } from '@dot-agent/kernel-dsl'
4
- import { RunOptions, AgentContext, FileEntry } from '../types.js'
5
- import { readZip, validateZipBomb, validateMagicBytes, extractFiles } from '../core/zip.js'
6
- import { parseAboutme } from '../core/envelope.js'
7
-
8
- function isZipFile(source: string): boolean {
9
- return source.endsWith('.agent')
10
- }
11
-
12
- export async function run(options: RunOptions): Promise<AgentContext> {
13
- const { source } = options
14
- const context = new EventEmitter() as any as AgentContext
15
-
16
- try {
17
- context.emit('progress', { step: 'opening', pct: 0 })
18
-
19
- let descriptionText: string
20
- let behaviorText: string
21
- let aboutme: any = null
22
- let id: string
23
-
24
- if (isZipFile(source)) {
25
- // Load from .agent ZIP
26
- await validateMagicBytes(source)
27
- await validateZipBomb(source)
28
-
29
- const zip = await readZip(source)
30
-
31
- const aboutmeFile = zip.file('.agent/aboutme.json')
32
- if (!aboutmeFile) {
33
- throw new Error('Missing .agent/aboutme.json')
34
- }
35
-
36
- const aboutmeText = await aboutmeFile.async('text')
37
- aboutme = parseAboutme(JSON.parse(aboutmeText))
38
- id = aboutme.id
39
-
40
- const descFile = zip.file('agent.description')
41
- if (!descFile) throw new Error('Missing agent.description')
42
- descriptionText = await descFile.async('text')
43
-
44
- const behavFile = zip.file('agent.behavior')
45
- if (!behavFile) throw new Error('Missing agent.behavior')
46
- behaviorText = await behavFile.async('text')
47
-
48
- context.emit('progress', { step: 'parsing', pct: 30 })
49
-
50
- // Load kernel-dsl
51
- let kernel: any
52
- try {
53
- await initKernel()
54
- kernel = new AgentDSLKernel()
55
- kernel.load_behavior(behaviorText)
56
- } catch (err: any) {
57
- // Kernel failed - create a stub
58
- kernel = {
59
- get_current_state: () => 'init',
60
- get_graph: () => ({}),
61
- get_memory: () => [],
62
- get_valid_intents: () => [],
63
- load_behavior: () => {},
64
- observe: () => {},
65
- send_complete: () => {},
66
- send_event: () => {},
67
- send_failed: () => {},
68
- send_fallback: () => {},
69
- send_intent: () => {},
70
- send_offtopic: () => {},
71
- tick_prompt: () => {},
72
- free: () => {},
73
- }
74
- }
75
-
76
- context.emit('progress', { step: 'loading-files', pct: 60 })
77
-
78
- // Load files
79
- const soulFile = zip.file('SOUL.md')
80
- const soul = soulFile ? await soulFile.async('text') : undefined
81
-
82
- const allFiles = await extractFiles(zip)
83
- const guides: FileEntry[] = []
84
- const knowledge: FileEntry[] = []
85
- const behaviors: FileEntry[] = []
86
-
87
- for (const [path, content] of allFiles) {
88
- if (path.startsWith('guides/') && path !== 'guides/.gitkeep') {
89
- guides.push({ path, content })
90
- } else if (path.startsWith('knowledge/') && path !== 'knowledge/.gitkeep') {
91
- knowledge.push({ path, content })
92
- } else if (path.startsWith('behaviors/') && path !== 'behaviors/.gitkeep') {
93
- behaviors.push({ path, content })
94
- }
95
- }
96
-
97
- context.id = id
98
- context.description = { domain: 'example.com', name: 'Agent' }
99
- context.behavior = {}
100
- context.kernel = kernel
101
- context.files = { soul, guides, knowledge, behaviors }
102
- context.aboutme = aboutme
103
- } else {
104
- // Load from directory
105
- const descPath = `${source}/agent.description`
106
- const behavPath = `${source}/agent.behavior`
107
-
108
- try {
109
- descriptionText = await readFile(descPath, 'utf-8')
110
- } catch {
111
- throw new Error(`Missing agent.description at ${descPath}`)
112
- }
113
-
114
- try {
115
- behaviorText = await readFile(behavPath, 'utf-8')
116
- } catch {
117
- throw new Error(`Missing agent.behavior at ${behavPath}`)
118
- }
119
-
120
- context.emit('progress', { step: 'parsing', pct: 30 })
121
-
122
- // Load kernel-dsl
123
- let kernel: any
124
- try {
125
- await initKernel()
126
- kernel = new AgentDSLKernel()
127
- kernel.load_behavior(behaviorText)
128
- } catch (err: any) {
129
- // Kernel failed - create a stub
130
- kernel = {
131
- get_current_state: () => 'init',
132
- get_graph: () => ({}),
133
- get_memory: () => [],
134
- get_valid_intents: () => [],
135
- load_behavior: () => {},
136
- observe: () => {},
137
- send_complete: () => {},
138
- send_event: () => {},
139
- send_failed: () => {},
140
- send_fallback: () => {},
141
- send_intent: () => {},
142
- send_offtopic: () => {},
143
- tick_prompt: () => {},
144
- free: () => {},
145
- }
146
- }
147
-
148
- context.emit('progress', { step: 'loading-files', pct: 60 })
149
-
150
- id = 'local/agent:v1.0~unknown'
151
-
152
- try {
153
- const soul = await readFile(`${source}/SOUL.md`, 'utf-8')
154
- context.files = {
155
- soul,
156
- guides: [],
157
- knowledge: [],
158
- behaviors: [],
159
- }
160
- } catch {
161
- context.files = { guides: [], knowledge: [], behaviors: [] }
162
- }
163
-
164
- const defaultAboutme: any = {
165
- schemaVersion: 'dot-agent/1.0',
166
- id,
167
- name: 'Agent',
168
- description: '',
169
- version: 'v1.0',
170
- domain: 'local',
171
- license: 'Apache-2.0',
172
- persona: 'SOUL.md',
173
- compiler: 'dot-agent/1.0.0',
174
- skills: [],
175
- requires: [],
176
- integrity: { sha256: '', files: '' },
177
- }
178
-
179
- context.id = id
180
- context.description = { domain: 'example.com', name: 'Agent' }
181
- context.behavior = {}
182
- context.kernel = kernel
183
- context.aboutme = defaultAboutme
184
- }
185
-
186
- context.emit('progress', { step: 'ready', pct: 100 })
187
- context.emit('ready', context)
188
-
189
- return context
190
- } catch (err) {
191
- context.emit('error', err)
192
- throw err
193
- }
194
- }
@@ -1,65 +0,0 @@
1
- import { mkdir, writeFile, stat } from 'fs/promises'
2
- import { join } from 'path'
3
- import { createHash } from 'crypto'
4
- import { UnpackOptions, UnpackResult } from '../types.js'
5
- import { readZip, validateZipBomb, validateMagicBytes, extractFiles } from '../core/zip.js'
6
- import { parseAboutme } from '../core/envelope.js'
7
-
8
- export async function unpack(options: UnpackOptions): Promise<UnpackResult> {
9
- const { file, out, force = false } = options
10
-
11
- // Validate magic bytes
12
- await validateMagicBytes(file)
13
-
14
- // Validate ZIP bomb
15
- await validateZipBomb(file)
16
-
17
- // Load ZIP
18
- const zip = await readZip(file)
19
-
20
- // Read aboutme.json
21
- const aboutmeFile = zip.file('.agent/aboutme.json')
22
- if (!aboutmeFile) {
23
- throw new Error('Missing .agent/aboutme.json in ZIP')
24
- }
25
-
26
- const aboutmeText = await aboutmeFile.async('text')
27
- const aboutme = parseAboutme(JSON.parse(aboutmeText))
28
-
29
- // Extract output directory
30
- const outDir = out || `./${aboutme.name}`
31
-
32
- // Check if directory exists
33
- try {
34
- await stat(outDir)
35
- if (!force) {
36
- throw new Error(`Output directory already exists: ${outDir}. Use --force to overwrite.`)
37
- }
38
- } catch (err: any) {
39
- if (err.code !== 'ENOENT') throw err
40
- }
41
-
42
- // Extract all files from root
43
- const files = await extractFiles(zip)
44
- await mkdir(outDir, { recursive: true })
45
-
46
- const extractedFiles: string[] = []
47
-
48
- for (const [path, content] of files) {
49
- if (path.startsWith('.agent/')) continue
50
-
51
- const fullPath = join(outDir, path)
52
- const dir = fullPath.substring(0, fullPath.lastIndexOf('/'))
53
-
54
- await mkdir(dir, { recursive: true })
55
- await writeFile(fullPath, content)
56
- extractedFiles.push(path)
57
- }
58
-
59
- return {
60
- dir: outDir,
61
- id: aboutme.id,
62
- files: extractedFiles,
63
- aboutme,
64
- }
65
- }