geowiki-cli 1.0.1

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,90 @@
1
+ /**
2
+ * Dashboard statistics commands
3
+ * Usage: geo stats [metrics] [options]
4
+ */
5
+
6
+ import { hasFlag } from '../utils/args.js';
7
+ import { apiGet } from '../utils/api.js';
8
+ import { outputJson } from '../utils/output.js';
9
+
10
+ export async function stats(args) {
11
+ if (args.includes('--help') || args.includes('-h')) {
12
+ printStatsHelp();
13
+ return;
14
+ }
15
+
16
+ const [action, ...subArgs] = args;
17
+
18
+ if (action === 'metrics') {
19
+ await metrics(subArgs);
20
+ return;
21
+ }
22
+
23
+ if (action && action !== '--json') {
24
+ console.error(`Unknown action: ${action}. Use 'geo stats --help' for usage.`);
25
+ process.exit(1);
26
+ }
27
+
28
+ // Default: show dashboard stats (geo stats)
29
+ const json = hasFlag(args, '--json');
30
+
31
+ const data = await apiGet('/api/v1/admin/stats');
32
+ const statsData = data.data || {};
33
+
34
+ if (outputJson(statsData, json)) return;
35
+
36
+ console.log('\n=== Dashboard Statistics ===\n');
37
+ if (statsData.documents !== undefined) console.log(` Documents: ${statsData.documents}`);
38
+ if (statsData.categories !== undefined) console.log(` Categories: ${statsData.categories}`);
39
+ if (statsData.tags !== undefined) console.log(` Tags: ${statsData.tags}`);
40
+ if (statsData.feedback !== undefined) console.log(` Feedback: ${statsData.feedback}`);
41
+ if (Array.isArray(statsData.recentDocs) && statsData.recentDocs.length > 0) {
42
+ console.log(` Recent Documents (7d): ${statsData.recentDocs.length}`);
43
+ for (const doc of statsData.recentDocs) {
44
+ console.log(` - ${doc.title || doc.slug} (${doc.category || ""})`);
45
+ }
46
+ }
47
+ console.log('');
48
+ }
49
+
50
+ async function metrics(args) {
51
+ if (args.includes('--help') || args.includes('-h')) {
52
+ console.log(`
53
+ Usage: geo stats metrics [options]
54
+
55
+ Display system metrics (detailed statistics).
56
+
57
+ Options:
58
+ --json Output as JSON
59
+ --help Show this help`);
60
+ return;
61
+ }
62
+
63
+ const json = hasFlag(args, '--json');
64
+
65
+ const data = await apiGet('/api/v1/admin/metrics');
66
+ const metricsData = data.data || {};
67
+
68
+ if (outputJson(metricsData, json)) return;
69
+
70
+ console.log('\n=== System Metrics ===\n');
71
+ console.log(JSON.stringify(metricsData, null, 2));
72
+ console.log('');
73
+ }
74
+
75
+ function printStatsHelp() {
76
+ console.log(`
77
+ Usage: geo stats [action] [options]
78
+
79
+ Actions:
80
+ metrics Display detailed system metrics
81
+
82
+ Options:
83
+ --json Output as JSON
84
+ --help Show this help
85
+
86
+ Examples:
87
+ geo stats Show dashboard statistics
88
+ geo stats metrics Show detailed system metrics
89
+ geo stats --json Output as JSON`);
90
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Tag management commands
3
+ * Usage: geo tag [list|create|update|delete] [options]
4
+ */
5
+
6
+ import { extractArg, hasFlag } from '../utils/args.js';
7
+ import { apiGet, apiPost, apiPut, apiDelete } from '../utils/api.js';
8
+ import { outputJson, outputSuccess, confirmDelete } from '../utils/output.js';
9
+ import { dispatch } from '../utils/dispatch.js';
10
+
11
+ export async function tag(args) {
12
+ const result = dispatch(args, actions, ['list', 'create', 'update', 'delete'], printTagHelp);
13
+ if (result) await actions[result.action](result.subArgs);
14
+ }
15
+
16
+ const actions = {
17
+ async list(args) {
18
+ const json = hasFlag(args, '--json');
19
+ const lang = extractArg(args, '--lang') || extractArg(args, '-l') || 'zh';
20
+
21
+ let url = '/api/v1/admin/tags';
22
+ if (lang) url += `?lang=${lang}`;
23
+
24
+ const data = await apiGet(url);
25
+ const tags = data.data || [];
26
+
27
+ if (outputJson(tags, json)) return;
28
+
29
+ console.log(`\nTags (${tags.length} found):\n`);
30
+ tags.forEach(t => {
31
+ const displayName = lang === 'en' ? (t.name_en || t.name) :
32
+ lang === 'jp' ? (t.name_jp || t.name) : t.name;
33
+ console.log(` ${t.slug.padEnd(30)} | ${displayName}`);
34
+ if (t.name_en || t.name_jp) {
35
+ console.log(` ${''.padEnd(30)} | EN: ${t.name_en || '-'} | JP: ${t.name_jp || '-'}`);
36
+ }
37
+ });
38
+ },
39
+
40
+ async create(args) {
41
+ const name = extractArg(args, '--name') || extractArg(args, '-n');
42
+ const slug = extractArg(args, '--slug') || extractArg(args, '-s');
43
+ const nameEn = extractArg(args, '--name-en');
44
+ const nameJp = extractArg(args, '--name-jp');
45
+ const json = hasFlag(args, '--json');
46
+
47
+ if (!name || !slug) {
48
+ console.error('Error: --name and --slug are required');
49
+ process.exit(1);
50
+ }
51
+
52
+ const payload = { name, slug };
53
+ if (nameEn) payload.name_en = nameEn;
54
+ if (nameJp) payload.name_jp = nameJp;
55
+
56
+ await apiPost('/api/v1/admin/tags', payload);
57
+ outputSuccess(`Tag created: ${name} (${slug})`, json, { slug, name, name_en: nameEn, name_jp: nameJp });
58
+ },
59
+
60
+ async update(args) {
61
+ const slug = extractArg(args, '--slug') || extractArg(args, '-s');
62
+ const name = extractArg(args, '--name') || extractArg(args, '-n');
63
+ const nameEn = extractArg(args, '--name-en');
64
+ const nameJp = extractArg(args, '--name-jp');
65
+ const json = hasFlag(args, '--json');
66
+
67
+ if (!slug) {
68
+ console.error('Error: --slug is required');
69
+ process.exit(1);
70
+ }
71
+
72
+ if (!name && !nameEn && !nameJp) {
73
+ console.error('Error: at least one of --name, --name-en, or --name-jp is required');
74
+ process.exit(1);
75
+ }
76
+
77
+ const payload = {};
78
+ if (name) payload.name = name;
79
+ if (nameEn) payload.name_en = nameEn;
80
+ if (nameJp) payload.name_jp = nameJp;
81
+
82
+ await apiPut(`/api/v1/admin/tags/${encodeURIComponent(slug)}`, payload);
83
+ outputSuccess(`Tag updated: ${slug}`, json, { slug, ...payload });
84
+ },
85
+
86
+ async delete(args) {
87
+ const slug = extractArg(args, '--slug') || extractArg(args, '-s');
88
+ const json = hasFlag(args, '--json');
89
+
90
+ if (!slug) {
91
+ console.error('Error: --slug is required');
92
+ process.exit(1);
93
+ }
94
+
95
+ const confirmed = await confirmDelete(`Delete tag "${slug}"?`, args);
96
+ if (!confirmed) {
97
+ console.log('Cancelled.');
98
+ return;
99
+ }
100
+
101
+ await apiDelete(`/api/v1/admin/tags/${encodeURIComponent(slug)}`);
102
+ outputSuccess(`Tag deleted: ${slug}`, json, { deleted: slug });
103
+ }
104
+ };
105
+
106
+ function printTagHelp() {
107
+ console.log(`
108
+ Usage: geo tag <action> [options]
109
+
110
+ Actions:
111
+ list List all tags
112
+ create Create a new tag
113
+ update Update tag names (multi-language)
114
+ delete Delete a tag
115
+
116
+ Options:
117
+ --slug,-s Tag slug (kebab-case)
118
+ --name,-n Display name (Chinese)
119
+ --name-en English name
120
+ --name-jp Japanese name
121
+ --lang,-l Language for list display (zh/en/jp)
122
+ --json Machine-readable JSON output
123
+
124
+ Examples:
125
+ geo tag list --json
126
+ geo tag list --lang en
127
+ geo tag create --name "CAN Bus" --slug can-bus --name-en "CAN Bus" --name-jp "CANバス"
128
+ geo tag update --slug can-bus --name "CAN 总线" --name-en "CAN Bus" --name-jp "CANバス"
129
+ geo tag delete --slug can-bus
130
+ `);
131
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * User management commands
3
+ * Usage: geo user [list|create|update|delete|reset-password] [options]
4
+ */
5
+
6
+ import { extractArg, hasFlag } from '../utils/args.js';
7
+ import { apiGet, apiPost, apiPut, apiDelete } from '../utils/api.js';
8
+ import { outputJson, outputSuccess, confirmDelete } from '../utils/output.js';
9
+ import { dispatch } from '../utils/dispatch.js';
10
+
11
+ export async function user(args) {
12
+ const result = dispatch(args, actions, ['list', 'create', 'update', 'delete', 'reset-password', 'token'], printUserHelp);
13
+ if (result) await actions[result.action](result.subArgs);
14
+ }
15
+
16
+ const actions = {
17
+ async list(args) {
18
+ const json = hasFlag(args, '--json');
19
+
20
+ const data = await apiGet('/api/v1/admin/users');
21
+ const users = data.data || [];
22
+
23
+ if (outputJson(users, json)) return;
24
+
25
+ console.log(`\nUsers (${users.length} found):\n`);
26
+ users.forEach(u => {
27
+ console.log(` ${(u.username || '').padEnd(20)} | role=${(u.role || '').padEnd(10)} | id=${u.id || u._id || ''}`);
28
+ });
29
+ console.log('\nTip: Use --username to lookup user (e.g. geo user delete --username "editor1")');
30
+ },
31
+
32
+ /**
33
+ * Resolve --id from --username if needed (looks up user list)
34
+ */
35
+ async resolveId(args) {
36
+ let id = extractArg(args, '--id');
37
+ if (id) return id;
38
+
39
+ const username = extractArg(args, '--username');
40
+ if (!username) return null;
41
+
42
+ // Look up user by username
43
+ const data = await apiGet('/api/v1/admin/users');
44
+ const users = data.data || [];
45
+ const user = users.find(u => u.username === username);
46
+ if (user) return user.id || user._id;
47
+ console.error(`Error: user "${username}" not found`);
48
+ process.exit(1);
49
+ },
50
+
51
+ async create(args) {
52
+ const username = extractArg(args, '--username');
53
+ const password = extractArg(args, '--password');
54
+ const role = extractArg(args, '--role') || 'editor';
55
+ const json = hasFlag(args, '--json');
56
+
57
+ if (!username || !password) {
58
+ console.error('Error: --username and --password are required');
59
+ process.exit(1);
60
+ }
61
+
62
+ const data = await apiPost('/api/v1/admin/users', { username, password, role });
63
+ const user = data.data || {};
64
+ outputSuccess(`User created: ${username} (role: ${role})`, json, { username, role, id: user.id || user._id });
65
+ },
66
+
67
+ async update(args) {
68
+ const id = await actions.resolveId(args);
69
+ const role = extractArg(args, '--role');
70
+ const newUsername = extractArg(args, '--new-username');
71
+ const json = hasFlag(args, '--json');
72
+
73
+ if (!id) {
74
+ console.error('Error: --id or --username is required');
75
+ process.exit(1);
76
+ }
77
+
78
+ const payload = {};
79
+ if (role != null) payload.role = role; // extractArg returns null when not found
80
+ if (newUsername != null) payload.username = newUsername;
81
+
82
+ if (Object.keys(payload).length === 0) {
83
+ console.error('Error: nothing to update (use --role or --username)');
84
+ process.exit(1);
85
+ }
86
+
87
+ await apiPut(`/api/v1/admin/users/${encodeURIComponent(id)}`, payload);
88
+ outputSuccess(`User updated: ${id}`, json, { id, ...payload });
89
+ },
90
+
91
+ async delete(args) {
92
+ const id = await actions.resolveId(args);
93
+ const json = hasFlag(args, '--json');
94
+
95
+ if (!id) {
96
+ console.error('Error: --id or --username is required');
97
+ process.exit(1);
98
+ }
99
+
100
+ const confirmed = await confirmDelete(`Delete user "${id}"?`, args);
101
+ if (!confirmed) {
102
+ console.log('Cancelled.');
103
+ return;
104
+ }
105
+
106
+ await apiDelete(`/api/v1/admin/users/${encodeURIComponent(id)}`);
107
+ outputSuccess(`User deleted: ${id}`, json, { deleted: id });
108
+ },
109
+
110
+ async 'reset-password'(args) {
111
+ const id = await actions.resolveId(args);
112
+ const json = hasFlag(args, '--json');
113
+
114
+ if (!id) {
115
+ console.error('Error: --id or --username is required');
116
+ process.exit(1);
117
+ }
118
+
119
+ const data = await apiPost(`/api/v1/admin/users/${encodeURIComponent(id)}/reset-password`);
120
+ const result = data.data || {};
121
+ const newPassword = result.password || result.newPassword;
122
+ if (newPassword) {
123
+ outputSuccess(`Password reset for user ${id}`, json, { id, newPassword });
124
+ } else {
125
+ outputSuccess(`Password reset for user ${id}`, json, { id });
126
+ }
127
+ },
128
+
129
+ async token(args) {
130
+ const action = args.find(a => ['list', 'delete'].includes(a));
131
+ const tokenId = extractArg(args, '--id');
132
+ const json = hasFlag(args, '--json');
133
+
134
+ if (!action) {
135
+ console.error('Error: token action required (list/delete)');
136
+ console.error('Token creation is only available in the web admin panel (Settings → API Token).');
137
+ process.exit(1);
138
+ }
139
+
140
+ if (action === 'list') {
141
+ const data = await apiGet('/api/v1/auth/tokens');
142
+ const tokens = data.data || [];
143
+ if (outputJson(tokens, json)) return;
144
+ if (tokens.length === 0) {
145
+ console.log('\nNo API tokens found.\n');
146
+ return;
147
+ }
148
+ console.log(`\nAPI Tokens (${tokens.length}):\n`);
149
+ tokens.forEach(t => {
150
+ console.log(` ${(t.name || '').padEnd(20)} | id=${t.id || ''} | created=${t.createdAt || ''}`);
151
+ });
152
+ console.log('');
153
+ } else if (action === 'delete') {
154
+ if (!tokenId) {
155
+ console.error('Error: --id is required');
156
+ process.exit(1);
157
+ }
158
+ await apiDelete(`/api/v1/auth/tokens/${tokenId}`);
159
+ outputSuccess(`Token ${tokenId} revoked`, json);
160
+ }
161
+ }
162
+ };
163
+
164
+ function printUserHelp() {
165
+ console.log(`
166
+ Usage: geo user <action> [options]
167
+
168
+ Actions:
169
+ list List all users
170
+ create Create a new user
171
+ update Update user role or username
172
+ delete Delete a user
173
+ reset-password Reset a user's password
174
+ token Manage API tokens (list/delete, create via web UI only)
175
+
176
+ Options:
177
+ --id User ID (for update/delete/reset-password)
178
+ --username Username (for create; or lookup ID for update/delete/reset-password)
179
+ --new-username New username (for update only)
180
+ --password Password (required for create)
181
+ --role User role: admin, editor, viewer (default: editor)
182
+ --yes, -y Skip confirmation prompt (for delete)
183
+ --json Machine-readable JSON output
184
+
185
+ Examples:
186
+ geo user list --json
187
+ geo user create --username "editor1" --password "Pass123" --role editor
188
+ geo user update --username "editor1" --role admin
189
+ geo user update --username "editor1" --new-username "editor2"
190
+ geo user delete --username "editor1" --yes
191
+ geo user reset-password --username "editor1"
192
+ geo user token list --json
193
+ geo user token delete --id "token-id" --yes`);
194
+
195
+ }
package/cli/index.js ADDED
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * GEO Wiki CLI - Agent-facing command line tool
4
+ */
5
+
6
+ import { readFileSync } from 'fs';
7
+ import { fileURLToPath } from 'url';
8
+ import { dirname, join } from 'path';
9
+ import { login } from './commands/login.js';
10
+ import { doc } from './commands/doc.js';
11
+ import { media } from './commands/media.js';
12
+ import { geo } from './commands/geo.js';
13
+ import { search } from './commands/search.js';
14
+ import { category } from './commands/category.js';
15
+ import { tag } from './commands/tag.js';
16
+ import { config } from './commands/config.js';
17
+ import { user } from './commands/user.js';
18
+ import { draft } from './commands/draft.js';
19
+ import { stats } from './commands/stats.js';
20
+ import { feedback } from './commands/feedback.js';
21
+ import { guestbook } from './commands/guestbook.js';
22
+ import { config as appConfig } from './utils/config.js';
23
+
24
+ const __dirname = dirname(fileURLToPath(import.meta.url));
25
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
26
+
27
+ const commands = { login, doc, media, geo, search, category, tag, config, user, draft, stats, feedback, guestbook };
28
+
29
+ function isHelpFlag(args) {
30
+ return args.includes('--help') || args.includes('-h');
31
+ }
32
+
33
+ async function main() {
34
+ const args = process.argv.slice(2);
35
+
36
+ // Handle --version flag
37
+ if (args.includes('--version') || args.includes('-v')) {
38
+ console.log(`geowiki-cli v${pkg.version}`);
39
+ return;
40
+ }
41
+
42
+ if (args.length === 0 || (args.length === 1 && isHelpFlag(args))) {
43
+ printHelp();
44
+ return;
45
+ }
46
+
47
+ const [cmd, ...subArgs] = args;
48
+
49
+ if (cmd === 'login') {
50
+ await login(subArgs);
51
+ return;
52
+ }
53
+
54
+ if (cmd === 'logout') {
55
+ appConfig.clearToken();
56
+ console.log('Logged out successfully.');
57
+ return;
58
+ }
59
+
60
+ if (cmd === 'status') {
61
+ await status();
62
+ return;
63
+ }
64
+
65
+ const token = appConfig.getToken();
66
+ if (!token && !['login'].includes(cmd) && !isHelpFlag(subArgs)) {
67
+ console.error('Not logged in. Run: geo login --url URL --user USER --pass PASS');
68
+ process.exit(1);
69
+ }
70
+
71
+ if (commands[cmd]) {
72
+ await commands[cmd](subArgs);
73
+ } else {
74
+ console.error(`Unknown command: ${cmd}`);
75
+ printHelp();
76
+ process.exit(1);
77
+ }
78
+ }
79
+
80
+ function printHelp() {
81
+ console.log(`
82
+ GEO Wiki CLI - AI Agent Wiki Management Tool
83
+
84
+ Usage:
85
+ geo <command> [options]
86
+
87
+ Commands:
88
+ login Authenticate and save session
89
+ logout Clear stored credentials
90
+ status Show current connection status
91
+ doc Document operations (create/list/get/update/delete/reorder)
92
+ category Category management (list/create/update/reorder/delete)
93
+ tag Tag management (list/create/delete)
94
+ config Site configuration (get/update)
95
+ user User management (list/create/update/delete/reset-password)
96
+ draft Draft management (list/get/publish/delete)
97
+ stats Dashboard statistics and metrics
98
+ feedback Feedback management (list/delete/promote)
99
+ guestbook Guestbook management (list/toggle/update/delete)
100
+ media Media operations (upload/list/delete)
101
+ geo GEO status, manifest, and report
102
+ search Search documents
103
+
104
+ All commands support --json flag for structured agent output.
105
+
106
+ Examples:
107
+ geo login --url http://your-site:9002 --user <username> --pass <password>
108
+ geo category create --name "CAN MOTION" --slug can-motion
109
+ geo category list --json
110
+ geo doc create --file ./product.md --category can-motion --lang zh
111
+ geo doc list --category can-motion --json
112
+ geo doc reorder --orders "slug1:0,slug2:1"
113
+ geo tag create --name "CAN Bus" --slug can-bus
114
+ geo config get --json
115
+ geo user list --json
116
+ geo draft list --json
117
+ geo stats --json
118
+ geo feedback list --json
119
+ geo guestbook list --json
120
+ geo geo status
121
+ geo geo report --json
122
+ geo search "CAN bus"
123
+
124
+ For help on specific command:
125
+ geo <command> --help
126
+ `);
127
+ }
128
+
129
+ async function status() {
130
+ const cookie = appConfig.getCookie();
131
+ const apiToken = appConfig.getApiToken();
132
+ const baseUrl = appConfig.getBaseUrl();
133
+
134
+ if (!cookie && !apiToken) {
135
+ console.log('Status: Not logged in');
136
+ return;
137
+ }
138
+
139
+ const authType = apiToken ? 'API Token' : 'httpOnly Cookie (geo_wiki_token)';
140
+ console.log(`Status: Logged in`);
141
+ console.log(`Base URL: ${baseUrl || 'Not set'}`);
142
+ console.log(`Auth: ${authType}`);
143
+
144
+ if (!baseUrl) {
145
+ console.log('Connection: Unknown (base URL not set)');
146
+ return;
147
+ }
148
+
149
+ try {
150
+ // Build headers based on auth type
151
+ const headers = {};
152
+ if (apiToken) {
153
+ headers['Authorization'] = `Bearer ${apiToken}`;
154
+ } else {
155
+ headers['Cookie'] = cookie;
156
+ }
157
+
158
+ const res = await fetch(`${baseUrl}/api/v1/auth/me`, { headers });
159
+ if (res.ok) {
160
+ const data = await res.json().catch(() => ({}));
161
+ const user = data?.data?.user;
162
+ if (user) {
163
+ console.log(`Connection: OK (user: ${user.username}, role: ${user.role})`);
164
+ } else {
165
+ console.log('Connection: OK');
166
+ }
167
+ } else if (res.status === 401) {
168
+ console.log('Connection: Session expired (please run `geo login` again)');
169
+ } else {
170
+ console.log(`Connection: Failed (HTTP ${res.status})`);
171
+ }
172
+ } catch (e) {
173
+ console.log(`Connection: Error - ${e.message}`);
174
+ }
175
+ }
176
+
177
+ export { main };
178
+ export default main;
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "geowiki-cli",
3
+ "version": "3.0.49",
4
+ "description": "GEO Wiki CLI - AI-powered knowledge base management tool",
5
+ "type": "module",
6
+ "main": "cli/index.js",
7
+ "bin": {
8
+ "geo": "../bin/geo.js"
9
+ },
10
+ "files": [
11
+ "bin/",
12
+ "cli/",
13
+ "!cli/__tests__/"
14
+ ],
15
+ "scripts": {
16
+ "test": "vitest run"
17
+ },
18
+ "dependencies": {
19
+ "gray-matter": "^4.0.3"
20
+ },
21
+ "devDependencies": {
22
+ "vitest": "^1.2.0"
23
+ },
24
+ "keywords": [
25
+ "geo",
26
+ "wiki",
27
+ "knowledge-base",
28
+ "cli",
29
+ "ai",
30
+ "seo"
31
+ ],
32
+ "author": "GEO Wiki Team",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/ai-littleyao/geo-wiki-framework.git"
37
+ },
38
+ "engines": {
39
+ "node": ">=18"
40
+ }
41
+ }