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