@meshxdata/fops 0.0.5 → 0.0.6
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/package.json +1 -1
- package/src/commands/index.js +115 -0
- package/src/doctor.js +7 -0
- package/src/plugins/bundled/coda/auth.js +79 -0
- package/src/plugins/bundled/coda/client.js +187 -0
- package/src/plugins/bundled/coda/fops.plugin.json +7 -0
- package/src/plugins/bundled/coda/index.js +284 -0
- package/src/plugins/bundled/coda/package.json +3 -0
- package/src/plugins/bundled/coda/skills/coda/SKILL.md +82 -0
- package/src/plugins/bundled/cursor/fops.plugin.json +7 -0
- package/src/plugins/bundled/cursor/index.js +432 -0
- package/src/plugins/bundled/cursor/package.json +1 -0
- package/src/plugins/bundled/cursor/skills/cursor/SKILL.md +48 -0
- package/src/plugins/bundled/fops-plugin-1password/fops.plugin.json +7 -0
- package/src/plugins/bundled/fops-plugin-1password/index.js +239 -0
- package/src/plugins/bundled/fops-plugin-1password/lib/env.js +100 -0
- package/src/plugins/bundled/fops-plugin-1password/lib/op.js +111 -0
- package/src/plugins/bundled/fops-plugin-1password/lib/setup.js +235 -0
- package/src/plugins/bundled/fops-plugin-1password/lib/sync.js +61 -0
- package/src/plugins/bundled/fops-plugin-1password/package.json +1 -0
- package/src/plugins/bundled/fops-plugin-1password/skills/1password/SKILL.md +79 -0
- package/src/plugins/bundled/fops-plugin-ecr/fops.plugin.json +7 -0
- package/src/plugins/bundled/fops-plugin-ecr/index.js +302 -0
- package/src/plugins/bundled/fops-plugin-ecr/lib/aws.js +147 -0
- package/src/plugins/bundled/fops-plugin-ecr/lib/images.js +73 -0
- package/src/plugins/bundled/fops-plugin-ecr/lib/setup.js +180 -0
- package/src/plugins/bundled/fops-plugin-ecr/lib/sync.js +74 -0
- package/src/plugins/bundled/fops-plugin-ecr/package.json +1 -0
- package/src/plugins/bundled/fops-plugin-ecr/skills/ecr/SKILL.md +105 -0
- package/src/plugins/bundled/fops-plugin-memory/fops.plugin.json +7 -0
- package/src/plugins/bundled/fops-plugin-memory/index.js +148 -0
- package/src/plugins/bundled/fops-plugin-memory/lib/relevance.js +72 -0
- package/src/plugins/bundled/fops-plugin-memory/lib/store.js +75 -0
- package/src/plugins/bundled/fops-plugin-memory/package.json +1 -0
- package/src/plugins/bundled/fops-plugin-memory/skills/memory/SKILL.md +58 -0
- package/src/plugins/loader.js +40 -0
- package/src/setup/setup.js +2 -0
- package/src/setup/wizard.js +12 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { CodaClient } from "./client.js";
|
|
2
|
+
import { loadCredentials, runCodaLogin } from "./auth.js";
|
|
3
|
+
|
|
4
|
+
function getClient() {
|
|
5
|
+
const creds = loadCredentials();
|
|
6
|
+
if (!creds) {
|
|
7
|
+
console.error("Not authenticated with Coda. Run: fops coda login");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
return new CodaClient(creds.access_token);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
async register(api) {
|
|
15
|
+
const config = api.config;
|
|
16
|
+
|
|
17
|
+
// ── Register "fops coda" command group ──────────────────────────────
|
|
18
|
+
api.registerCommand((program) => {
|
|
19
|
+
const coda = program
|
|
20
|
+
.command("coda")
|
|
21
|
+
.description("Coda knowledge base");
|
|
22
|
+
|
|
23
|
+
// fops coda login
|
|
24
|
+
coda
|
|
25
|
+
.command("login")
|
|
26
|
+
.description("Authenticate with Coda (API token)")
|
|
27
|
+
.action(async () => {
|
|
28
|
+
const ok = await runCodaLogin();
|
|
29
|
+
if (!ok) process.exit(1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// fops coda whoami
|
|
33
|
+
coda
|
|
34
|
+
.command("whoami")
|
|
35
|
+
.description("Show current Coda user")
|
|
36
|
+
.action(async () => {
|
|
37
|
+
try {
|
|
38
|
+
const client = getClient();
|
|
39
|
+
const user = await client.whoami();
|
|
40
|
+
console.log(`Logged in as: ${user.name} (${user.loginId})`);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.error(err.message);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// fops coda docs [-q <query>]
|
|
48
|
+
coda
|
|
49
|
+
.command("docs")
|
|
50
|
+
.description("List or search Coda docs")
|
|
51
|
+
.option("-q, --query <query>", "Filter docs by name")
|
|
52
|
+
.action(async (opts) => {
|
|
53
|
+
try {
|
|
54
|
+
const client = getClient();
|
|
55
|
+
const result = await client.listDocs(opts.query);
|
|
56
|
+
const docs = result.items || [];
|
|
57
|
+
|
|
58
|
+
if (docs.length === 0) {
|
|
59
|
+
console.log(opts.query ? `No docs matching "${opts.query}"` : "No docs found");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const doc of docs) {
|
|
64
|
+
console.log(`${doc.id} ${doc.name}`);
|
|
65
|
+
}
|
|
66
|
+
console.log(`\n${docs.length} doc(s) found`);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error(err.message);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// fops coda search <query>
|
|
74
|
+
coda
|
|
75
|
+
.command("search <query>")
|
|
76
|
+
.description("Search docs and pages by name")
|
|
77
|
+
.action(async (query) => {
|
|
78
|
+
try {
|
|
79
|
+
const client = getClient();
|
|
80
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
81
|
+
const matches = (text) => words.every((w) => text.toLowerCase().includes(w));
|
|
82
|
+
|
|
83
|
+
// Fetch all docs (no query filter — API only searches doc names)
|
|
84
|
+
const result = await client.listDocs();
|
|
85
|
+
const docs = result.items || [];
|
|
86
|
+
|
|
87
|
+
let found = false;
|
|
88
|
+
|
|
89
|
+
for (const doc of docs) {
|
|
90
|
+
const docMatches = matches(doc.name);
|
|
91
|
+
|
|
92
|
+
let pages = [];
|
|
93
|
+
try {
|
|
94
|
+
const pageResult = await client.listPages(doc.id);
|
|
95
|
+
pages = pageResult.items || [];
|
|
96
|
+
} catch {
|
|
97
|
+
// skip if pages can't be listed
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const matchingPages = pages.filter((p) => matches(p.name));
|
|
101
|
+
|
|
102
|
+
if (!docMatches && matchingPages.length === 0) continue;
|
|
103
|
+
|
|
104
|
+
found = true;
|
|
105
|
+
console.log(`\n── ${doc.name} (${doc.id}) ──`);
|
|
106
|
+
|
|
107
|
+
if (docMatches) {
|
|
108
|
+
// Doc name matches → show all pages
|
|
109
|
+
for (const page of pages) {
|
|
110
|
+
const marker = matches(page.name) ? " ◀" : "";
|
|
111
|
+
console.log(` ${page.id} ${page.name}${marker}`);
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
// Only page names match → show matching pages
|
|
115
|
+
for (const page of matchingPages) {
|
|
116
|
+
console.log(` ${page.id} ${page.name} ◀`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!found) {
|
|
122
|
+
console.log(`No docs or pages matching "${query}"`);
|
|
123
|
+
}
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.error(err.message);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// fops coda read <docId> [pageId]
|
|
131
|
+
coda
|
|
132
|
+
.command("read <docId> [pageId]")
|
|
133
|
+
.description("List pages or read a page as markdown")
|
|
134
|
+
.action(async (docId, pageId) => {
|
|
135
|
+
try {
|
|
136
|
+
const client = getClient();
|
|
137
|
+
|
|
138
|
+
if (!pageId) {
|
|
139
|
+
// List pages
|
|
140
|
+
const result = await client.listPages(docId);
|
|
141
|
+
const pages = result.items || [];
|
|
142
|
+
|
|
143
|
+
if (pages.length === 0) {
|
|
144
|
+
console.log("No pages in this doc");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const page of pages) {
|
|
149
|
+
console.log(`${page.id} ${page.name}`);
|
|
150
|
+
}
|
|
151
|
+
console.log(`\n${pages.length} page(s). Read one with: fops coda read ${docId} <pageId>`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Export page as markdown
|
|
156
|
+
const content = await client.exportPage(docId, pageId);
|
|
157
|
+
console.log(typeof content === "string" ? content : JSON.stringify(content, null, 2));
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.error(err.message);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// fops coda tables <docId> [-t <tableId>] [-q <query>]
|
|
165
|
+
coda
|
|
166
|
+
.command("tables <docId>")
|
|
167
|
+
.description("List tables or query rows")
|
|
168
|
+
.option("-t, --table <tableId>", "Table ID to query rows from")
|
|
169
|
+
.option("-q, --query <query>", "Query to filter rows")
|
|
170
|
+
.action(async (docId, opts) => {
|
|
171
|
+
try {
|
|
172
|
+
const client = getClient();
|
|
173
|
+
|
|
174
|
+
if (!opts.table) {
|
|
175
|
+
// List tables
|
|
176
|
+
const result = await client.listTables(docId);
|
|
177
|
+
const tables = result.items || [];
|
|
178
|
+
|
|
179
|
+
if (tables.length === 0) {
|
|
180
|
+
console.log("No tables in this doc");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for (const t of tables) {
|
|
185
|
+
console.log(`${t.id} ${t.name}`);
|
|
186
|
+
}
|
|
187
|
+
console.log(`\n${tables.length} table(s). Query rows with: fops coda tables ${docId} -t <tableId>`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// List/query rows
|
|
192
|
+
const result = await client.listRows(docId, opts.table, opts.query);
|
|
193
|
+
const rows = result.items || [];
|
|
194
|
+
|
|
195
|
+
if (rows.length === 0) {
|
|
196
|
+
console.log(opts.query ? `No rows matching "${opts.query}"` : "No rows found");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (const row of rows) {
|
|
201
|
+
const values = row.values || {};
|
|
202
|
+
const cells = Object.entries(values)
|
|
203
|
+
.map(([k, v]) => `${k}: ${typeof v === "object" ? JSON.stringify(v) : v}`)
|
|
204
|
+
.join(" | ");
|
|
205
|
+
console.log(`${row.id} ${row.name || "(unnamed)"} ${cells}`);
|
|
206
|
+
}
|
|
207
|
+
console.log(`\n${rows.length} row(s)`);
|
|
208
|
+
} catch (err) {
|
|
209
|
+
console.error(err.message);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// ── Register doctor check ───────────────────────────────────────────
|
|
216
|
+
api.registerDoctorCheck({
|
|
217
|
+
name: "coda",
|
|
218
|
+
fn: async (ok, warn) => {
|
|
219
|
+
const creds = loadCredentials();
|
|
220
|
+
if (!creds) {
|
|
221
|
+
warn("Coda", "Not authenticated. Run: fops coda login");
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const client = new CodaClient(creds.access_token);
|
|
227
|
+
const user = await client.whoami();
|
|
228
|
+
ok("Coda", `connected as ${user.name}`);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
warn("Coda", `Token may be expired: ${err.message}. Run: fops coda login`);
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ── Register RAG knowledge source ─────────────────────────────────
|
|
236
|
+
api.registerKnowledgeSource({
|
|
237
|
+
name: "Coda",
|
|
238
|
+
description: "Search Coda docs and pages by name",
|
|
239
|
+
async search(query) {
|
|
240
|
+
const creds = loadCredentials();
|
|
241
|
+
if (!creds) return [];
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const client = new CodaClient(creds.access_token);
|
|
245
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
246
|
+
const matches = (text) => words.every((w) => text.toLowerCase().includes(w));
|
|
247
|
+
|
|
248
|
+
const result = await client.listDocs();
|
|
249
|
+
const docs = result.items || [];
|
|
250
|
+
const results = [];
|
|
251
|
+
|
|
252
|
+
for (const doc of docs) {
|
|
253
|
+
const docMatches = matches(doc.name);
|
|
254
|
+
|
|
255
|
+
let pages = [];
|
|
256
|
+
try {
|
|
257
|
+
const pageResult = await client.listPages(doc.id);
|
|
258
|
+
pages = pageResult.items || [];
|
|
259
|
+
} catch {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (const page of pages) {
|
|
264
|
+
const pageMatches = matches(page.name);
|
|
265
|
+
if (!docMatches && !pageMatches) continue;
|
|
266
|
+
|
|
267
|
+
const score = docMatches && pageMatches ? 1.0 : docMatches ? 0.7 : 0.5;
|
|
268
|
+
results.push({
|
|
269
|
+
title: `${doc.name} → ${page.name}`,
|
|
270
|
+
content: `Coda page "${page.name}" in doc "${doc.name}".\nRead with: fops coda read ${doc.id} ${page.id}`,
|
|
271
|
+
url: `https://coda.io/d/${doc.id}/_/${page.id}`,
|
|
272
|
+
score,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return results.sort((a, b) => b.score - a.score).slice(0, 10);
|
|
278
|
+
} catch {
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
},
|
|
284
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coda
|
|
3
|
+
description: Search and read Coda knowledge base docs interactively
|
|
4
|
+
---
|
|
5
|
+
## Coda Knowledge Base
|
|
6
|
+
|
|
7
|
+
Use `fops coda` commands to search and read internal documentation, runbooks, architecture decisions, and process docs stored in Coda.
|
|
8
|
+
|
|
9
|
+
### When to Use
|
|
10
|
+
|
|
11
|
+
- User asks about internal processes, onboarding, or "how we do X"
|
|
12
|
+
- User needs a runbook, playbook, or architecture doc
|
|
13
|
+
- User references a doc that might be in Coda
|
|
14
|
+
- User asks "where is the doc for..." or "find the page about..."
|
|
15
|
+
|
|
16
|
+
### Discovery Pattern
|
|
17
|
+
|
|
18
|
+
Always follow: **search → list pages → read page**.
|
|
19
|
+
|
|
20
|
+
1. Search for relevant docs by name:
|
|
21
|
+
```bash
|
|
22
|
+
fops coda docs -q "deployment"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
2. Explore a doc's page structure:
|
|
26
|
+
```bash
|
|
27
|
+
fops coda read <docId>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
3. Read a specific page as markdown:
|
|
31
|
+
```bash
|
|
32
|
+
fops coda read <docId> <pageId>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Commands
|
|
36
|
+
|
|
37
|
+
**List or search docs:**
|
|
38
|
+
```bash
|
|
39
|
+
fops coda docs
|
|
40
|
+
fops coda docs -q "onboarding"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Full-text search across docs (shows page structure):**
|
|
44
|
+
```bash
|
|
45
|
+
fops coda search "incident response"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Read pages from a doc:**
|
|
49
|
+
```bash
|
|
50
|
+
fops coda read <docId> # list all pages
|
|
51
|
+
fops coda read <docId> <pageId> # export page as markdown
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**List tables or query rows:**
|
|
55
|
+
```bash
|
|
56
|
+
fops coda tables <docId> # list tables
|
|
57
|
+
fops coda tables <docId> -t <tableId> # list rows
|
|
58
|
+
fops coda tables <docId> -t <tableId> -q "status:active" # query rows
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Auth:**
|
|
62
|
+
```bash
|
|
63
|
+
fops coda login # authenticate with Coda
|
|
64
|
+
fops coda whoami # check current auth
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Suggesting Commands
|
|
68
|
+
|
|
69
|
+
Always suggest **2 commands** so the user can discover and then read:
|
|
70
|
+
|
|
71
|
+
- Search + read: `fops coda docs -q "runbook"` + `fops coda read <docId>`
|
|
72
|
+
- List + query: `fops coda tables <docId>` + `fops coda tables <docId> -t <tableId>`
|
|
73
|
+
- Broad + narrow: `fops coda search "deploy"` + `fops coda read <docId> <pageId>`
|
|
74
|
+
|
|
75
|
+
### Error Handling
|
|
76
|
+
|
|
77
|
+
If a command returns an auth error, suggest:
|
|
78
|
+
```bash
|
|
79
|
+
fops coda login
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Then retry the original command.
|