@townco/secret 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.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Represents a line in an .env file
3
+ */
4
+ type EnvLine = {
5
+ type: "comment";
6
+ content: string;
7
+ } | {
8
+ type: "blank";
9
+ } | {
10
+ type: "entry";
11
+ key: string;
12
+ value: string;
13
+ raw: string;
14
+ };
15
+ /**
16
+ * A data structure that represents a parsed .env file while preserving
17
+ * all original content including comments, blank lines, and order.
18
+ */
19
+ export declare class EnvFile {
20
+ private lines;
21
+ constructor(lines?: EnvLine[]);
22
+ /**
23
+ * Parse a .env file string into an EnvFile structure
24
+ */
25
+ static parse(content: string): EnvFile;
26
+ /**
27
+ * Serialize the EnvFile back to a string
28
+ */
29
+ toString(): string;
30
+ /**
31
+ * Get all entries as a key-value record
32
+ */
33
+ toRecord(): Record<string, string>;
34
+ /**
35
+ * Find an entry line by key
36
+ */
37
+ private findEntry;
38
+ /**
39
+ * Find the index of an entry by key
40
+ */
41
+ private findEntryIndex;
42
+ /**
43
+ * Create an entry line from key and value
44
+ */
45
+ private createEntry;
46
+ /**
47
+ * Normalize a value by ensuring it's properly quoted
48
+ */
49
+ private normalizeValue;
50
+ /**
51
+ * Find the insertion index for a new entry (before the final blank line if present)
52
+ */
53
+ private findInsertionIndex;
54
+ /**
55
+ * Get the value for a specific key
56
+ */
57
+ get(key: string): string | undefined;
58
+ /**
59
+ * Set a value for a key. If the key exists, updates it in place.
60
+ * If it doesn't exist, appends it above the final newline (if present).
61
+ * Values are always double quoted.
62
+ */
63
+ set(key: string, value: string): this;
64
+ /**
65
+ * Delete a key-value entry
66
+ */
67
+ delete(key: string): this;
68
+ /**
69
+ * Check if a key exists
70
+ */
71
+ has(key: string): boolean;
72
+ /**
73
+ * Get all keys
74
+ */
75
+ keys(): string[];
76
+ /**
77
+ * Iterate over all entries
78
+ */
79
+ entries(): IterableIterator<[string, string]>;
80
+ /**
81
+ * Apply a function to all entries and return a new EnvFile
82
+ */
83
+ map(fn: (key: string, value: string) => [string, string]): EnvFile;
84
+ /**
85
+ * Filter entries based on a predicate
86
+ */
87
+ filter(fn: (key: string, value: string) => boolean): EnvFile;
88
+ /**
89
+ * Add a comment line
90
+ */
91
+ addComment(content: string): this;
92
+ /**
93
+ * Add a blank line
94
+ */
95
+ addBlank(): this;
96
+ /**
97
+ * Get the raw lines array for custom operations
98
+ */
99
+ getLines(): readonly EnvLine[];
100
+ /**
101
+ * Create a clone of this EnvFile
102
+ */
103
+ clone(): EnvFile;
104
+ }
105
+ export {};
@@ -0,0 +1,205 @@
1
+ /**
2
+ * A data structure that represents a parsed .env file while preserving
3
+ * all original content including comments, blank lines, and order.
4
+ */
5
+ export class EnvFile {
6
+ lines;
7
+ constructor(lines = []) {
8
+ this.lines = lines;
9
+ }
10
+ /**
11
+ * Parse a .env file string into an EnvFile structure
12
+ */
13
+ static parse(content) {
14
+ const lines = content.split("\n").map((line) => {
15
+ const trimmed = line.trim();
16
+ if (trimmed === "") {
17
+ return { type: "blank" };
18
+ }
19
+ if (trimmed.startsWith("#")) {
20
+ return { type: "comment", content: line };
21
+ }
22
+ const equalsIndex = line.indexOf("=");
23
+ if (equalsIndex === -1) {
24
+ // Malformed line, treat as comment
25
+ return { type: "comment", content: line };
26
+ }
27
+ const key = line.substring(0, equalsIndex).trim();
28
+ const value = line.substring(equalsIndex + 1);
29
+ return { type: "entry", key, value, raw: line };
30
+ });
31
+ return new EnvFile(lines);
32
+ }
33
+ /**
34
+ * Serialize the EnvFile back to a string
35
+ */
36
+ toString() {
37
+ return this.lines
38
+ .map((line) => {
39
+ switch (line.type) {
40
+ case "blank":
41
+ return "";
42
+ case "comment":
43
+ return line.content;
44
+ case "entry":
45
+ return line.raw;
46
+ default:
47
+ throw new Error(`Unknown line type`);
48
+ }
49
+ })
50
+ .join("\n");
51
+ }
52
+ /**
53
+ * Get all entries as a key-value record
54
+ */
55
+ toRecord() {
56
+ return Object.fromEntries(this.entries());
57
+ }
58
+ /**
59
+ * Find an entry line by key
60
+ */
61
+ findEntry(key) {
62
+ const line = this.lines.find((l) => l.type === "entry" && l.key === key);
63
+ return line;
64
+ }
65
+ /**
66
+ * Find the index of an entry by key
67
+ */
68
+ findEntryIndex(key) {
69
+ return this.lines.findIndex((l) => l.type === "entry" && l.key === key);
70
+ }
71
+ /**
72
+ * Create an entry line from key and value
73
+ */
74
+ createEntry(key, value) {
75
+ const quotedValue = this.normalizeValue(value);
76
+ return {
77
+ type: "entry",
78
+ key,
79
+ value: quotedValue,
80
+ raw: `${key}=${quotedValue}`,
81
+ };
82
+ }
83
+ /**
84
+ * Normalize a value by ensuring it's properly quoted
85
+ */
86
+ normalizeValue(value) {
87
+ // Strip existing quotes if present, then add quotes
88
+ const unquoted = value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
89
+ return `"${unquoted}"`;
90
+ }
91
+ /**
92
+ * Find the insertion index for a new entry (before the final blank line if present)
93
+ */
94
+ findInsertionIndex() {
95
+ const lastLine = this.lines[this.lines.length - 1];
96
+ return lastLine?.type === "blank"
97
+ ? this.lines.length - 1
98
+ : this.lines.length;
99
+ }
100
+ /**
101
+ * Get the value for a specific key
102
+ */
103
+ get(key) {
104
+ return this.findEntry(key)?.value;
105
+ }
106
+ /**
107
+ * Set a value for a key. If the key exists, updates it in place.
108
+ * If it doesn't exist, appends it above the final newline (if present).
109
+ * Values are always double quoted.
110
+ */
111
+ set(key, value) {
112
+ const index = this.findEntryIndex(key);
113
+ const entry = this.createEntry(key, value);
114
+ if (index !== -1) {
115
+ this.lines[index] = entry;
116
+ }
117
+ else {
118
+ this.lines.splice(this.findInsertionIndex(), 0, entry);
119
+ }
120
+ return this;
121
+ }
122
+ /**
123
+ * Delete a key-value entry
124
+ */
125
+ delete(key) {
126
+ this.lines = this.lines.filter((l) => !(l.type === "entry" && l.key === key));
127
+ return this;
128
+ }
129
+ /**
130
+ * Check if a key exists
131
+ */
132
+ has(key) {
133
+ return this.findEntry(key) !== undefined;
134
+ }
135
+ /**
136
+ * Get all keys
137
+ */
138
+ keys() {
139
+ return this.lines
140
+ .filter((l) => l.type === "entry")
141
+ .map((l) => l.key);
142
+ }
143
+ /**
144
+ * Iterate over all entries
145
+ */
146
+ *entries() {
147
+ for (const line of this.lines) {
148
+ if (line.type === "entry") {
149
+ yield [line.key, line.value];
150
+ }
151
+ }
152
+ }
153
+ /**
154
+ * Apply a function to all entries and return a new EnvFile
155
+ */
156
+ map(fn) {
157
+ const newLines = this.lines.map((line) => {
158
+ if (line.type === "entry") {
159
+ const [newKey, newValue] = fn(line.key, line.value);
160
+ return this.createEntry(newKey, newValue);
161
+ }
162
+ return line;
163
+ });
164
+ return new EnvFile(newLines);
165
+ }
166
+ /**
167
+ * Filter entries based on a predicate
168
+ */
169
+ filter(fn) {
170
+ const newLines = this.lines.filter((line) => {
171
+ if (line.type === "entry") {
172
+ return fn(line.key, line.value);
173
+ }
174
+ return true; // Keep comments and blank lines
175
+ });
176
+ return new EnvFile(newLines);
177
+ }
178
+ /**
179
+ * Add a comment line
180
+ */
181
+ addComment(content) {
182
+ const comment = content.startsWith("#") ? content : `# ${content}`;
183
+ this.lines.push({ type: "comment", content: comment });
184
+ return this;
185
+ }
186
+ /**
187
+ * Add a blank line
188
+ */
189
+ addBlank() {
190
+ this.lines.push({ type: "blank" });
191
+ return this;
192
+ }
193
+ /**
194
+ * Get the raw lines array for custom operations
195
+ */
196
+ getLines() {
197
+ return this.lines;
198
+ }
199
+ /**
200
+ * Create a clone of this EnvFile
201
+ */
202
+ clone() {
203
+ return new EnvFile([...this.lines]);
204
+ }
205
+ }
@@ -0,0 +1,12 @@
1
+ export { EnvFile } from "./env-file";
2
+ export { type OnePassword, OnePassword as OpItem } from "./onepassword";
3
+ export type SecretValidation = {
4
+ key: string;
5
+ value: string;
6
+ valid: boolean;
7
+ error?: string;
8
+ };
9
+ export declare const listSecrets: () => Promise<Promise<SecretValidation[]>>;
10
+ export declare const createSecret: (name: string, value: string) => Promise<Promise<void>>;
11
+ export declare const deleteSecret: (name: string) => Promise<Promise<void>>;
12
+ export declare const genenv: () => Promise<Promise<void>>;
package/dist/index.js ADDED
@@ -0,0 +1,130 @@
1
+ import * as path from "node:path";
2
+ import { EnvFile } from "./env-file";
3
+ import { OnePassword } from "./onepassword";
4
+ export { EnvFile } from "./env-file";
5
+ export { OnePassword as OpItem } from "./onepassword";
6
+ const ROOT_MARKER = ".root";
7
+ const SECRET_FILE = ".env.in";
8
+ const OP_VAULT = "app";
9
+ const OP_ITEM = "dev";
10
+ const findRoot = async (start = ".") => {
11
+ const startResolved = path.resolve(start);
12
+ const rootPath = path.join(startResolved, ROOT_MARKER);
13
+ if (await Bun.file(rootPath).exists())
14
+ return startResolved;
15
+ const parent = path.dirname(startResolved);
16
+ if (parent === "/") {
17
+ throw new Error("No root found");
18
+ }
19
+ return await findRoot(parent);
20
+ };
21
+ const withOpSignIn = (fn) => async (...args) => {
22
+ await OnePassword.signin();
23
+ return fn(...args);
24
+ };
25
+ const readEnvFile = async (filePath) => {
26
+ const content = await Bun.file(filePath).text();
27
+ return EnvFile.parse(content);
28
+ };
29
+ const writeEnvFile = async (filePath, envFile) => {
30
+ await Bun.write(filePath, envFile.toString());
31
+ };
32
+ const withEnvFile = (fn) => async (...args) => {
33
+ const filePath = path.join(await findRoot(), SECRET_FILE);
34
+ const envFile = await readEnvFile(filePath);
35
+ const originalRecord = envFile.toRecord();
36
+ const result = fn({ ...originalRecord }, ...args);
37
+ // If the result is a Record<string, string>, write it back
38
+ if (result && typeof result === "object" && !Array.isArray(result)) {
39
+ const resultRecord = result;
40
+ // Check if this looks like a secrets record (all string values)
41
+ const isSecretsRecord = Object.values(resultRecord).every((v) => typeof v === "string");
42
+ if (isSecretsRecord) {
43
+ const resultSecrets = resultRecord;
44
+ // Delete keys that are in original but not in result
45
+ for (const key of Object.keys(originalRecord)) {
46
+ if (!(key in resultSecrets)) {
47
+ envFile.delete(key);
48
+ }
49
+ }
50
+ // Update or add keys from result
51
+ for (const [key, value] of Object.entries(resultSecrets)) {
52
+ envFile.set(key, value);
53
+ }
54
+ await writeEnvFile(filePath, envFile);
55
+ }
56
+ }
57
+ return result;
58
+ };
59
+ export const listSecrets = withOpSignIn(async () => {
60
+ const root = await findRoot();
61
+ const filePath = path.join(root, SECRET_FILE);
62
+ const envFile = await readEnvFile(filePath);
63
+ const secrets = envFile.toRecord();
64
+ // Fetch the dev item using OpItem
65
+ const opItem = await OnePassword.fetch(OP_VAULT, OP_ITEM);
66
+ const validations = [];
67
+ for (const [key, value] of Object.entries(secrets)) {
68
+ // Strip quotes from value if present
69
+ let unquotedValue = value;
70
+ if (value.startsWith('"') && value.endsWith('"')) {
71
+ unquotedValue = value.slice(1, -1);
72
+ }
73
+ const validation = {
74
+ key,
75
+ value: unquotedValue,
76
+ valid: true,
77
+ };
78
+ if (!validation.value.startsWith("op://")) {
79
+ validations.push(validation);
80
+ continue;
81
+ }
82
+ // Check if field exists in 1Password item
83
+ if (opItem.has(key)) {
84
+ validation.valid = false;
85
+ validation.error = `Field '${key}' not found in 1Password item`;
86
+ validations.push(validation);
87
+ continue;
88
+ }
89
+ // Build expected op:// reference
90
+ const expectedRef = opItem.getReference(key);
91
+ // Compare the value from .env.in with expected reference
92
+ if (unquotedValue !== expectedRef) {
93
+ validation.valid = false;
94
+ validation.error = `Reference mismatch: expected '${expectedRef}', got '${unquotedValue}'`;
95
+ validations.push(validation);
96
+ continue;
97
+ }
98
+ validations.push(validation);
99
+ }
100
+ return validations;
101
+ });
102
+ export const createSecret = withOpSignIn(async (name, value) => {
103
+ // Fetch the item, update it, and sync
104
+ const opItem = await OnePassword.fetch(OP_VAULT, OP_ITEM);
105
+ opItem.set(name, value);
106
+ await opItem.sync();
107
+ // Update the env file with the secret reference
108
+ await withEnvFile((secrets) => {
109
+ secrets[name] = opItem.getReference(name);
110
+ return secrets;
111
+ })();
112
+ });
113
+ export const deleteSecret = withOpSignIn(async (name) => {
114
+ // Fetch the item, delete the field, and sync
115
+ const opItem = await OnePassword.fetch(OP_VAULT, OP_ITEM);
116
+ opItem.delete(name);
117
+ await opItem.sync();
118
+ // Update the env file by removing the secret
119
+ await withEnvFile((secrets, fieldName) => {
120
+ delete secrets[fieldName];
121
+ return secrets;
122
+ })(name);
123
+ });
124
+ export const genenv = withOpSignIn(async () => {
125
+ const root = await findRoot();
126
+ const inputPath = path.join(root, SECRET_FILE);
127
+ const outputPath = path.join(root, ".env");
128
+ // Use OpItem to inject and resolve secret references
129
+ await OnePassword.inject(inputPath, outputPath);
130
+ });
@@ -0,0 +1,102 @@
1
+ import type { FullItem } from "@1password/connect";
2
+ /**
3
+ * Represents a change to be made to a 1Password item
4
+ */
5
+ type OnePasswordChange = {
6
+ type: "add";
7
+ key: string;
8
+ value: string;
9
+ } | {
10
+ type: "update";
11
+ key: string;
12
+ value: string;
13
+ } | {
14
+ type: "delete";
15
+ key: string;
16
+ };
17
+ /**
18
+ * A data structure that represents a 1Password item and provides
19
+ * methods to detect changes and generate appropriate `op` CLI commands.
20
+ */
21
+ export declare class OnePassword {
22
+ private vault;
23
+ private item;
24
+ private fields;
25
+ private originalFields;
26
+ constructor(vault: string, item: string, fields?: Map<string, string>);
27
+ /**
28
+ * Create an OpItem from a 1Password FullItem JSON response
29
+ */
30
+ static fromFullItem(vault: string, itemName: string, fullItem: FullItem): OnePassword;
31
+ /**
32
+ * Fetch an OpItem from 1Password using the CLI
33
+ */
34
+ static fetch(vault: string, item: string): Promise<OnePassword>;
35
+ /**
36
+ * Sign in to 1Password CLI
37
+ */
38
+ static signin(): Promise<void>;
39
+ /**
40
+ * Get a field value
41
+ */
42
+ get(key: string): string | undefined;
43
+ /**
44
+ * Set a field value (marks as changed)
45
+ */
46
+ set(key: string, value: string): this;
47
+ /**
48
+ * Delete a field (marks as deleted)
49
+ */
50
+ delete(key: string): this;
51
+ /**
52
+ * Check if a field exists
53
+ */
54
+ has(key: string): boolean;
55
+ /**
56
+ * Get all field keys
57
+ */
58
+ keys(): string[];
59
+ /**
60
+ * Get all fields as a record
61
+ */
62
+ toRecord(): Record<string, string>;
63
+ /**
64
+ * Get all field entries
65
+ */
66
+ entries(): IterableIterator<[string, string]>;
67
+ /**
68
+ * Build an op:// reference for a field
69
+ */
70
+ getReference(key: string): string;
71
+ /**
72
+ * Detect changes between original and current state
73
+ */
74
+ detectChanges(): OnePasswordChange[];
75
+ /**
76
+ * Generate op CLI arguments for a single change
77
+ */
78
+ private buildEditArgs;
79
+ /**
80
+ * Apply all changes to 1Password using the op CLI
81
+ * Returns the number of changes applied
82
+ */
83
+ sync(): Promise<number>;
84
+ /**
85
+ * Inject secrets from a template file to an output file
86
+ * This resolves op:// references to actual values
87
+ */
88
+ static inject(inputPath: string, outputPath: string): Promise<void>;
89
+ /**
90
+ * Create a clone of this OpItem
91
+ */
92
+ clone(): OnePassword;
93
+ /**
94
+ * Reset to original state (discard changes)
95
+ */
96
+ reset(): this;
97
+ /**
98
+ * Check if there are unsaved changes
99
+ */
100
+ hasChanges(): boolean;
101
+ }
102
+ export {};
@@ -0,0 +1,203 @@
1
+ /**
2
+ * A data structure that represents a 1Password item and provides
3
+ * methods to detect changes and generate appropriate `op` CLI commands.
4
+ */
5
+ export class OnePassword {
6
+ vault;
7
+ item;
8
+ fields;
9
+ originalFields;
10
+ constructor(vault, item, fields = new Map()) {
11
+ this.vault = vault;
12
+ this.item = item;
13
+ this.fields = new Map(fields);
14
+ this.originalFields = new Map(fields);
15
+ }
16
+ /**
17
+ * Create an OpItem from a 1Password FullItem JSON response
18
+ */
19
+ static fromFullItem(vault, itemName, fullItem) {
20
+ const fields = new Map();
21
+ if (fullItem.fields) {
22
+ for (const field of fullItem.fields) {
23
+ if (field.label && field.value) {
24
+ fields.set(field.label, field.value);
25
+ }
26
+ }
27
+ }
28
+ return new OnePassword(vault, itemName, fields);
29
+ }
30
+ /**
31
+ * Fetch an OpItem from 1Password using the CLI
32
+ */
33
+ static async fetch(vault, item) {
34
+ try {
35
+ const proc = Bun.spawn([
36
+ "op",
37
+ "--format=json",
38
+ "item",
39
+ "get",
40
+ "--vault",
41
+ vault,
42
+ item,
43
+ ]);
44
+ const output = await new Response(proc.stdout).text();
45
+ await proc.exited;
46
+ const fullItem = JSON.parse(output);
47
+ return OnePassword.fromFullItem(vault, item, fullItem);
48
+ }
49
+ catch (error) {
50
+ throw new Error(`Failed to fetch item '${item}' from vault '${vault}': ${error}`);
51
+ }
52
+ }
53
+ /**
54
+ * Sign in to 1Password CLI
55
+ */
56
+ static async signin() {
57
+ await Bun.spawn(["op", "signin"]).exited;
58
+ }
59
+ /**
60
+ * Get a field value
61
+ */
62
+ get(key) {
63
+ return this.fields.get(key);
64
+ }
65
+ /**
66
+ * Set a field value (marks as changed)
67
+ */
68
+ set(key, value) {
69
+ this.fields.set(key, value);
70
+ return this;
71
+ }
72
+ /**
73
+ * Delete a field (marks as deleted)
74
+ */
75
+ delete(key) {
76
+ this.fields.delete(key);
77
+ return this;
78
+ }
79
+ /**
80
+ * Check if a field exists
81
+ */
82
+ has(key) {
83
+ return this.fields.has(key);
84
+ }
85
+ /**
86
+ * Get all field keys
87
+ */
88
+ keys() {
89
+ return Array.from(this.fields.keys());
90
+ }
91
+ /**
92
+ * Get all fields as a record
93
+ */
94
+ toRecord() {
95
+ return Object.fromEntries(this.fields);
96
+ }
97
+ /**
98
+ * Get all field entries
99
+ */
100
+ entries() {
101
+ return this.fields.entries();
102
+ }
103
+ /**
104
+ * Build an op:// reference for a field
105
+ */
106
+ getReference(key) {
107
+ return `op://${this.vault}/${this.item}/${key}`;
108
+ }
109
+ /**
110
+ * Detect changes between original and current state
111
+ */
112
+ detectChanges() {
113
+ const changes = [];
114
+ // Check for deletions
115
+ for (const key of this.originalFields.keys()) {
116
+ if (!this.fields.has(key)) {
117
+ changes.push({ type: "delete", key });
118
+ }
119
+ }
120
+ // Check for additions and updates
121
+ for (const [key, value] of this.fields.entries()) {
122
+ const originalValue = this.originalFields.get(key);
123
+ if (originalValue === undefined) {
124
+ changes.push({ type: "add", key, value });
125
+ }
126
+ else if (originalValue !== value) {
127
+ changes.push({ type: "update", key, value });
128
+ }
129
+ }
130
+ return changes;
131
+ }
132
+ /**
133
+ * Generate op CLI arguments for a single change
134
+ */
135
+ buildEditArgs(change) {
136
+ switch (change.type) {
137
+ case "add":
138
+ case "update":
139
+ return [`${change.key}[password]=${change.value}`];
140
+ case "delete":
141
+ return [`${change.key}[delete]`];
142
+ }
143
+ }
144
+ /**
145
+ * Apply all changes to 1Password using the op CLI
146
+ * Returns the number of changes applied
147
+ */
148
+ async sync() {
149
+ const changes = this.detectChanges();
150
+ if (changes.length === 0) {
151
+ return 0;
152
+ }
153
+ // Build all edit arguments
154
+ const editArgs = changes.flatMap((change) => this.buildEditArgs(change));
155
+ // Execute a single op item edit command with all changes
156
+ await Bun.spawn([
157
+ "op",
158
+ "item",
159
+ "edit",
160
+ this.item,
161
+ "--vault",
162
+ this.vault,
163
+ ...editArgs,
164
+ ]).exited;
165
+ // Update originalFields to match current state
166
+ this.originalFields = new Map(this.fields);
167
+ return changes.length;
168
+ }
169
+ /**
170
+ * Inject secrets from a template file to an output file
171
+ * This resolves op:// references to actual values
172
+ */
173
+ static async inject(inputPath, outputPath) {
174
+ await Bun.spawn([
175
+ "op",
176
+ "inject",
177
+ "-i",
178
+ inputPath,
179
+ "-o",
180
+ outputPath,
181
+ "--force",
182
+ ]).exited;
183
+ }
184
+ /**
185
+ * Create a clone of this OpItem
186
+ */
187
+ clone() {
188
+ return new OnePassword(this.vault, this.item, new Map(this.fields));
189
+ }
190
+ /**
191
+ * Reset to original state (discard changes)
192
+ */
193
+ reset() {
194
+ this.fields = new Map(this.originalFields);
195
+ return this;
196
+ }
197
+ /**
198
+ * Check if there are unsaved changes
199
+ */
200
+ hasChanges() {
201
+ return this.detectChanges().length > 0;
202
+ }
203
+ }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@townco/secret",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/federicoweber/agent_hub.git"
14
+ },
15
+ "author": "Federico Weber",
16
+ "exports": {
17
+ ".": {
18
+ "import": "./dist/index.js",
19
+ "types": "./dist/index.d.ts"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "check": "tsc --noEmit"
25
+ },
26
+ "dependencies": {
27
+ "@1password/connect": "^1.4.2"
28
+ },
29
+ "devDependencies": {
30
+ "@townco/tsconfig": "^0.1.0",
31
+ "@types/bun": "^1.3.1"
32
+ }
33
+ }