@mkterswingman/5mghost-wonder 0.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/dist/auth/runtime.js +15 -0
- package/dist/cli.js +75 -0
- package/dist/commands/auth.js +100 -0
- package/dist/commands/check.js +258 -0
- package/dist/commands/help.js +38 -0
- package/dist/commands/index.js +50 -0
- package/dist/commands/read.js +198 -0
- package/dist/commands/setup.js +81 -0
- package/dist/commands/types.js +4 -0
- package/dist/commands/uninstall.js +14 -0
- package/dist/commands/update.js +21 -0
- package/dist/commands/version.js +8 -0
- package/dist/commands/wecom.js +136 -0
- package/dist/platform/npm.js +14 -0
- package/dist/platform/paths.js +25 -0
- package/dist/telemetry/events.js +42 -0
- package/dist/telemetry/policy.js +51 -0
- package/dist/telemetry/runtime.js +31 -0
- package/dist/wecom/browser.js +344 -0
- package/dist/wecom/cache.js +119 -0
- package/dist/wecom/cookies.js +151 -0
- package/dist/wecom/export.js +236 -0
- package/dist/wecom/url.js +45 -0
- package/dist/wecom/url.test.js +64 -0
- package/dist/xlsx/drawing.js +131 -0
- package/dist/xlsx/metadata.js +34 -0
- package/dist/xlsx/parse-tab.js +124 -0
- package/dist/xlsx/shared-strings.js +51 -0
- package/dist/xlsx/sheet.js +161 -0
- package/dist/xlsx/styles.js +85 -0
- package/dist/xlsx/unzip.js +33 -0
- package/dist/xlsx/workbook.js +51 -0
- package/dist/xlsx/workbook.test.js +19 -0
- package/package.json +41 -0
- package/scripts/check-export-types.mjs +37 -0
- package/scripts/postinstall.mjs +50 -0
- package/skills/setup-5mghost-wonder/SKILL.md +245 -0
- package/skills/use-5mghost-wonder/SKILL.md +240 -0
- package/skills.manifest.json +36 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/auth/runtime.ts
|
|
2
|
+
// Factory for the auth SDK instance injected into CommandContext.
|
|
3
|
+
// Reads MKTERSWINGMAN_AUTH_URL from env so tests and staging can override it.
|
|
4
|
+
// All auth logic lives in @mkterswingman/5mghost-auth; this file only wires the URL.
|
|
5
|
+
import { DEFAULT_AUTH_URL, getAuthStatus, logout, runOAuthLogin, writeAuthState, writePatAuth, } from "@mkterswingman/5mghost-auth";
|
|
6
|
+
export function createWonderAuthSdk(env = process.env) {
|
|
7
|
+
const authUrl = env["MKTERSWINGMAN_AUTH_URL"] ?? DEFAULT_AUTH_URL;
|
|
8
|
+
return {
|
|
9
|
+
getAuthStatus: (options = {}) => getAuthStatus({ homeDir: options.homeDir, authUrl }),
|
|
10
|
+
runOAuthLogin: (options) => runOAuthLogin({ ...options, authUrl }),
|
|
11
|
+
writeAuthState: (state, opts) => writeAuthState(state, opts),
|
|
12
|
+
writePatAuth: (token, opts) => writePatAuth(token, opts),
|
|
13
|
+
logout: (opts) => logout(opts),
|
|
14
|
+
};
|
|
15
|
+
}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// src/cli.ts
|
|
3
|
+
// Main entry point for the `wonder` CLI.
|
|
4
|
+
// Telemetry wired by P1-05. Auth wired by P1-04.
|
|
5
|
+
import { resolveWonderPaths } from "./platform/paths.js";
|
|
6
|
+
import { dispatchWonderCommand } from "./commands/index.js";
|
|
7
|
+
import { runHelpCommand } from "./commands/help.js";
|
|
8
|
+
import { createWonderTelemetryRuntime } from "./telemetry/runtime.js";
|
|
9
|
+
import { createWonderAuthSdk } from "./auth/runtime.js";
|
|
10
|
+
import { resolveTelemetryCommand, classifyUnhandledCliError, } from "./telemetry/policy.js";
|
|
11
|
+
import { buildCompletionTelemetry } from "./telemetry/events.js";
|
|
12
|
+
// ── Global error handlers (catch errors outside async dispatch) ─────────────
|
|
13
|
+
process.on("uncaughtException", (err) => {
|
|
14
|
+
process.stderr.write(`Unhandled exception: ${String(err)}\n`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
});
|
|
17
|
+
process.on("unhandledRejection", (reason) => {
|
|
18
|
+
process.stderr.write(`Unhandled rejection: ${String(reason)}\n`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
|
21
|
+
const argv = process.argv.slice(2);
|
|
22
|
+
const paths = resolveWonderPaths();
|
|
23
|
+
const io = {
|
|
24
|
+
stdout: (m) => process.stdout.write(m + "\n"),
|
|
25
|
+
stderr: (m) => process.stderr.write(m + "\n"),
|
|
26
|
+
};
|
|
27
|
+
const telemetry = createWonderTelemetryRuntime({
|
|
28
|
+
homeDir: paths.homeDir,
|
|
29
|
+
env: process.env,
|
|
30
|
+
});
|
|
31
|
+
// Flush any queued events from the previous run before starting.
|
|
32
|
+
telemetry?.triggerFlush("startup");
|
|
33
|
+
// authSdk wired by P1-04.
|
|
34
|
+
const context = {
|
|
35
|
+
io,
|
|
36
|
+
homeDir: paths.homeDir,
|
|
37
|
+
authSdk: createWonderAuthSdk(process.env),
|
|
38
|
+
telemetrySdk: telemetry,
|
|
39
|
+
};
|
|
40
|
+
const startMs = Date.now();
|
|
41
|
+
const cmdInfo = resolveTelemetryCommand(argv);
|
|
42
|
+
try {
|
|
43
|
+
// No arguments → show help, exit 0
|
|
44
|
+
if (argv.length === 0) {
|
|
45
|
+
const result = await runHelpCommand(io);
|
|
46
|
+
process.exit(result.exitCode);
|
|
47
|
+
}
|
|
48
|
+
const result = await dispatchWonderCommand(argv, context);
|
|
49
|
+
if (cmdInfo && telemetry) {
|
|
50
|
+
try {
|
|
51
|
+
telemetry.recordCompletion(buildCompletionTelemetry(cmdInfo, result, Date.now() - startMs));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Telemetry failure must never change the CLI exit code.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
process.exit(result.exitCode);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
// Classify and record unhandled errors before exiting.
|
|
61
|
+
if (cmdInfo && telemetry) {
|
|
62
|
+
try {
|
|
63
|
+
const meta = classifyUnhandledCliError(cmdInfo.toolName, err);
|
|
64
|
+
telemetry.recordCompletion({
|
|
65
|
+
...meta,
|
|
66
|
+
durationMs: Date.now() - startMs,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Telemetry failure in error path — ignore.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
io.stderr(`Unhandled error: ${String(err)}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// src/commands/auth.ts
|
|
2
|
+
// Implements: wonder auth status | login | login --pat <token> | logout
|
|
3
|
+
// Auth SDK injected via CommandContext.authSdk (DI pattern).
|
|
4
|
+
// Falls back to direct SDK imports when authSdk is not provided in context.
|
|
5
|
+
import { buildOAuthFailureHelpText, buildPatFailureHelpText, buildUnauthedHelpText, getAuthStatus, logout as sharedLogout, runOAuthLogin, writeAuthState, writePatAuth, } from "@mkterswingman/5mghost-auth";
|
|
6
|
+
function renderAuthHelp() {
|
|
7
|
+
return [
|
|
8
|
+
"Usage:",
|
|
9
|
+
" wonder auth status",
|
|
10
|
+
" wonder auth login",
|
|
11
|
+
" wonder auth login --pat <TOKEN>",
|
|
12
|
+
" wonder auth logout",
|
|
13
|
+
"",
|
|
14
|
+
"Login defaults to OAuth.",
|
|
15
|
+
"Use --pat <TOKEN> for the explicit PAT fallback.",
|
|
16
|
+
].join("\n");
|
|
17
|
+
}
|
|
18
|
+
export async function runAuthCommand(args, context) {
|
|
19
|
+
const [subcommand] = args;
|
|
20
|
+
// ── help ──────────────────────────────────────────────────────────────────
|
|
21
|
+
if (!subcommand ||
|
|
22
|
+
subcommand === "help" ||
|
|
23
|
+
subcommand === "--help" ||
|
|
24
|
+
subcommand === "-h") {
|
|
25
|
+
context.io.stdout(renderAuthHelp());
|
|
26
|
+
return { exitCode: 0 };
|
|
27
|
+
}
|
|
28
|
+
// ── status ────────────────────────────────────────────────────────────────
|
|
29
|
+
if (subcommand === "status") {
|
|
30
|
+
const readStatus = context.authSdk?.getAuthStatus ?? getAuthStatus;
|
|
31
|
+
let status;
|
|
32
|
+
try {
|
|
33
|
+
status = await readStatus({ homeDir: context.homeDir });
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
37
|
+
context.io.stderr(`Authentication status check failed: ${reason}`);
|
|
38
|
+
return { exitCode: 1 };
|
|
39
|
+
}
|
|
40
|
+
if (!status.authenticated) {
|
|
41
|
+
context.io.stdout(buildUnauthedHelpText("wonder"));
|
|
42
|
+
return { exitCode: 1 };
|
|
43
|
+
}
|
|
44
|
+
context.io.stdout([
|
|
45
|
+
"Logged in.",
|
|
46
|
+
`Type: ${status.type ?? "unknown"}`,
|
|
47
|
+
`Source: ${status.source ?? "unknown"}`,
|
|
48
|
+
`Auth file: ${status.authJsonPath}`,
|
|
49
|
+
].join("\n"));
|
|
50
|
+
return { exitCode: 0 };
|
|
51
|
+
}
|
|
52
|
+
// ── logout ────────────────────────────────────────────────────────────────
|
|
53
|
+
if (subcommand === "logout") {
|
|
54
|
+
const doLogout = context.authSdk?.logout ?? sharedLogout;
|
|
55
|
+
doLogout({ homeDir: context.homeDir });
|
|
56
|
+
context.io.stdout("Logged out.");
|
|
57
|
+
return { exitCode: 0 };
|
|
58
|
+
}
|
|
59
|
+
// ── login ─────────────────────────────────────────────────────────────────
|
|
60
|
+
if (subcommand === "login") {
|
|
61
|
+
const patFlagIndex = args.indexOf("--pat");
|
|
62
|
+
// login --pat <TOKEN>
|
|
63
|
+
if (patFlagIndex >= 0) {
|
|
64
|
+
const token = args[patFlagIndex + 1]?.trim();
|
|
65
|
+
if (!token) {
|
|
66
|
+
context.io.stdout(buildPatFailureHelpText("wonder"));
|
|
67
|
+
return { exitCode: 1 };
|
|
68
|
+
}
|
|
69
|
+
const savePat = context.authSdk?.writePatAuth ?? writePatAuth;
|
|
70
|
+
savePat(token, { homeDir: context.homeDir });
|
|
71
|
+
context.io.stdout("PAT saved.");
|
|
72
|
+
return { exitCode: 0 };
|
|
73
|
+
}
|
|
74
|
+
// login (OAuth)
|
|
75
|
+
const doOAuthLogin = context.authSdk?.runOAuthLogin ?? runOAuthLogin;
|
|
76
|
+
const saveJwt = context.authSdk?.writeAuthState ?? writeAuthState;
|
|
77
|
+
try {
|
|
78
|
+
const result = await doOAuthLogin({ clientName: "wonder" });
|
|
79
|
+
saveJwt({
|
|
80
|
+
type: "jwt",
|
|
81
|
+
access_token: result.accessToken,
|
|
82
|
+
refresh_token: result.refreshToken,
|
|
83
|
+
expires_at: Date.now() + result.expiresIn * 1000,
|
|
84
|
+
client_id: result.clientId,
|
|
85
|
+
}, { homeDir: context.homeDir });
|
|
86
|
+
context.io.stdout("Authentication complete.");
|
|
87
|
+
return { exitCode: 0 };
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
91
|
+
context.io.stderr(`Authentication failed: ${reason}`);
|
|
92
|
+
context.io.stdout(buildOAuthFailureHelpText("wonder"));
|
|
93
|
+
return { exitCode: 1 };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ── unknown subcommand ────────────────────────────────────────────────────
|
|
97
|
+
context.io.stderr(`Unknown auth subcommand: ${subcommand}`);
|
|
98
|
+
context.io.stdout(renderAuthHelp());
|
|
99
|
+
return { exitCode: 1 };
|
|
100
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// src/commands/check.ts
|
|
2
|
+
// Checks all runtime dependencies wonder needs.
|
|
3
|
+
//
|
|
4
|
+
// Fields checked:
|
|
5
|
+
// - auth: mkterswingman auth JWT/PAT
|
|
6
|
+
// - wecom-cookie: WeCom cookies.json presence + live validity probe
|
|
7
|
+
// - pandoc: CLI on PATH (used by use-skill for docx/pptx text)
|
|
8
|
+
// - soffice: CLI on PATH + real executable (broken symlinks are rejected)
|
|
9
|
+
// - docx-skill: docx SKILL.md present in plugin cache or user skills dir
|
|
10
|
+
// - pptx-skill: pptx SKILL.md present in plugin cache or user skills dir
|
|
11
|
+
// - cache: local export cache directory state (informational)
|
|
12
|
+
// - cookies-perms: POSIX 0600 on cookies.json (informational)
|
|
13
|
+
//
|
|
14
|
+
// Each check produces { label, ok, hint? }. The command exits 0 iff all
|
|
15
|
+
// required checks pass. docx/pptx skills are optional because some consumers
|
|
16
|
+
// (raw JSON, non-Claude AI clients) do not need the Anthropic bundled skills.
|
|
17
|
+
import { accessSync, constants, existsSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
18
|
+
import { delimiter, join, resolve } from "node:path";
|
|
19
|
+
import { spawnSync } from "node:child_process";
|
|
20
|
+
import { homedir } from "node:os";
|
|
21
|
+
import { resolveWonderPaths } from "../platform/paths.js";
|
|
22
|
+
import { getCookieStatus } from "../wecom/cookies.js";
|
|
23
|
+
import { describeCacheDir } from "../wecom/cache.js";
|
|
24
|
+
function findOnPath(binName) {
|
|
25
|
+
const pathEnv = process.env["PATH"] ?? "";
|
|
26
|
+
const exts = process.platform === "win32"
|
|
27
|
+
? (process.env["PATHEXT"] ?? ".EXE;.CMD;.BAT").split(";")
|
|
28
|
+
: [""];
|
|
29
|
+
for (const dir of pathEnv.split(delimiter)) {
|
|
30
|
+
if (!dir)
|
|
31
|
+
continue;
|
|
32
|
+
for (const ext of exts) {
|
|
33
|
+
const candidate = resolve(dir, `${binName}${ext.toLowerCase()}`);
|
|
34
|
+
try {
|
|
35
|
+
accessSync(candidate, constants.X_OK);
|
|
36
|
+
return candidate;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* try next */
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function checkExecutable(binName, installHints) {
|
|
46
|
+
const found = findOnPath(binName);
|
|
47
|
+
if (!found) {
|
|
48
|
+
return {
|
|
49
|
+
label: binName,
|
|
50
|
+
ok: false,
|
|
51
|
+
hint: installHints[process.platform] ?? installHints["default"] ?? `install ${binName}`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Resolve symlinks — catches the classic `/opt/homebrew/bin/soffice → /Applications/LibreOffice.app/...`
|
|
55
|
+
// broken-symlink case where `which` returns a path but the target is gone.
|
|
56
|
+
let realTarget = null;
|
|
57
|
+
try {
|
|
58
|
+
realTarget = realpathSync(found);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return {
|
|
62
|
+
label: binName,
|
|
63
|
+
ok: false,
|
|
64
|
+
hint: `${binName} is a broken symlink at ${found}. Reinstall: ${installHints[process.platform] ?? installHints["default"]}`,
|
|
65
|
+
detail: found,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Cheap liveness probe: does the binary actually run?
|
|
69
|
+
const probe = spawnSync(found, ["--version"], {
|
|
70
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
71
|
+
timeout: 5000,
|
|
72
|
+
});
|
|
73
|
+
if (probe.error || probe.status === null) {
|
|
74
|
+
return {
|
|
75
|
+
label: binName,
|
|
76
|
+
ok: false,
|
|
77
|
+
hint: `${binName} is on PATH (${found}) but failed to execute. Reinstall: ${installHints[process.platform] ?? installHints["default"]}`,
|
|
78
|
+
detail: `realpath=${realTarget}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
label: binName,
|
|
83
|
+
ok: true,
|
|
84
|
+
detail: found === realTarget ? found : `${found} → ${realTarget}`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function checkSkillFile(skillName, opts) {
|
|
88
|
+
// Check plugin cache first (Anthropic-bundled skills)
|
|
89
|
+
const pluginGlob = join(opts.home, ".claude-internal", "plugins", "cache", "anthropic-agent-skills");
|
|
90
|
+
let foundPluginVersion = null;
|
|
91
|
+
if (existsSync(pluginGlob)) {
|
|
92
|
+
try {
|
|
93
|
+
for (const entry of readdirSync(pluginGlob, { withFileTypes: true })) {
|
|
94
|
+
if (!entry.isDirectory())
|
|
95
|
+
continue;
|
|
96
|
+
const candidate = join(pluginGlob, entry.name, "skills", skillName, "SKILL.md");
|
|
97
|
+
if (existsSync(candidate)) {
|
|
98
|
+
foundPluginVersion = entry.name;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
/* fall through to user skills */
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (foundPluginVersion) {
|
|
108
|
+
return {
|
|
109
|
+
label: `${skillName}-skill`,
|
|
110
|
+
ok: true,
|
|
111
|
+
detail: `plugin cache (${foundPluginVersion})`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// User-installed skills (both -internal and plain variants)
|
|
115
|
+
for (const claudeRoot of [".claude-internal", ".claude"]) {
|
|
116
|
+
const userSkill = join(opts.home, claudeRoot, "skills", skillName, "SKILL.md");
|
|
117
|
+
if (existsSync(userSkill)) {
|
|
118
|
+
return { label: `${skillName}-skill`, ok: true, detail: userSkill };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
label: `${skillName}-skill`,
|
|
123
|
+
ok: false,
|
|
124
|
+
optional: true,
|
|
125
|
+
hint: `For Claude consumers that read .${skillName}: ` +
|
|
126
|
+
`mkdir -p ~/.claude-internal/skills/${skillName} && ` +
|
|
127
|
+
`curl -fsSL https://raw.githubusercontent.com/anthropics/skills/main/skills/${skillName}/SKILL.md ` +
|
|
128
|
+
`-o ~/.claude-internal/skills/${skillName}/SKILL.md`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export async function runCheckCommand(_argv, context) {
|
|
132
|
+
const paths = resolveWonderPaths({ homeDir: context.homeDir });
|
|
133
|
+
const items = [];
|
|
134
|
+
// ── Auth ────────────────────────────────────────────────────────────────
|
|
135
|
+
if (!context.authSdk) {
|
|
136
|
+
items.push({
|
|
137
|
+
label: "auth",
|
|
138
|
+
ok: false,
|
|
139
|
+
hint: "mkterswingman auth SDK unavailable — reinstall wonder",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
try {
|
|
144
|
+
const status = await context.authSdk.getAuthStatus({
|
|
145
|
+
homeDir: context.homeDir,
|
|
146
|
+
});
|
|
147
|
+
items.push({
|
|
148
|
+
label: "auth",
|
|
149
|
+
ok: status.authenticated,
|
|
150
|
+
hint: status.authenticated ? undefined : "run: wonder auth login",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
items.push({
|
|
155
|
+
label: "auth",
|
|
156
|
+
ok: false,
|
|
157
|
+
hint: `auth check error: ${String(err)}`,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// ── WeCom cookie ────────────────────────────────────────────────────────
|
|
162
|
+
try {
|
|
163
|
+
const cs = await getCookieStatus(paths.cookiesPath);
|
|
164
|
+
if (cs.error) {
|
|
165
|
+
items.push({ label: "wecom-cookie", ok: false, hint: `cookie error: ${cs.error}` });
|
|
166
|
+
}
|
|
167
|
+
else if (!cs.exists) {
|
|
168
|
+
items.push({ label: "wecom-cookie", ok: false, hint: "run: wonder wecom cookie" });
|
|
169
|
+
}
|
|
170
|
+
else if (cs.valid === true) {
|
|
171
|
+
items.push({ label: "wecom-cookie", ok: true, detail: paths.cookiesPath });
|
|
172
|
+
}
|
|
173
|
+
else if (cs.valid === false) {
|
|
174
|
+
items.push({
|
|
175
|
+
label: "wecom-cookie",
|
|
176
|
+
ok: false,
|
|
177
|
+
hint: "cookie invalid or expired — run: wonder wecom cookie",
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
items.push({
|
|
182
|
+
label: "wecom-cookie",
|
|
183
|
+
ok: false,
|
|
184
|
+
hint: "cookie status unknown (network?)",
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
items.push({ label: "wecom-cookie", ok: false, hint: `check error: ${String(err)}` });
|
|
190
|
+
}
|
|
191
|
+
// ── pandoc ──────────────────────────────────────────────────────────────
|
|
192
|
+
items.push(checkExecutable("pandoc", {
|
|
193
|
+
darwin: "brew install pandoc",
|
|
194
|
+
linux: "sudo apt install -y pandoc (or: sudo dnf install -y pandoc)",
|
|
195
|
+
win32: "winget install pandoc",
|
|
196
|
+
default: "https://pandoc.org/installing.html",
|
|
197
|
+
}));
|
|
198
|
+
// ── LibreOffice soffice ─────────────────────────────────────────────────
|
|
199
|
+
items.push(checkExecutable("soffice", {
|
|
200
|
+
darwin: "brew install --cask libreoffice",
|
|
201
|
+
linux: "sudo apt install -y libreoffice (or: sudo dnf install -y libreoffice)",
|
|
202
|
+
win32: "winget install LibreOffice.LibreOffice",
|
|
203
|
+
default: "https://www.libreoffice.org/download",
|
|
204
|
+
}));
|
|
205
|
+
// ── docx / pptx skill files ─────────────────────────────────────────────
|
|
206
|
+
const homeForSkills = context.homeDir || homedir();
|
|
207
|
+
items.push(checkSkillFile("docx", { home: homeForSkills }));
|
|
208
|
+
items.push(checkSkillFile("pptx", { home: homeForSkills }));
|
|
209
|
+
// ── Cache directory (informational) ─────────────────────────────────────
|
|
210
|
+
const cache = describeCacheDir(paths.cacheDir);
|
|
211
|
+
items.push({
|
|
212
|
+
label: "cache",
|
|
213
|
+
ok: true,
|
|
214
|
+
detail: cache.exists ? `${paths.cacheDir} (${cache.entries} entries)` : "not yet used",
|
|
215
|
+
optional: true,
|
|
216
|
+
});
|
|
217
|
+
// Cookies file permissions (POSIX only)
|
|
218
|
+
if (process.platform !== "win32" && existsSync(paths.cookiesPath)) {
|
|
219
|
+
try {
|
|
220
|
+
const st = statSync(paths.cookiesPath);
|
|
221
|
+
const mode = st.mode & 0o777;
|
|
222
|
+
if (mode & 0o077) {
|
|
223
|
+
items.push({
|
|
224
|
+
label: "cookies-perms",
|
|
225
|
+
ok: false,
|
|
226
|
+
optional: true,
|
|
227
|
+
hint: `cookies.json mode is 0${mode.toString(8)}; expected 0600. Run: chmod 600 ${paths.cookiesPath}`,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
/* ignore — file may disappear between checks */
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// ── Render ──────────────────────────────────────────────────────────────
|
|
236
|
+
const requiredFailures = items.filter((i) => !i.ok && !i.optional).length;
|
|
237
|
+
const optionalFailures = items.filter((i) => !i.ok && i.optional).length;
|
|
238
|
+
const lines = ["wonder check", ""];
|
|
239
|
+
for (const i of items) {
|
|
240
|
+
const icon = i.ok ? "✓" : i.optional ? "!" : "✗";
|
|
241
|
+
const tail = i.ok
|
|
242
|
+
? i.detail ? ` (${i.detail})` : ""
|
|
243
|
+
: ` — ${i.hint ?? ""}`;
|
|
244
|
+
lines.push(` ${icon} ${i.label}${tail}`);
|
|
245
|
+
}
|
|
246
|
+
lines.push("");
|
|
247
|
+
if (requiredFailures === 0 && optionalFailures === 0) {
|
|
248
|
+
lines.push("Status: ready ✓");
|
|
249
|
+
}
|
|
250
|
+
else if (requiredFailures === 0) {
|
|
251
|
+
lines.push(`Status: ready with ${optionalFailures} warning(s) ⚠`);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
lines.push(`Status: not ready — ${requiredFailures} required check(s) failed`);
|
|
255
|
+
}
|
|
256
|
+
context.io.stdout(lines.join("\n"));
|
|
257
|
+
return { exitCode: requiredFailures === 0 ? 0 : 1 };
|
|
258
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/commands/help.ts
|
|
2
|
+
// Renders the top-level help text and implements `wonder help` / `wonder --help`.
|
|
3
|
+
export function renderHelpText() {
|
|
4
|
+
return [
|
|
5
|
+
"5mghost-wonder",
|
|
6
|
+
"",
|
|
7
|
+
"Usage:",
|
|
8
|
+
" wonder <command> [options]",
|
|
9
|
+
"",
|
|
10
|
+
"Lifecycle:",
|
|
11
|
+
" setup First-time guided setup (auth + wecom cookie + check)",
|
|
12
|
+
" check Check auth + WeCom cookie status",
|
|
13
|
+
" update Update to latest version (npm install -g @latest)",
|
|
14
|
+
" version Print the installed CLI version",
|
|
15
|
+
" uninstall Uninstall (npm uninstall -g)",
|
|
16
|
+
"",
|
|
17
|
+
"Auth:",
|
|
18
|
+
" auth status mkterswingman auth status",
|
|
19
|
+
" auth login Browser OAuth login",
|
|
20
|
+
" auth login --pat <TOKEN> PAT login",
|
|
21
|
+
" auth logout Logout",
|
|
22
|
+
"",
|
|
23
|
+
"WeCom:",
|
|
24
|
+
" wecom cookie Launch Chrome via CDP to grab WeCom cookies",
|
|
25
|
+
" wecom status Check cookie validity and expiry",
|
|
26
|
+
" wecom set-cookie Manually paste cookies (dev / CI)",
|
|
27
|
+
"",
|
|
28
|
+
"Documents:",
|
|
29
|
+
" read <url> Auto-detect type; xlsx → tab list JSON,",
|
|
30
|
+
" doc/slide → download file + path JSON",
|
|
31
|
+
" read <url> --tab <name> Read specific xlsx tab (cells + merges + images)",
|
|
32
|
+
" read <url> --save <dir> Output directory (default: ~/Downloads/5mghost-wonder/)",
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
35
|
+
export async function runHelpCommand(io) {
|
|
36
|
+
io.stdout(renderHelpText());
|
|
37
|
+
return { exitCode: 0 };
|
|
38
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/commands/index.ts
|
|
2
|
+
// Top-level command dispatcher.
|
|
3
|
+
// Lifecycle commands are implemented in their own files.
|
|
4
|
+
// auth / wecom / read stubs remain until P1-04, P1-03, P1-07 implement them.
|
|
5
|
+
import { runHelpCommand } from "./help.js";
|
|
6
|
+
import { runVersionCommand } from "./version.js";
|
|
7
|
+
import { runUpdateCommand } from "./update.js";
|
|
8
|
+
import { runUninstallCommand } from "./uninstall.js";
|
|
9
|
+
import { runCheckCommand } from "./check.js";
|
|
10
|
+
import { runSetupCommand } from "./setup.js";
|
|
11
|
+
import { runAuthCommand } from "./auth.js";
|
|
12
|
+
import { runWecom } from "./wecom.js";
|
|
13
|
+
import { runReadCommand } from "./read.js";
|
|
14
|
+
export async function dispatchWonderCommand(argv, context) {
|
|
15
|
+
const [cmd, ...rest] = argv;
|
|
16
|
+
switch (cmd) {
|
|
17
|
+
case "setup":
|
|
18
|
+
return runSetupCommand(rest, context);
|
|
19
|
+
case "check":
|
|
20
|
+
return runCheckCommand(rest, context);
|
|
21
|
+
case "update":
|
|
22
|
+
return runUpdateCommand(rest, context);
|
|
23
|
+
case "uninstall":
|
|
24
|
+
return runUninstallCommand(rest, context);
|
|
25
|
+
case "version":
|
|
26
|
+
return runVersionCommand(rest, context);
|
|
27
|
+
case "auth":
|
|
28
|
+
return runAuthCommand(rest, context);
|
|
29
|
+
case "wecom":
|
|
30
|
+
return runWecom(rest, context);
|
|
31
|
+
case "read":
|
|
32
|
+
return runRead(rest, context);
|
|
33
|
+
case "--help":
|
|
34
|
+
case "-h":
|
|
35
|
+
case "help":
|
|
36
|
+
return runHelpCommand(context.io);
|
|
37
|
+
case "--version":
|
|
38
|
+
case "-v":
|
|
39
|
+
return runVersionCommand(rest, context);
|
|
40
|
+
default:
|
|
41
|
+
context.io.stderr(`Unknown command: ${cmd ?? "(none)"}\nRun "wonder --help" for usage.`);
|
|
42
|
+
return { exitCode: 1 };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// read — P1-07
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
async function runRead(args, context) {
|
|
49
|
+
return runReadCommand(args, context);
|
|
50
|
+
}
|