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.
- package/README.md +318 -0
- package/bin/geo.js +22 -0
- package/cli/commands/category.js +171 -0
- package/cli/commands/config.js +180 -0
- package/cli/commands/doc.js +380 -0
- package/cli/commands/draft.js +113 -0
- package/cli/commands/feedback.js +84 -0
- package/cli/commands/geo.js +144 -0
- package/cli/commands/guestbook.js +114 -0
- package/cli/commands/login.js +181 -0
- package/cli/commands/media.js +187 -0
- package/cli/commands/search.js +67 -0
- package/cli/commands/stats.js +90 -0
- package/cli/commands/tag.js +131 -0
- package/cli/commands/user.js +195 -0
- package/cli/index.js +178 -0
- package/cli/package.json +41 -0
- package/cli/utils/api.js +197 -0
- package/cli/utils/args.js +25 -0
- package/cli/utils/config.js +94 -0
- package/cli/utils/dispatch.js +20 -0
- package/cli/utils/output.js +41 -0
- package/package.json +98 -0
|
@@ -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;
|
package/cli/package.json
ADDED
|
@@ -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
|
+
}
|