@aiagentwiki/cli 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.
Files changed (3) hide show
  1. package/README.md +27 -0
  2. package/dist/index.js +520 -0
  3. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @aiagentwiki/cli
2
+
3
+ Command-line interface for [AgentWiki](https://github.com/digitopvn/agentwiki) — manage notes, documents, media, publish static sites, and query your knowledge base from the terminal.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ # Global install
9
+ npm i -g @aiagentwiki/cli
10
+
11
+ # Or run without installing
12
+ npx @aiagentwiki/cli --help
13
+ ```
14
+
15
+ Requires Node.js `>=20`.
16
+
17
+ ## Usage
18
+
19
+ ```bash
20
+ agentwiki --help
21
+ ```
22
+
23
+ See the [main repository README](https://github.com/digitopvn/agentwiki#readme) for configuration (API URL, auth token) and full command reference.
24
+
25
+ ## License
26
+
27
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,520 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import { existsSync as existsSync2, statSync, readFileSync as readFileSync2, readdirSync } from "fs";
6
+ import { resolve, join as join2, relative, basename, dirname } from "path";
7
+ import { fileURLToPath } from "url";
8
+ import { zipSync } from "fflate";
9
+
10
+ // src/api-client.ts
11
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
12
+ import { join } from "path";
13
+ import { homedir } from "os";
14
+ var CONFIG_DIR = join(homedir(), ".agentwiki");
15
+ var CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
16
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
17
+ function getCredentials() {
18
+ if (!existsSync(CREDENTIALS_FILE)) {
19
+ return { apiUrl: "https://api.agentwiki.cc" };
20
+ }
21
+ return { apiUrl: "https://api.agentwiki.cc", ...JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8")) };
22
+ }
23
+ function saveCredentials(creds) {
24
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
25
+ const existing = getCredentials();
26
+ writeFileSync(CREDENTIALS_FILE, JSON.stringify({ ...existing, ...creds }, null, 2));
27
+ }
28
+ async function apiFetch(path, options) {
29
+ const creds = getCredentials();
30
+ const url = `${creds.apiUrl}/api${path}`;
31
+ const headers = {
32
+ "Content-Type": "application/json",
33
+ ...options?.headers
34
+ };
35
+ if (creds.apiKey) {
36
+ headers["Authorization"] = `Bearer ${creds.apiKey}`;
37
+ } else if (creds.accessToken) {
38
+ headers["Authorization"] = `Bearer ${creds.accessToken}`;
39
+ }
40
+ const res = await fetch(url, { ...options, headers });
41
+ if (!res.ok) {
42
+ const body = await res.text();
43
+ let message = `API error ${res.status}: ${body}`;
44
+ if (res.status === 403) {
45
+ try {
46
+ const parsed = JSON.parse(body);
47
+ message = `403 Forbidden: ${parsed.error || "Permission denied"}
48
+ Hint: ensure your API key has the required scope. Recreate with additional scopes if needed.`;
49
+ } catch {
50
+ }
51
+ }
52
+ throw new Error(message);
53
+ }
54
+ return res.json();
55
+ }
56
+ async function apiUpload(path, formData) {
57
+ const creds = getCredentials();
58
+ const url = `${creds.apiUrl}/api${path}`;
59
+ const headers = {};
60
+ if (creds.apiKey) headers["Authorization"] = `Bearer ${creds.apiKey}`;
61
+ else if (creds.accessToken) headers["Authorization"] = `Bearer ${creds.accessToken}`;
62
+ const res = await fetch(url, { method: "POST", headers, body: formData });
63
+ if (!res.ok) {
64
+ const body = await res.text();
65
+ let message = `Upload error ${res.status}: ${body}`;
66
+ if (res.status === 403) {
67
+ try {
68
+ const parsed = JSON.parse(body);
69
+ message = `403 Forbidden: ${parsed.error || "Permission denied"}
70
+ Hint: ensure your API key has the required scope (e.g. storage:upload, site:upload).`;
71
+ } catch {
72
+ }
73
+ }
74
+ throw new Error(message);
75
+ }
76
+ return res.json();
77
+ }
78
+ async function streamSSE(path, onEvent) {
79
+ const creds = getCredentials();
80
+ const url = `${creds.apiUrl}/api${path}`;
81
+ const headers = {};
82
+ if (creds.apiKey) headers["Authorization"] = `Bearer ${creds.apiKey}`;
83
+ else if (creds.accessToken) headers["Authorization"] = `Bearer ${creds.accessToken}`;
84
+ const res = await fetch(url, { headers });
85
+ if (!res.ok) throw new Error(`SSE error ${res.status}`);
86
+ if (!res.body) return;
87
+ const reader = res.body.getReader();
88
+ const decoder = new TextDecoder();
89
+ let buffer = "";
90
+ while (true) {
91
+ const { done, value } = await reader.read();
92
+ if (done) break;
93
+ buffer += decoder.decode(value, { stream: true });
94
+ const lines = buffer.split("\n");
95
+ buffer = lines.pop() ?? "";
96
+ for (const line of lines) {
97
+ if (line.startsWith("data: ")) {
98
+ try {
99
+ const event = JSON.parse(line.slice(6));
100
+ onEvent(event);
101
+ if (event.type === "complete" || event.type === "error") return;
102
+ } catch {
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ // src/index.ts
110
+ var pkgJson = JSON.parse(
111
+ readFileSync2(join2(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8")
112
+ );
113
+ var program = new Command();
114
+ program.name("agentwiki").description("AgentWiki CLI \u2014 Knowledge management for humans & AI agents").version(pkgJson.version, "-v, --version", "output the current CLI version");
115
+ program.command("login").description("Configure API key for authentication").option("--api-key <key>", "API key (starts with aw_)").option("--url <url>", "API base URL", "https://api.agentwiki.cc").action(async (opts) => {
116
+ if (opts.apiKey) {
117
+ saveCredentials({ apiKey: opts.apiKey, apiUrl: opts.url });
118
+ console.log("\u2713 API key saved");
119
+ try {
120
+ const me = await apiFetch("/auth/me");
121
+ console.log("\u2713 Authenticated as:", JSON.stringify(me, null, 2));
122
+ } catch (err) {
123
+ console.error("\u2717 API key validation failed:", err.message);
124
+ }
125
+ } else {
126
+ console.log("Usage: agentwiki login --api-key aw_xxxxx");
127
+ console.log("Get your API key at https://app.agentwiki.cc/settings/api-keys");
128
+ }
129
+ });
130
+ program.command("whoami").description("Show current user info").action(async () => {
131
+ try {
132
+ const me = await apiFetch("/auth/me");
133
+ console.log(JSON.stringify(me, null, 2));
134
+ } catch (err) {
135
+ console.error("Not authenticated. Run: agentwiki login --api-key <key>");
136
+ }
137
+ });
138
+ var doc = program.command("doc").description("Manage documents");
139
+ doc.command("list").description("List documents").option("--limit <n>", "Max results", "20").option("--offset <n>", "Offset", "0").option("--category <cat>", "Filter by category").option("--tag <tag>", "Filter by tag").option("--json", "Output as JSON").action(async (opts) => {
140
+ const params = new URLSearchParams({
141
+ limit: opts.limit,
142
+ offset: opts.offset
143
+ });
144
+ if (opts.category) params.set("category", opts.category);
145
+ if (opts.tag) params.set("tag", opts.tag);
146
+ const result = await apiFetch(
147
+ `/documents?${params}`
148
+ );
149
+ if (opts.json) {
150
+ console.log(JSON.stringify(result, null, 2));
151
+ } else {
152
+ for (const d of result.data) {
153
+ console.log(`${d.id} ${d.title} (${d.slug})`);
154
+ }
155
+ console.log(`
156
+ ${result.data.length} documents`);
157
+ }
158
+ });
159
+ doc.command("get <id>").description("Get a document by ID").option("--json", "Output as JSON").option("--markdown", "Output markdown content only").action(async (id, opts) => {
160
+ const result = await apiFetch(`/documents/${id}`);
161
+ if (opts.markdown) {
162
+ console.log(result.content);
163
+ } else if (opts.json) {
164
+ console.log(JSON.stringify(result, null, 2));
165
+ } else {
166
+ console.log(`# ${result.title}
167
+ `);
168
+ console.log(result.content);
169
+ if (result.tags?.length) console.log(`
170
+ Tags: ${result.tags.join(", ")}`);
171
+ }
172
+ });
173
+ doc.command("create").description("Create a new document").requiredOption("--title <title>", "Document title").option("--content <content>", "Markdown content").option("--file <path>", "Read content from file").option("--category <cat>", "Category").option("--tags <tags>", "Comma-separated tags").option("--folder <id>", "Folder ID").action(async (opts) => {
174
+ let content = opts.content ?? "";
175
+ if (opts.file) {
176
+ const { readFileSync: readFileSync3 } = await import("fs");
177
+ content = readFileSync3(opts.file, "utf-8");
178
+ }
179
+ const body = { title: opts.title, content };
180
+ if (opts.category) body.category = opts.category;
181
+ if (opts.tags) body.tags = opts.tags.split(",").map((t) => t.trim());
182
+ if (opts.folder) body.folderId = opts.folder;
183
+ const result = await apiFetch("/documents", {
184
+ method: "POST",
185
+ body: JSON.stringify(body)
186
+ });
187
+ console.log("\u2713 Document created:", JSON.stringify(result, null, 2));
188
+ });
189
+ doc.command("update <id>").description("Update a document").option("--title <title>", "New title").option("--content <content>", "New content").option("--file <path>", "Read content from file").option("--category <cat>", "New category").option("--tags <tags>", "New tags (comma-separated)").action(async (id, opts) => {
190
+ const body = {};
191
+ if (opts.title) body.title = opts.title;
192
+ if (opts.content) body.content = opts.content;
193
+ if (opts.file) {
194
+ const { readFileSync: readFileSync3 } = await import("fs");
195
+ body.content = readFileSync3(opts.file, "utf-8");
196
+ }
197
+ if (opts.category) body.category = opts.category;
198
+ if (opts.tags) body.tags = opts.tags.split(",").map((t) => t.trim());
199
+ const result = await apiFetch(`/documents/${id}`, {
200
+ method: "PUT",
201
+ body: JSON.stringify(body)
202
+ });
203
+ console.log("\u2713 Document updated:", JSON.stringify(result, null, 2));
204
+ });
205
+ doc.command("delete <id>").description("Delete a document").action(async (id) => {
206
+ await apiFetch(`/documents/${id}`, { method: "DELETE" });
207
+ console.log("\u2713 Document deleted");
208
+ });
209
+ doc.command("search <query>").description("Search documents and/or uploaded files").option("--type <type>", "Search type: hybrid|keyword|semantic", "hybrid").option("--source <source>", "Search source: docs|storage|all", "docs").option("--limit <n>", "Max results", "10").option("--json", "Output as JSON").action(async (query, opts) => {
210
+ const params = new URLSearchParams({ q: query, type: opts.type, limit: opts.limit, source: opts.source });
211
+ const result = await apiFetch(
212
+ `/search?${params}`
213
+ );
214
+ if (opts.json) {
215
+ console.log(JSON.stringify(result, null, 2));
216
+ } else {
217
+ result.results.forEach((r, i) => {
218
+ if (i > 0) console.log("---");
219
+ console.log(`${r.id} ${r.title}${r.score ? ` (${r.score.toFixed(3)})` : ""}`);
220
+ console.log(` ${r.snippet}`);
221
+ });
222
+ console.log(`
223
+ ${result.results.length} results`);
224
+ }
225
+ });
226
+ doc.command("share <id>").description("Create a share link for a document").option("--expires <days>", "Expiry in days", "30").action(async (id, opts) => {
227
+ const result = await apiFetch("/share/links", {
228
+ method: "POST",
229
+ body: JSON.stringify({ documentId: id, expiresInDays: parseInt(opts.expires) })
230
+ });
231
+ const creds = getCredentials();
232
+ console.log(`\u2713 Share link: ${creds.apiUrl}${result.url}`);
233
+ });
234
+ doc.command("publish <id>").description("Publish a document as HTML").action(async (id) => {
235
+ const result = await apiFetch(`/share/publish/${id}`, {
236
+ method: "POST"
237
+ });
238
+ const creds = getCredentials();
239
+ console.log(`\u2713 Published: ${creds.apiUrl}${result.url}`);
240
+ console.log(` At: ${result.publishedAt}`);
241
+ });
242
+ var folder = program.command("folder").description("Manage folders");
243
+ folder.command("list").description("Get folder tree").option("--json", "Output as JSON").action(async (opts) => {
244
+ const result = await apiFetch("/folders");
245
+ console.log(JSON.stringify(result, null, 2));
246
+ });
247
+ folder.command("create <name>").description("Create a folder").option("--parent <id>", "Parent folder ID").action(async (name, opts) => {
248
+ const body = { name };
249
+ if (opts.parent) body.parentId = opts.parent;
250
+ const result = await apiFetch("/folders", {
251
+ method: "POST",
252
+ body: JSON.stringify(body)
253
+ });
254
+ console.log("\u2713 Folder created:", JSON.stringify(result, null, 2));
255
+ });
256
+ var upload = program.command("upload").description("Manage uploads");
257
+ upload.command("list").description("List uploaded files with extraction status").option("--json", "Output as JSON").action(async (opts) => {
258
+ const result = await apiFetch(
259
+ "/uploads"
260
+ );
261
+ if (opts.json) {
262
+ console.log(JSON.stringify(result, null, 2));
263
+ } else {
264
+ for (const f of result.files) {
265
+ const status = f.extractionStatus ?? "unknown";
266
+ const size = f.sizeBytes < 1024 * 1024 ? `${(f.sizeBytes / 1024).toFixed(1)}KB` : `${(f.sizeBytes / (1024 * 1024)).toFixed(1)}MB`;
267
+ console.log(`${f.id} ${f.filename} ${size} [${status}]`);
268
+ if (f.summary) console.log(` ${f.summary}`);
269
+ }
270
+ console.log(`
271
+ ${result.files.length} files`);
272
+ }
273
+ });
274
+ var sites = program.command("sites").description("Manage hosted static sites");
275
+ sites.command("upload <file>").description("Upload an HTML file or ZIP archive as a hosted static site").option("--description <desc>", "Description of the site").option("--auto-summary", "Auto-generate AI summary of the content").action(async (filePath, opts) => {
276
+ const absPath = resolve(filePath);
277
+ if (!existsSync2(absPath)) {
278
+ console.error("\u2717 Error: File not found:", absPath);
279
+ process.exit(1);
280
+ }
281
+ const stat = statSync(absPath);
282
+ if (!stat.isFile()) {
283
+ console.error("\u2717 Error: Path must be a file (.html or .zip)");
284
+ process.exit(1);
285
+ }
286
+ const filename = basename(absPath);
287
+ if (!filename.endsWith(".html") && !filename.endsWith(".htm") && !filename.endsWith(".zip")) {
288
+ console.error("\u2717 Error: Only .html and .zip files are supported");
289
+ process.exit(1);
290
+ }
291
+ const fileData = readFileSync2(absPath);
292
+ const mimeType = filename.endsWith(".zip") ? "application/zip" : "text/html";
293
+ console.log(`Uploading ${filename} (${(fileData.byteLength / 1024).toFixed(1)}KB)...`);
294
+ const formData = new FormData();
295
+ formData.append("file", new Blob([fileData], { type: mimeType }), filename);
296
+ if (opts.description) formData.append("description", opts.description);
297
+ if (opts.autoSummary) formData.append("autoSummary", "true");
298
+ try {
299
+ const result = await apiUpload(
300
+ "/sites",
301
+ formData
302
+ );
303
+ const creds = getCredentials();
304
+ console.log(`\u2713 Site hosted!`);
305
+ console.log(` ID: ${result.id}`);
306
+ console.log(` URL: ${creds.apiUrl}${result.url}`);
307
+ console.log(` Files: ${result.fileCount}`);
308
+ console.log(` Size: ${(result.totalSizeBytes / 1024).toFixed(1)}KB`);
309
+ } catch (err) {
310
+ console.error("\u2717 Upload failed:", err.message);
311
+ process.exit(1);
312
+ }
313
+ });
314
+ sites.command("list").description("List hosted static sites").option("--search <query>", "Search by filename or description").option("--limit <n>", "Max results", "20").option("--offset <n>", "Offset", "0").option("--json", "Output as JSON").action(async (opts) => {
315
+ const params = new URLSearchParams({ limit: opts.limit, offset: opts.offset });
316
+ if (opts.search) params.set("search", opts.search);
317
+ const result = await apiFetch(
318
+ `/sites?${params}`
319
+ );
320
+ if (opts.json) {
321
+ console.log(JSON.stringify(result, null, 2));
322
+ } else {
323
+ if (!result.sites.length) {
324
+ console.log("No hosted sites found.");
325
+ return;
326
+ }
327
+ for (const s of result.sites) {
328
+ const size = s.totalSizeBytes < 1024 * 1024 ? `${(s.totalSizeBytes / 1024).toFixed(1)}KB` : `${(s.totalSizeBytes / (1024 * 1024)).toFixed(1)}MB`;
329
+ console.log(`${s.id} ${s.filename} ${size} [${s.status}]`);
330
+ if (s.description) console.log(` ${s.description}`);
331
+ }
332
+ console.log(`
333
+ ${result.total} sites total`);
334
+ }
335
+ });
336
+ sites.command("get <id>").description("Get hosted site details").option("--json", "Output as JSON").action(async (id, opts) => {
337
+ const result = await apiFetch(
338
+ `/sites/${id}`
339
+ );
340
+ if (opts.json) {
341
+ console.log(JSON.stringify(result, null, 2));
342
+ } else {
343
+ const creds = getCredentials();
344
+ console.log(`ID: ${result.id}`);
345
+ console.log(`File: ${result.filename} (${result.siteType})`);
346
+ console.log(`URL: ${creds.apiUrl}${result.url}`);
347
+ console.log(`Entry: ${result.entryPath}`);
348
+ console.log(`Files: ${result.fileCount}`);
349
+ console.log(`Size: ${(result.totalSizeBytes / 1024).toFixed(1)}KB`);
350
+ console.log(`Status: ${result.status}`);
351
+ if (result.description) console.log(`Description: ${result.description}`);
352
+ if (result.summary) console.log(`Summary: ${result.summary}`);
353
+ console.log(`Created: ${new Date(result.createdAt).toLocaleString()}`);
354
+ }
355
+ });
356
+ sites.command("delete <id>").description("Delete a hosted static site").action(async (id) => {
357
+ await apiFetch(`/sites/${id}`, { method: "DELETE" });
358
+ console.log("\u2713 Site deleted");
359
+ });
360
+ program.command("search-images <query>").description("Search uploaded images by text description").option("--type <type>", "Search type: hybrid|keyword|semantic", "hybrid").option("--limit <n>", "Max results", "10").option("--doc-id <id>", "Filter by linked document ID").option("--date-from <date>", "Filter images uploaded after this date (ISO 8601)").option("--date-to <date>", "Filter images uploaded before this date (ISO 8601)").option("--json", "Output as JSON").action(async (query, opts) => {
361
+ const params = new URLSearchParams({ q: query, type: opts.type, limit: opts.limit });
362
+ if (opts.docId) params.set("documentId", opts.docId);
363
+ if (opts.dateFrom) params.set("dateFrom", opts.dateFrom);
364
+ if (opts.dateTo) params.set("dateTo", opts.dateTo);
365
+ const result = await apiFetch(`/search/images?${params}`);
366
+ if (opts.json) {
367
+ console.log(JSON.stringify(result, null, 2));
368
+ } else {
369
+ if (!result.results.length) {
370
+ console.log("No images found.");
371
+ return;
372
+ }
373
+ const creds = getCredentials();
374
+ result.results.forEach((r, i) => {
375
+ if (i > 0) console.log("---");
376
+ const size = r.sizeBytes < 1024 * 1024 ? `${(r.sizeBytes / 1024).toFixed(1)}KB` : `${(r.sizeBytes / (1024 * 1024)).toFixed(1)}MB`;
377
+ const score = r.score ? ` (${r.score.toFixed(3)})` : "";
378
+ console.log(`${r.id} ${r.filename} ${size}${score}`);
379
+ console.log(` ${creds.apiUrl}${r.fileUrl}`);
380
+ if (r.summary) console.log(` ${r.summary}`);
381
+ else if (r.snippet) console.log(` ${r.snippet}`);
382
+ });
383
+ console.log(`
384
+ ${result.results.length} images found`);
385
+ }
386
+ });
387
+ program.command("tags").description("List all tags").action(async () => {
388
+ const result = await apiFetch("/tags");
389
+ for (const t of result.tags) {
390
+ console.log(`${t.tag} (${t.count})`);
391
+ }
392
+ });
393
+ var importCmd = program.command("import").description("Import documents from other tools");
394
+ importCmd.command("obsidian <vault-path>").description("Import Obsidian vault (directory will be zipped and uploaded)").option("--folder <id>", "Target folder ID in AgentWiki").option("--json", "JSON output").action(async (vaultPath, opts) => {
395
+ const absPath = resolve(vaultPath);
396
+ if (!existsSync2(absPath) || !statSync(absPath).isDirectory()) {
397
+ console.error("\u2717 Error: Vault path must be an existing directory");
398
+ process.exit(1);
399
+ }
400
+ console.log("Zipping vault...");
401
+ const zipBuffer = zipDirectory(absPath);
402
+ console.log(`Uploading ${(zipBuffer.byteLength / (1024 * 1024)).toFixed(1)}MB...`);
403
+ const formData = new FormData();
404
+ formData.append("file", new Blob([zipBuffer], { type: "application/zip" }), "vault.zip");
405
+ formData.append("source", "obsidian");
406
+ if (opts.folder) formData.append("targetFolderId", opts.folder);
407
+ const { jobId } = await apiUpload("/import", formData);
408
+ console.log(`Import started: ${jobId}
409
+ `);
410
+ await streamImportProgress(jobId, opts.json ?? false);
411
+ });
412
+ importCmd.command("notion <zip-path>").description("Import Notion export ZIP file").option("--folder <id>", "Target folder ID in AgentWiki").option("--json", "JSON output").action(async (zipPath, opts) => {
413
+ const absPath = resolve(zipPath);
414
+ if (!existsSync2(absPath) || !absPath.endsWith(".zip")) {
415
+ console.error("\u2717 Error: Must provide a valid .zip file");
416
+ process.exit(1);
417
+ }
418
+ const fileData = readFileSync2(absPath);
419
+ console.log(`Uploading ${(fileData.byteLength / (1024 * 1024)).toFixed(1)}MB...`);
420
+ const formData = new FormData();
421
+ formData.append("file", new Blob([fileData], { type: "application/zip" }), "notion-export.zip");
422
+ formData.append("source", "notion");
423
+ if (opts.folder) formData.append("targetFolderId", opts.folder);
424
+ const { jobId } = await apiUpload("/import", formData);
425
+ console.log(`Import started: ${jobId}
426
+ `);
427
+ await streamImportProgress(jobId, opts.json ?? false);
428
+ });
429
+ importCmd.command("lark").description("Import from LarkSuite workspace via API").requiredOption("--token <token>", "Lark access token").option("--space <id>", "Lark space ID").option("--folder <id>", "Target folder ID in AgentWiki").option("--json", "JSON output").action(async (opts) => {
430
+ const body = { token: opts.token };
431
+ if (opts.space) body.spaceId = opts.space;
432
+ if (opts.folder) body.targetFolderId = opts.folder;
433
+ const { jobId } = await apiFetch("/import/lark", {
434
+ method: "POST",
435
+ body: JSON.stringify(body)
436
+ });
437
+ console.log(`Import started: ${jobId}
438
+ `);
439
+ await streamImportProgress(jobId, opts.json ?? false);
440
+ });
441
+ importCmd.command("history").description("List past imports").option("--json", "JSON output").action(async (opts) => {
442
+ const jobs = await apiFetch("/import");
443
+ if (opts.json) {
444
+ console.log(JSON.stringify(jobs, null, 2));
445
+ return;
446
+ }
447
+ if (!jobs.length) {
448
+ console.log("No imports found.");
449
+ return;
450
+ }
451
+ console.log("ID SOURCE STATUS DOCS DATE");
452
+ for (const j of jobs) {
453
+ const status = j.status === "completed" ? "\u2713 done" : j.status === "failed" ? "\u2717 failed" : j.status;
454
+ const date = new Date(j.createdAt).toLocaleDateString();
455
+ console.log(`${j.id} ${j.source.padEnd(10)} ${status.padEnd(11)} ${j.processedDocs}/${j.totalDocs} ${date}`);
456
+ }
457
+ });
458
+ async function streamImportProgress(jobId, jsonMode) {
459
+ await streamSSE(`/import/${jobId}/progress`, (event) => {
460
+ if (jsonMode) {
461
+ console.log(JSON.stringify(event));
462
+ return;
463
+ }
464
+ switch (event.type) {
465
+ case "start":
466
+ console.log(`Importing ${event.total} documents...
467
+ `);
468
+ break;
469
+ case "folder":
470
+ console.log(` \u{1F4C1} ${event.name}`);
471
+ break;
472
+ case "document":
473
+ console.log(` \u2713 [${event.current}/${event.total}] ${event.name}`);
474
+ break;
475
+ case "attachment":
476
+ console.log(` \u{1F4CE} ${event.name}`);
477
+ break;
478
+ case "link-resolve":
479
+ console.log(` \u{1F517} Resolved ${event.current} internal links`);
480
+ break;
481
+ case "error":
482
+ console.error(` \u2717 ${event.message}`);
483
+ break;
484
+ case "complete": {
485
+ const s = event.summary;
486
+ if (s) {
487
+ console.log(`
488
+ \u2713 Import complete!`);
489
+ console.log(` Folders: ${s.foldersCreated}`);
490
+ console.log(` Documents: ${s.documentsCreated}`);
491
+ console.log(` Images: ${s.attachmentsUploaded}`);
492
+ console.log(` Links: ${s.linksResolved}`);
493
+ const errors = s.errors;
494
+ if (errors?.length) console.log(` Errors: ${errors.length}`);
495
+ console.log(` Duration: ${(s.durationMs / 1e3).toFixed(1)}s`);
496
+ }
497
+ break;
498
+ }
499
+ }
500
+ });
501
+ }
502
+ function zipDirectory(dirPath) {
503
+ const files = {};
504
+ function walk(dir) {
505
+ for (const entry of readdirSync(dir)) {
506
+ if (entry === ".obsidian" || entry === ".trash" || entry === ".DS_Store") continue;
507
+ const fullPath = join2(dir, entry);
508
+ const relPath = relative(dirPath, fullPath).replace(/\\/g, "/");
509
+ if (statSync(fullPath).isDirectory()) {
510
+ walk(fullPath);
511
+ } else {
512
+ files[relPath] = new Uint8Array(readFileSync2(fullPath));
513
+ }
514
+ }
515
+ }
516
+ walk(dirPath);
517
+ const zipped = zipSync(files);
518
+ return zipped.buffer.slice(zipped.byteOffset, zipped.byteOffset + zipped.byteLength);
519
+ }
520
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@aiagentwiki/cli",
3
+ "version": "0.1.0",
4
+ "description": "AgentWiki CLI — manage notes, docs, media, publish sites, and query your knowledge base from the terminal.",
5
+ "keywords": [
6
+ "agentwiki",
7
+ "cli",
8
+ "knowledge-base",
9
+ "notes",
10
+ "documentation"
11
+ ],
12
+ "homepage": "https://github.com/digitopvn/agentwiki#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/digitopvn/agentwiki/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/digitopvn/agentwiki.git",
19
+ "directory": "packages/cli"
20
+ },
21
+ "license": "MIT",
22
+ "type": "module",
23
+ "bin": {
24
+ "agentwiki": "dist/index.js"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md"
29
+ ],
30
+ "publishConfig": {
31
+ "access": "public",
32
+ "provenance": true
33
+ },
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "dev": "tsup --watch",
37
+ "lint": "eslint src/",
38
+ "type-check": "tsc --noEmit",
39
+ "prepublishOnly": "pnpm run build"
40
+ },
41
+ "dependencies": {
42
+ "commander": "^13.1.0",
43
+ "fflate": "^0.8.2"
44
+ },
45
+ "devDependencies": {
46
+ "@agentwiki/shared": "workspace:*",
47
+ "@types/node": "^22.0.0",
48
+ "eslint": "^9.0.0",
49
+ "tsup": "^8.3.5",
50
+ "typescript-eslint": "^8.0.0"
51
+ },
52
+ "engines": {
53
+ "node": ">=20.0.0"
54
+ }
55
+ }