+20
.gitignore
+20
.gitignore
+48
README.md
+48
README.md
···
1
+
# ATP Autonomy Declaration
2
+
3
+
A Deno project for managing and sharing an ATProtocol lexicon with accompanying tooling.
4
+
5
+
## Project Structure
6
+
7
+
- `lexicon/` - ATProtocol lexicon definition (JSON)
8
+
- `mod.ts` - Main module exporting the lexicon (published to JSR)
9
+
- `client/` - Single-page web form for users to add records to their PDS
10
+
- `scripts/` - Utility scripts for publishing and managing the lexicon
11
+
12
+
## Usage
13
+
14
+
### Development
15
+
16
+
Run the client locally:
17
+
```bash
18
+
deno task dev
19
+
```
20
+
21
+
### Publish to JSR
22
+
23
+
Publish the lexicon package to JSR:
24
+
```bash
25
+
deno publish
26
+
```
27
+
28
+
Or with dry-run to check first:
29
+
```bash
30
+
deno publish --dry-run
31
+
```
32
+
33
+
### Publish Lexicon to PDS
34
+
35
+
Publish the lexicon to the owning PDS:
36
+
```bash
37
+
deno task publish:lexicon
38
+
```
39
+
40
+
Set up your credentials first:
41
+
```bash
42
+
export ATP_HANDLE=your-handle.bsky.social
43
+
export ATP_PASSWORD=your-app-password
44
+
```
45
+
46
+
## Requirements
47
+
48
+
- Deno 2.0+
+115
client/app.js
+115
client/app.js
···
1
+
/**
2
+
* Client-side application for submitting autonomy declarations
3
+
*/
4
+
5
+
import { BskyAgent } from "https://esm.sh/@atproto/api@0.13.0";
6
+
7
+
const form = document.getElementById("declaration-form");
8
+
const submitBtn = document.getElementById("submit-btn");
9
+
const resultDiv = document.getElementById("result");
10
+
11
+
form.addEventListener("submit", async (e) => {
12
+
e.preventDefault();
13
+
14
+
// Get form values
15
+
const handle = document.getElementById("handle").value.trim();
16
+
const password = document.getElementById("password").value;
17
+
18
+
const automationLevel = document.getElementById("automationLevel").value;
19
+
const usesGenerativeAI = document.getElementById("usesGenerativeAI").checked;
20
+
const description = document.getElementById("description").value.trim();
21
+
22
+
const responsibleType = document.getElementById("responsibleType").value;
23
+
const responsibleName = document.getElementById("responsibleName").value.trim();
24
+
const responsibleContact = document.getElementById("responsibleContact").value.trim();
25
+
const responsibleDid = document.getElementById("responsibleDid").value.trim();
26
+
27
+
const disclosureUrl = document.getElementById("disclosureUrl").value.trim();
28
+
const externalServicesText = document.getElementById("externalServices").value.trim();
29
+
30
+
// Disable form during submission
31
+
submitBtn.disabled = true;
32
+
submitBtn.textContent = "Submitting...";
33
+
resultDiv.className = "result hidden";
34
+
35
+
try {
36
+
// Create agent and login
37
+
const agent = new BskyAgent({
38
+
service: "https://bsky.social"
39
+
});
40
+
41
+
await agent.login({
42
+
identifier: handle,
43
+
password: password
44
+
});
45
+
46
+
// Build the record
47
+
const record = {
48
+
$type: "studio.voyager.account.autonomy",
49
+
createdAt: new Date().toISOString()
50
+
};
51
+
52
+
// Add optional fields
53
+
if (automationLevel) {
54
+
record.automationLevel = automationLevel;
55
+
}
56
+
57
+
if (usesGenerativeAI) {
58
+
record.usesGenerativeAI = usesGenerativeAI;
59
+
}
60
+
61
+
if (description) {
62
+
record.description = description;
63
+
}
64
+
65
+
// Build responsible party object if any fields are filled
66
+
if (responsibleType || responsibleName || responsibleContact || responsibleDid) {
67
+
record.responsibleParty = {};
68
+
if (responsibleType) record.responsibleParty.type = responsibleType;
69
+
if (responsibleName) record.responsibleParty.name = responsibleName;
70
+
if (responsibleContact) record.responsibleParty.contact = responsibleContact;
71
+
if (responsibleDid) record.responsibleParty.did = responsibleDid;
72
+
}
73
+
74
+
if (disclosureUrl) {
75
+
record.disclosureUrl = disclosureUrl;
76
+
}
77
+
78
+
// Parse external services (one per line)
79
+
if (externalServicesText) {
80
+
const services = externalServicesText
81
+
.split('\n')
82
+
.map(s => s.trim())
83
+
.filter(s => s.length > 0)
84
+
.slice(0, 20); // Max 20 services
85
+
if (services.length > 0) {
86
+
record.externalServices = services;
87
+
}
88
+
}
89
+
90
+
// Submit to PDS
91
+
const response = await agent.api.com.atproto.repo.createRecord({
92
+
repo: agent.session?.did || "",
93
+
collection: "studio.voyager.account.autonomy",
94
+
record: record
95
+
});
96
+
97
+
// Show success
98
+
resultDiv.className = "result success";
99
+
resultDiv.textContent = `โ
Declaration submitted successfully! URI: ${response.data.uri}`;
100
+
101
+
// Clear form
102
+
form.reset();
103
+
104
+
} catch (error) {
105
+
// Show error
106
+
resultDiv.className = "result error";
107
+
resultDiv.textContent = `โ Error: ${error.message}`;
108
+
console.error("Submission error:", error);
109
+
} finally {
110
+
// Re-enable form
111
+
submitBtn.disabled = false;
112
+
submitBtn.textContent = "Submit Declaration";
113
+
resultDiv.classList.remove("hidden");
114
+
}
115
+
});
+153
client/index.html
+153
client/index.html
···
1
+
<!DOCTYPE html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="UTF-8">
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+
<title>Autonomy Declaration - ATProtocol</title>
7
+
<link rel="stylesheet" href="styles.css">
8
+
</head>
9
+
<body>
10
+
<div class="container">
11
+
<header>
12
+
<h1>Autonomy Declaration</h1>
13
+
<p>Declare automation and AI usage for transparency and accountability</p>
14
+
</header>
15
+
16
+
<main>
17
+
<form id="declaration-form">
18
+
<div class="form-group">
19
+
<label for="handle">Your Handle</label>
20
+
<input
21
+
type="text"
22
+
id="handle"
23
+
name="handle"
24
+
placeholder="username.bsky.social"
25
+
required
26
+
/>
27
+
<small>Your Bluesky handle or DID</small>
28
+
</div>
29
+
30
+
<div class="form-group">
31
+
<label for="password">App Password</label>
32
+
<input
33
+
type="password"
34
+
id="password"
35
+
name="password"
36
+
placeholder="xxxx-xxxx-xxxx-xxxx"
37
+
required
38
+
/>
39
+
<small>Generate an app password in Bluesky settings</small>
40
+
</div>
41
+
42
+
<div class="form-group">
43
+
<label for="automationLevel">Automation Level</label>
44
+
<select id="automationLevel" name="automationLevel">
45
+
<option value="">Select level (optional)</option>
46
+
<option value="human">Human - No automation</option>
47
+
<option value="assisted">Assisted - Tools help but human decides</option>
48
+
<option value="collaborative">Collaborative - Human and AI work together</option>
49
+
<option value="automated">Automated - Primarily AI-driven</option>
50
+
</select>
51
+
<small>Level of automation in account management and content creation</small>
52
+
</div>
53
+
54
+
<div class="form-group">
55
+
<label>
56
+
<input type="checkbox" id="usesGenerativeAI" name="usesGenerativeAI" />
57
+
Uses Generative AI
58
+
</label>
59
+
<small>Check if this account uses LLMs, image generation, etc.</small>
60
+
</div>
61
+
62
+
<div class="form-group">
63
+
<label for="description">Description</label>
64
+
<textarea
65
+
id="description"
66
+
name="description"
67
+
rows="4"
68
+
maxlength="300"
69
+
placeholder="Explain how this account is automated and what it does..."
70
+
></textarea>
71
+
<small>Plain language explanation (max 300 characters)</small>
72
+
</div>
73
+
74
+
<fieldset class="form-group">
75
+
<legend>Responsible Party</legend>
76
+
77
+
<div class="form-group">
78
+
<label for="responsibleType">Type</label>
79
+
<select id="responsibleType" name="responsibleType">
80
+
<option value="">Select type (optional)</option>
81
+
<option value="person">Person</option>
82
+
<option value="organization">Organization</option>
83
+
</select>
84
+
</div>
85
+
86
+
<div class="form-group">
87
+
<label for="responsibleName">Name</label>
88
+
<input
89
+
type="text"
90
+
id="responsibleName"
91
+
name="responsibleName"
92
+
maxlength="100"
93
+
placeholder="Name of person or organization"
94
+
/>
95
+
</div>
96
+
97
+
<div class="form-group">
98
+
<label for="responsibleContact">Contact</label>
99
+
<input
100
+
type="text"
101
+
id="responsibleContact"
102
+
name="responsibleContact"
103
+
maxlength="300"
104
+
placeholder="Email, URL, handle, or DID"
105
+
/>
106
+
</div>
107
+
108
+
<div class="form-group">
109
+
<label for="responsibleDid">DID (optional)</label>
110
+
<input
111
+
type="text"
112
+
id="responsibleDid"
113
+
name="responsibleDid"
114
+
placeholder="did:plc:..."
115
+
/>
116
+
<small>ATProto DID if they have an identity</small>
117
+
</div>
118
+
</fieldset>
119
+
120
+
<div class="form-group">
121
+
<label for="disclosureUrl">Disclosure URL</label>
122
+
<input
123
+
type="url"
124
+
id="disclosureUrl"
125
+
name="disclosureUrl"
126
+
placeholder="https://..."
127
+
/>
128
+
<small>URL with additional information about automation</small>
129
+
</div>
130
+
131
+
<div class="form-group">
132
+
<label for="externalServices">External Services</label>
133
+
<textarea
134
+
id="externalServices"
135
+
name="externalServices"
136
+
rows="3"
137
+
placeholder="Letta, Railway, Google Gemini 2.5-pro (one per line)"
138
+
></textarea>
139
+
<small>External tools and services used (one per line, max 20)</small>
140
+
</div>
141
+
142
+
<button type="submit" id="submit-btn">
143
+
Submit Declaration
144
+
</button>
145
+
</form>
146
+
147
+
<div id="result" class="result hidden"></div>
148
+
</main>
149
+
</div>
150
+
151
+
<script type="module" src="./app.js"></script>
152
+
</body>
153
+
</html>
+17
client/main.ts
+17
client/main.ts
···
1
+
/**
2
+
* Simple development server for the client
3
+
*/
4
+
5
+
import { serveDir } from "https://deno.land/std@0.224.0/http/file_server.ts";
6
+
7
+
Deno.serve({
8
+
port: 8000,
9
+
handler: (req) => {
10
+
return serveDir(req, {
11
+
fsRoot: "./client",
12
+
showDirListing: false,
13
+
});
14
+
},
15
+
});
16
+
17
+
console.log("๐ Client running at http://localhost:8000");
+148
client/styles.css
+148
client/styles.css
···
1
+
* {
2
+
margin: 0;
3
+
padding: 0;
4
+
box-sizing: border-box;
5
+
}
6
+
7
+
body {
8
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
9
+
line-height: 1.6;
10
+
color: #333;
11
+
background: #f5f5f5;
12
+
padding: 20px;
13
+
}
14
+
15
+
.container {
16
+
max-width: 600px;
17
+
margin: 0 auto;
18
+
background: white;
19
+
padding: 40px;
20
+
border-radius: 8px;
21
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
22
+
}
23
+
24
+
header {
25
+
text-align: center;
26
+
margin-bottom: 40px;
27
+
}
28
+
29
+
h1 {
30
+
font-size: 2rem;
31
+
margin-bottom: 10px;
32
+
color: #1a1a1a;
33
+
}
34
+
35
+
header p {
36
+
color: #666;
37
+
}
38
+
39
+
.form-group {
40
+
margin-bottom: 24px;
41
+
}
42
+
43
+
label {
44
+
display: block;
45
+
margin-bottom: 8px;
46
+
font-weight: 500;
47
+
color: #333;
48
+
}
49
+
50
+
input[type="text"],
51
+
input[type="password"],
52
+
input[type="url"],
53
+
select,
54
+
textarea {
55
+
width: 100%;
56
+
padding: 12px;
57
+
border: 1px solid #ddd;
58
+
border-radius: 4px;
59
+
font-size: 14px;
60
+
font-family: inherit;
61
+
transition: border-color 0.2s;
62
+
}
63
+
64
+
input[type="text"]:focus,
65
+
input[type="password"]:focus,
66
+
input[type="url"]:focus,
67
+
select:focus,
68
+
textarea:focus {
69
+
outline: none;
70
+
border-color: #0085ff;
71
+
}
72
+
73
+
select {
74
+
cursor: pointer;
75
+
}
76
+
77
+
textarea {
78
+
resize: vertical;
79
+
min-height: 100px;
80
+
}
81
+
82
+
input[type="checkbox"] {
83
+
margin-right: 8px;
84
+
}
85
+
86
+
fieldset {
87
+
border: 1px solid #ddd;
88
+
border-radius: 4px;
89
+
padding: 20px;
90
+
margin-bottom: 24px;
91
+
}
92
+
93
+
legend {
94
+
font-weight: 600;
95
+
color: #333;
96
+
padding: 0 8px;
97
+
}
98
+
99
+
small {
100
+
display: block;
101
+
margin-top: 4px;
102
+
color: #666;
103
+
font-size: 12px;
104
+
}
105
+
106
+
button[type="submit"] {
107
+
width: 100%;
108
+
padding: 14px;
109
+
background: #0085ff;
110
+
color: white;
111
+
border: none;
112
+
border-radius: 4px;
113
+
font-size: 16px;
114
+
font-weight: 500;
115
+
cursor: pointer;
116
+
transition: background 0.2s;
117
+
}
118
+
119
+
button[type="submit"]:hover {
120
+
background: #0070dd;
121
+
}
122
+
123
+
button[type="submit"]:disabled {
124
+
background: #ccc;
125
+
cursor: not-allowed;
126
+
}
127
+
128
+
.result {
129
+
margin-top: 24px;
130
+
padding: 16px;
131
+
border-radius: 4px;
132
+
}
133
+
134
+
.result.hidden {
135
+
display: none;
136
+
}
137
+
138
+
.result.success {
139
+
background: #d4edda;
140
+
color: #155724;
141
+
border: 1px solid #c3e6cb;
142
+
}
143
+
144
+
.result.error {
145
+
background: #f8d7da;
146
+
color: #721c24;
147
+
border: 1px solid #f5c6cb;
148
+
}
+20
deno.json
+20
deno.json
···
1
+
{
2
+
"name": "@voyager/autonomy-lexicon",
3
+
"version": "0.1.0",
4
+
"exports": "./mod.ts",
5
+
"tasks": {
6
+
"dev": "deno run --watch --allow-net --allow-read client/main.ts",
7
+
"publish:lexicon": "deno run --env --allow-net --allow-read --allow-env scripts/publish_lexicon.ts"
8
+
},
9
+
"imports": {
10
+
"@atproto/api": "npm:@atproto/api@^0.13.0"
11
+
},
12
+
"publish": {
13
+
"exclude": [
14
+
"client/",
15
+
"scripts/",
16
+
".env",
17
+
".env.example"
18
+
]
19
+
}
20
+
}
+83
deno.lock
+83
deno.lock
···
1
+
{
2
+
"version": "5",
3
+
"specifiers": {
4
+
"npm:@atproto/api@0.13": "0.13.35"
5
+
},
6
+
"npm": {
7
+
"@atproto/api@0.13.35": {
8
+
"integrity": "sha512-vsEfBj0C333TLjDppvTdTE0IdKlXuljKSveAeI4PPx/l6eUKNnDTsYxvILtXUVzwUlTDmSRqy5O4Ryh78n1b7g==",
9
+
"dependencies": [
10
+
"@atproto/common-web",
11
+
"@atproto/lexicon",
12
+
"@atproto/syntax@0.3.4",
13
+
"@atproto/xrpc",
14
+
"await-lock",
15
+
"multiformats",
16
+
"tlds",
17
+
"zod"
18
+
]
19
+
},
20
+
"@atproto/common-web@0.4.3": {
21
+
"integrity": "sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg==",
22
+
"dependencies": [
23
+
"graphemer",
24
+
"multiformats",
25
+
"uint8arrays",
26
+
"zod"
27
+
]
28
+
},
29
+
"@atproto/lexicon@0.4.14": {
30
+
"integrity": "sha512-jiKpmH1QER3Gvc7JVY5brwrfo+etFoe57tKPQX/SmPwjvUsFnJAow5xLIryuBaJgFAhnTZViXKs41t//pahGHQ==",
31
+
"dependencies": [
32
+
"@atproto/common-web",
33
+
"@atproto/syntax@0.4.1",
34
+
"iso-datestring-validator",
35
+
"multiformats",
36
+
"zod"
37
+
]
38
+
},
39
+
"@atproto/syntax@0.3.4": {
40
+
"integrity": "sha512-8CNmi5DipOLaVeSMPggMe7FCksVag0aO6XZy9WflbduTKM4dFZVCs4686UeMLfGRXX+X966XgwECHoLYrovMMg=="
41
+
},
42
+
"@atproto/syntax@0.4.1": {
43
+
"integrity": "sha512-CJdImtLAiFO+0z3BWTtxwk6aY5w4t8orHTMVJgkf++QRJWTxPbIFko/0hrkADB7n2EruDxDSeAgfUGehpH6ngw=="
44
+
},
45
+
"@atproto/xrpc@0.6.12": {
46
+
"integrity": "sha512-Ut3iISNLujlmY9Gu8sNU+SPDJDvqlVzWddU8qUr0Yae5oD4SguaUFjjhireMGhQ3M5E0KljQgDbTmnBo1kIZ3w==",
47
+
"dependencies": [
48
+
"@atproto/lexicon",
49
+
"zod"
50
+
]
51
+
},
52
+
"await-lock@2.2.2": {
53
+
"integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw=="
54
+
},
55
+
"graphemer@1.4.0": {
56
+
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="
57
+
},
58
+
"iso-datestring-validator@2.2.2": {
59
+
"integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA=="
60
+
},
61
+
"multiformats@9.9.0": {
62
+
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="
63
+
},
64
+
"tlds@1.260.0": {
65
+
"integrity": "sha512-78+28EWBhCEE7qlyaHA9OR3IPvbCLiDh3Ckla593TksfFc9vfTsgvH7eS+dr3o9qr31gwGbogcI16yN91PoRjQ==",
66
+
"bin": true
67
+
},
68
+
"uint8arrays@3.0.0": {
69
+
"integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==",
70
+
"dependencies": [
71
+
"multiformats"
72
+
]
73
+
},
74
+
"zod@3.25.76": {
75
+
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="
76
+
}
77
+
},
78
+
"workspace": {
79
+
"dependencies": [
80
+
"npm:@atproto/api@0.13"
81
+
]
82
+
}
83
+
}
+9
jsr.json
+9
jsr.json
+86
lexicon/autonomy-declaration.json
+86
lexicon/autonomy-declaration.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "studio.voyager.account.autonomy",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"key": "literal:self",
8
+
"record": {
9
+
"type": "object",
10
+
"properties": {
11
+
"automationLevel": {
12
+
"type": "string",
13
+
"knownValues": [
14
+
"human",
15
+
"assisted",
16
+
"collaborative",
17
+
"automated"
18
+
],
19
+
"description": "Level of automation in account management and content creation"
20
+
},
21
+
"usesGenerativeAI": {
22
+
"type": "boolean",
23
+
"description": "Whether this account uses generative AI (LLMs, image generation, etc.) to create content"
24
+
},
25
+
"description": {
26
+
"type": "string",
27
+
"maxGraphemes": 300,
28
+
"description": "Plain language explanation of how this account is automated and what it does"
29
+
},
30
+
"responsibleParty": {
31
+
"type": "object",
32
+
"properties": {
33
+
"type": {
34
+
"type": "string",
35
+
"knownValues": [
36
+
"person",
37
+
"organization"
38
+
],
39
+
"description": "Whether the responsible party is a person or organization"
40
+
},
41
+
"name": {
42
+
"type": "string",
43
+
"maxGraphemes": 100,
44
+
"description": "Name of the person or organization responsible"
45
+
},
46
+
"contact": {
47
+
"type": "string",
48
+
"maxLength": 300,
49
+
"description": "Contact information (email, URL, handle, or DID)"
50
+
},
51
+
"did": {
52
+
"type": "string",
53
+
"format": "did",
54
+
"description": "DID of the responsible party if they have an ATProto identity"
55
+
}
56
+
},
57
+
"description": "Information about who is accountable for this account's automated behavior"
58
+
},
59
+
"disclosureUrl": {
60
+
"type": "string",
61
+
"format": "uri",
62
+
"description": "URL with additional information about this account's automation"
63
+
},
64
+
"externalServices": {
65
+
"type": "array",
66
+
"items": {
67
+
"type": "string",
68
+
"maxLength": 200
69
+
},
70
+
"maxLength": 20,
71
+
"description": "External tools and services this agent relies on outside of Bluesky (e.g., 'Letta', 'Railway', 'Google Gemini 2.5-pro')"
72
+
},
73
+
"createdAt": {
74
+
"type": "string",
75
+
"format": "datetime",
76
+
"description": "Timestamp when this declaration was created"
77
+
}
78
+
},
79
+
"required": [
80
+
"createdAt"
81
+
]
82
+
},
83
+
"description": "Declaration of automation and AI usage for transparency and accountability"
84
+
}
85
+
}
86
+
}
+32
mod.ts
+32
mod.ts
···
1
+
/**
2
+
* ATProtocol Autonomy Declaration Lexicon
3
+
*
4
+
* This package exports the lexicon definition for the autonomy declaration record type.
5
+
* Used for declaring automation and AI usage for transparency and accountability on ATProtocol/Bluesky.
6
+
*
7
+
* @module
8
+
*/
9
+
10
+
import lexicon from "./lexicon/autonomy-declaration.json" with { type: "json" };
11
+
12
+
export const AUTONOMY_DECLARATION_LEXICON = lexicon;
13
+
14
+
export type AutomationLevel = "human" | "assisted" | "collaborative" | "automated";
15
+
export type ResponsiblePartyType = "person" | "organization";
16
+
17
+
export interface ResponsibleParty {
18
+
type?: ResponsiblePartyType;
19
+
name?: string;
20
+
contact?: string;
21
+
did?: string;
22
+
}
23
+
24
+
export interface AutonomyDeclaration {
25
+
automationLevel?: AutomationLevel;
26
+
usesGenerativeAI?: boolean;
27
+
description?: string;
28
+
responsibleParty?: ResponsibleParty;
29
+
disclosureUrl?: string;
30
+
externalServices?: string[];
31
+
createdAt: string;
32
+
}
+115
scripts/publish_lexicon.ts
+115
scripts/publish_lexicon.ts
···
1
+
#!/usr/bin/env -S deno run --allow-net --allow-read --allow-env
2
+
3
+
/**
4
+
* Schema Publication Script
5
+
*
6
+
* This script publishes the studio.voyager.account.autonomy lexicon schema
7
+
* to the voyager.studio PDS.
8
+
*
9
+
* Prerequisites:
10
+
* 1. DNS TXT record: _lexicon.account.voyager.studio pointing to your DID
11
+
* 2. VOYAGER_PASSWORD: App password for voyager.studio account
12
+
*
13
+
* Usage:
14
+
* Set VOYAGER_PASSWORD in .env and run: deno task publish:lexicon
15
+
*/
16
+
17
+
import { AtpAgent } from "npm:@atproto/api@^0.13.0";
18
+
import { AUTONOMY_DECLARATION_LEXICON } from "../mod.ts";
19
+
20
+
const VOYAGER_HANDLE = "voyager.studio";
21
+
const SERVICE_URL = "https://bsky.social";
22
+
23
+
const publishSchema = async () => {
24
+
console.log("๐ง Publishing studio.voyager.account.autonomy schema...\n");
25
+
26
+
// Get password from environment
27
+
const password = Deno.env.get("VOYAGER_PASSWORD");
28
+
29
+
if (!password) {
30
+
console.error("โ Error: VOYAGER_PASSWORD not found.\n");
31
+
console.error(" Set VOYAGER_PASSWORD in .env\n");
32
+
Deno.exit(1);
33
+
}
34
+
35
+
// Initialize agent
36
+
const agent = new AtpAgent({ service: SERVICE_URL });
37
+
38
+
try {
39
+
// Login
40
+
console.log(`๐ Logging in as ${VOYAGER_HANDLE}...`);
41
+
await agent.login({
42
+
identifier: VOYAGER_HANDLE,
43
+
password: password,
44
+
});
45
+
console.log(`โ
Logged in as: ${agent.session?.handle} (${agent.session?.did})\n`);
46
+
47
+
// Prepare schema record
48
+
const schemaRecord = {
49
+
$type: "com.atproto.lexicon.schema",
50
+
lexicon: AUTONOMY_DECLARATION_LEXICON.lexicon,
51
+
id: AUTONOMY_DECLARATION_LEXICON.id,
52
+
defs: AUTONOMY_DECLARATION_LEXICON.defs,
53
+
description:
54
+
"Lexicon for declaring AI agent autonomy and automation practices for transparency and accountability",
55
+
};
56
+
57
+
console.log(`๐ Schema Details:`);
58
+
console.log(` NSID: ${schemaRecord.id}`);
59
+
console.log(` Lexicon Version: ${schemaRecord.lexicon}`);
60
+
console.log(` Definitions: ${Object.keys(schemaRecord.defs).join(", ")}\n`);
61
+
62
+
// Check if schema already exists
63
+
let existingSchema = null;
64
+
try {
65
+
const existing = await agent.com.atproto.repo.getRecord({
66
+
repo: agent.session?.did!,
67
+
collection: "com.atproto.lexicon.schema",
68
+
rkey: "studio.voyager.account.autonomy",
69
+
});
70
+
existingSchema = existing.data;
71
+
console.log("๐ Existing schema found - will update\n");
72
+
} catch (error: any) {
73
+
if (error?.status === 400 || error?.status === 404) {
74
+
console.log("๐ No existing schema found - will create new\n");
75
+
} else {
76
+
throw error;
77
+
}
78
+
}
79
+
80
+
// Publish schema
81
+
console.log("๐ค Publishing schema...");
82
+
const result = await agent.com.atproto.repo.putRecord({
83
+
repo: agent.session?.did!,
84
+
collection: "com.atproto.lexicon.schema",
85
+
rkey: "studio.voyager.account.autonomy",
86
+
record: schemaRecord,
87
+
});
88
+
89
+
console.log(`โ
Schema ${existingSchema ? "updated" : "published"} successfully!\n`);
90
+
console.log(`๐ AT-URI: ${result.uri}`);
91
+
console.log(`๐ CID: ${result.cid}\n`);
92
+
93
+
// Verify DNS setup
94
+
console.log("๐ Next steps:");
95
+
console.log(" 1. Verify DNS TXT record is set:");
96
+
console.log(" Name: _lexicon.account.voyager.studio");
97
+
console.log(" Type: TXT");
98
+
console.log(` Value: did=${agent.session?.did}`);
99
+
console.log(
100
+
"\n 2. Test resolution (may take time for DNS to propagate):",
101
+
);
102
+
console.log(
103
+
` dig TXT _lexicon.account.voyager.studio\n`,
104
+
);
105
+
console.log("โจ Schema is now discoverable by AT Protocol resolvers!");
106
+
} catch (error) {
107
+
console.error("โ Error publishing schema:", error);
108
+
Deno.exit(1);
109
+
}
110
+
};
111
+
112
+
// Run if executed directly
113
+
if (import.meta.main) {
114
+
await publishSchema();
115
+
}