weaver-work-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 (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +422 -0
  3. package/dist/cmd/action.js +35 -0
  4. package/dist/cmd/commands/index.js +15 -0
  5. package/dist/cmd/config/index.js +64 -0
  6. package/dist/cmd/docs/index.js +56 -0
  7. package/dist/cmd/doctor/index.js +45 -0
  8. package/dist/cmd/e10/index.js +25 -0
  9. package/dist/cmd/index.js +18 -0
  10. package/dist/cmd/register.js +19 -0
  11. package/dist/cmd/root.js +36 -0
  12. package/dist/cmd/run/index.js +38 -0
  13. package/dist/cmd/setup/index.js +77 -0
  14. package/dist/cmd/skills/index.js +72 -0
  15. package/dist/core/config-store.js +43 -0
  16. package/dist/core/context.js +31 -0
  17. package/dist/core/errors.js +60 -0
  18. package/dist/core/hooks.js +41 -0
  19. package/dist/core/logger.js +27 -0
  20. package/dist/core/output.js +25 -0
  21. package/dist/core/write-with-retry.js +21 -0
  22. package/dist/index.js +11 -0
  23. package/dist/internal/e10/auth/commands.js +594 -0
  24. package/dist/internal/e10/auth/crypto.js +126 -0
  25. package/dist/internal/e10/auth/paths.js +12 -0
  26. package/dist/internal/e10/auth/session.js +449 -0
  27. package/dist/internal/e10/client.js +104 -0
  28. package/dist/internal/e10/context.js +20 -0
  29. package/dist/internal/e10/index.js +2 -0
  30. package/dist/internal/e10/types.js +1 -0
  31. package/dist/internal/skills/install.js +99 -0
  32. package/dist/internal/skills/reader.js +119 -0
  33. package/dist/services/command-runner.js +44 -0
  34. package/dist/shortcuts/index.js +4 -0
  35. package/dist/shortcuts/invoice/continuation.js +70 -0
  36. package/dist/shortcuts/invoice/errors.js +59 -0
  37. package/dist/shortcuts/invoice/host.js +337 -0
  38. package/dist/shortcuts/invoice/index.js +301 -0
  39. package/dist/shortcuts/invoice/manifest.js +288 -0
  40. package/dist/shortcuts/invoice/operations/add.js +106 -0
  41. package/dist/shortcuts/invoice/operations/delete.js +92 -0
  42. package/dist/shortcuts/invoice/operations/download.js +16 -0
  43. package/dist/shortcuts/invoice/operations/get.js +7 -0
  44. package/dist/shortcuts/invoice/operations/import.js +128 -0
  45. package/dist/shortcuts/invoice/operations/list.js +12 -0
  46. package/dist/shortcuts/invoice/operations/ocr-preview.js +40 -0
  47. package/dist/shortcuts/invoice/operations/registry.js +50 -0
  48. package/dist/shortcuts/invoice/operations/shared.js +640 -0
  49. package/dist/shortcuts/invoice/operations/types.js +1 -0
  50. package/dist/shortcuts/invoice/operations/update.js +109 -0
  51. package/dist/shortcuts/invoice/operations/upload.js +8 -0
  52. package/dist/shortcuts/invoice/operations/validate-preview.js +39 -0
  53. package/dist/shortcuts/invoice/operations.js +8 -0
  54. package/dist/types.js +1 -0
  55. package/docs/SKILL.md +36 -0
  56. package/docs/_catalog.md +12 -0
  57. package/docs/agent-invoice.md +53 -0
  58. package/docs/e10-auth.md +51 -0
  59. package/docs/invoice.md +99 -0
  60. package/package.json +51 -0
  61. package/scripts/clean.mjs +4 -0
  62. package/scripts/package-skill.mjs +164 -0
  63. package/scripts/sync-docs.mjs +17 -0
  64. package/skill-template/business-info.json +4 -0
  65. package/skill-template/domains/invoice.md +23 -0
  66. package/skill-template/domains/shared.md +8 -0
  67. package/skill-template/master-skill-template.md +29 -0
  68. package/skill-template/skill-template.md +60 -0
  69. package/skills/weaver-work-cli-invoice/SKILL.md +103 -0
  70. package/skills/weaver-work-cli-invoice/product.json +8 -0
  71. package/skills/weaver-work-cli-invoice/references/invoice-add.md +18 -0
  72. package/skills/weaver-work-cli-invoice/references/invoice-agent-entry.md +38 -0
  73. package/skills/weaver-work-cli-invoice/references/invoice-delete.md +20 -0
  74. package/skills/weaver-work-cli-invoice/references/invoice-detail.md +29 -0
  75. package/skills/weaver-work-cli-invoice/references/invoice-disabled-capabilities.md +26 -0
  76. package/skills/weaver-work-cli-invoice/references/invoice-download.md +22 -0
  77. package/skills/weaver-work-cli-invoice/references/invoice-enterprise-list.md +35 -0
  78. package/skills/weaver-work-cli-invoice/references/invoice-file-upload.md +28 -0
  79. package/skills/weaver-work-cli-invoice/references/invoice-import.md +28 -0
  80. package/skills/weaver-work-cli-invoice/references/invoice-ocr-preview.md +33 -0
  81. package/skills/weaver-work-cli-invoice/references/invoice-personal-list.md +47 -0
  82. package/skills/weaver-work-cli-invoice/references/invoice-update.md +38 -0
  83. package/skills/weaver-work-cli-invoice/references/invoice-validation-preview.md +26 -0
  84. package/skills/weaver-work-cli-shared/SKILL.md +53 -0
  85. package/skills/weaver-work-cli-shared/references/e10-auth-and-session.md +83 -0
  86. package/skills/weaver-work-cli-shared/references/high-risk-write.md +36 -0
  87. package/skills/weaver-work-cli-shared/references/json-output-contract.md +61 -0
  88. package/skills/weaver-work-cli-shared/references/weaver-work-cli-installation.md +61 -0
  89. package/templates/shortcut/index.ts.tpl +19 -0
  90. package/templates/shortcut/operations/ping.ts.tpl +7 -0
@@ -0,0 +1,20 @@
1
+ import { CliError, ExitCode } from '../../core/errors.js';
2
+ import { E10AuthSession } from './auth/session.js';
3
+ import { E10ApiClient } from './client.js';
4
+ export async function getE10Session(ctx) {
5
+ return E10AuthSession.fromOptions(ctx.getGlobalOptions(), ctx.cwd);
6
+ }
7
+ export async function requireE10Session(ctx) {
8
+ const session = await getE10Session(ctx);
9
+ if (!session) {
10
+ throw new CliError('E10 auth not found. Ask the user for the E10 login domain (base URL), then run: weaver-work-cli auth login --base-url <url>.', ExitCode.AuthError);
11
+ }
12
+ return session;
13
+ }
14
+ export async function getE10Client(ctx) {
15
+ const session = await getE10Session(ctx);
16
+ return session ? new E10ApiClient(session) : null;
17
+ }
18
+ export async function requireE10Client(ctx) {
19
+ return new E10ApiClient(await requireE10Session(ctx));
20
+ }
@@ -0,0 +1,2 @@
1
+ export { E10ApiClient } from './client.js';
2
+ export { getE10Client, getE10Session, requireE10Client, requireE10Session } from './context.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,99 @@
1
+ import { cpSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { basename, relative, resolve, sep } from 'node:path';
4
+ const SKILL_PREFIX = 'weaver-work-cli-';
5
+ const SHARED_SKILL_NAME = 'weaver-work-cli-shared';
6
+ export function defaultCodexSkillsRoot(env = process.env) {
7
+ const codexHome = env.CODEX_HOME || resolve(homedir(), '.codex');
8
+ return resolve(codexHome, 'skills');
9
+ }
10
+ export function normalizePackagedSkillName(value) {
11
+ const trimmed = value.trim();
12
+ if (!trimmed || trimmed === 'all')
13
+ return trimmed;
14
+ if (trimmed.includes('/') || trimmed.includes('\\') || trimmed === '.' || trimmed === '..') {
15
+ throw new Error(`invalid skill name: ${value}`);
16
+ }
17
+ if (!/^[A-Za-z0-9._-]+$/u.test(trimmed)) {
18
+ throw new Error(`invalid skill name: ${value}`);
19
+ }
20
+ return trimmed.startsWith(SKILL_PREFIX) ? trimmed : `${SKILL_PREFIX}${trimmed}`;
21
+ }
22
+ export function resolvePackagedSkillNames(sourceRoot, requested = 'all') {
23
+ const available = readdirSync(sourceRoot, { withFileTypes: true })
24
+ .filter((entry) => entry.isDirectory() && existsSync(resolve(sourceRoot, entry.name, 'SKILL.md')))
25
+ .map((entry) => entry.name)
26
+ .sort();
27
+ const normalized = normalizePackagedSkillName(requested);
28
+ if (!normalized || normalized === 'all')
29
+ return available;
30
+ if (!available.includes(normalized)) {
31
+ throw new Error(`unknown packaged skill: ${normalized}`);
32
+ }
33
+ const names = [];
34
+ if (normalized !== SHARED_SKILL_NAME && available.includes(SHARED_SKILL_NAME)) {
35
+ names.push(SHARED_SKILL_NAME);
36
+ }
37
+ names.push(normalized);
38
+ return names;
39
+ }
40
+ export function installPackagedSkills(sourceRoot, targetRoot, requested = 'all') {
41
+ const resolvedSourceRoot = resolve(sourceRoot);
42
+ const resolvedTargetRoot = resolve(targetRoot);
43
+ const skillNames = resolvePackagedSkillNames(resolvedSourceRoot, requested);
44
+ mkdirSync(resolvedTargetRoot, { recursive: true, mode: 0o700 });
45
+ const installed = skillNames.map((name) => {
46
+ const source = resolveInside(resolvedSourceRoot, name);
47
+ const target = resolve(resolvedTargetRoot, basename(name));
48
+ statSync(resolve(source, 'SKILL.md'));
49
+ cpSync(source, target, {
50
+ recursive: true,
51
+ force: true,
52
+ filter: (entry) => shouldCopy(entry, source),
53
+ });
54
+ return {
55
+ name,
56
+ source,
57
+ target,
58
+ files: countFiles(target),
59
+ };
60
+ });
61
+ return {
62
+ sourceRoot: resolvedSourceRoot,
63
+ targetRoot: resolvedTargetRoot,
64
+ skillNames,
65
+ installed,
66
+ };
67
+ }
68
+ function shouldCopy(entry, sourceRoot) {
69
+ const relativePath = relative(sourceRoot, entry);
70
+ const name = basename(entry);
71
+ if (!relativePath)
72
+ return true;
73
+ if (name === '.DS_Store' || name.startsWith('.'))
74
+ return false;
75
+ const parts = relativePath.split(sep);
76
+ return !parts.includes('node_modules') && !parts.includes('scripts') && !parts.includes('assets');
77
+ }
78
+ function countFiles(root) {
79
+ let count = 0;
80
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
81
+ const child = resolve(root, entry.name);
82
+ if (entry.isDirectory())
83
+ count += countFiles(child);
84
+ if (entry.isFile())
85
+ count += 1;
86
+ }
87
+ return count;
88
+ }
89
+ function resolveInside(base, subpath = '') {
90
+ if (subpath.startsWith('/') || subpath.split(/[\\/]/u).includes('..')) {
91
+ throw new Error(`invalid skill path: ${subpath}`);
92
+ }
93
+ const target = resolve(base, subpath || '.');
94
+ const rel = relative(base, target);
95
+ if (rel === '..' || rel.startsWith(`..${sep}`)) {
96
+ throw new Error(`invalid skill path: ${subpath}`);
97
+ }
98
+ return target;
99
+ }
@@ -0,0 +1,119 @@
1
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import { relative, resolve, sep } from 'node:path';
3
+ export class SkillReader {
4
+ root;
5
+ constructor(root) {
6
+ this.root = root;
7
+ }
8
+ list() {
9
+ return readdirSync(this.root, { withFileTypes: true })
10
+ .filter((entry) => entry.isDirectory())
11
+ .map((entry) => this.skillInfo(entry.name))
12
+ .filter((entry) => Boolean(entry))
13
+ .sort((a, b) => a.name.localeCompare(b.name));
14
+ }
15
+ listPath(target) {
16
+ const { name, subpath } = splitTarget(target);
17
+ const skillRoot = this.ensureSkill(name);
18
+ const dir = this.resolveInside(skillRoot, subpath);
19
+ const info = statSync(dir);
20
+ if (!info.isDirectory()) {
21
+ throw new Error(`path is a file, not a directory: ${target}`);
22
+ }
23
+ const listed = cleanOutputPath(name, subpath);
24
+ const entries = readdirSync(dir, { withFileTypes: true })
25
+ .map((entry) => ({
26
+ path: cleanOutputPath(listed, entry.name),
27
+ isDir: entry.isDirectory(),
28
+ }))
29
+ .sort((a, b) => a.path.localeCompare(b.path));
30
+ return { path: listed, entries };
31
+ }
32
+ read(target, pathArg = '') {
33
+ const split = splitTarget(target);
34
+ const name = split.name;
35
+ const subpath = pathArg || split.subpath || 'SKILL.md';
36
+ const skillRoot = this.ensureSkill(name);
37
+ const file = this.resolveInside(skillRoot, subpath);
38
+ const info = statSync(file);
39
+ if (info.isDirectory()) {
40
+ throw new Error(`path is a directory, not a file: ${cleanOutputPath(name, subpath)}`);
41
+ }
42
+ return {
43
+ skill: name,
44
+ path: cleanOutputPath(subpath),
45
+ content: readFileSync(file, 'utf-8'),
46
+ };
47
+ }
48
+ skillInfo(name) {
49
+ try {
50
+ const skillRoot = this.ensureSkill(name);
51
+ const content = readFileSync(resolve(skillRoot, 'SKILL.md'), 'utf-8');
52
+ const frontmatter = parseFrontmatter(content);
53
+ return {
54
+ name,
55
+ title: frontmatter.name,
56
+ description: frontmatter.description,
57
+ version: frontmatter.version,
58
+ };
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ ensureSkill(name) {
65
+ if (!name || name.includes('/') || name.includes('\\') || name === '.' || name === '..') {
66
+ throw new Error(`unknown skill: ${name}`);
67
+ }
68
+ const skillRoot = this.resolveInside(this.root, name);
69
+ const info = statSync(skillRoot);
70
+ if (!info.isDirectory())
71
+ throw new Error(`unknown skill: ${name}`);
72
+ statSync(resolve(skillRoot, 'SKILL.md'));
73
+ return skillRoot;
74
+ }
75
+ resolveInside(base, subpath = '') {
76
+ const cleaned = subpath.replace(/\\/g, '/');
77
+ if (cleaned.startsWith('/') || cleaned.split('/').includes('..')) {
78
+ throw new Error(`invalid skill path: ${subpath}`);
79
+ }
80
+ const target = resolve(base, cleaned || '.');
81
+ const rel = relative(base, target);
82
+ if (rel === '..' || rel.startsWith(`..${sep}`) || resolve(base) === target && cleaned === '..') {
83
+ throw new Error(`invalid skill path: ${subpath}`);
84
+ }
85
+ return target;
86
+ }
87
+ }
88
+ export function splitTarget(target) {
89
+ const index = target.indexOf('/');
90
+ if (index === -1)
91
+ return { name: target, subpath: '' };
92
+ return {
93
+ name: target.slice(0, index),
94
+ subpath: target.slice(index + 1),
95
+ };
96
+ }
97
+ function cleanOutputPath(...parts) {
98
+ return parts
99
+ .filter(Boolean)
100
+ .join('/')
101
+ .replace(/\/+/g, '/')
102
+ .replace(/^\.\//, '');
103
+ }
104
+ function parseFrontmatter(content) {
105
+ const lines = content.split(/\r?\n/u);
106
+ if (lines[0] !== '---')
107
+ return {};
108
+ const result = {};
109
+ for (let index = 1; index < lines.length; index += 1) {
110
+ const line = lines[index];
111
+ if (line === '---')
112
+ return result;
113
+ const match = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/u);
114
+ if (!match)
115
+ continue;
116
+ result[match[1]] = match[2].replace(/^['"]|['"]$/g, '');
117
+ }
118
+ return {};
119
+ }
@@ -0,0 +1,44 @@
1
+ import { spawn } from 'node:child_process';
2
+ export class CommandRunner {
3
+ ctx;
4
+ constructor(ctx) {
5
+ this.ctx = ctx;
6
+ }
7
+ async run(command, args = [], options = {}) {
8
+ const runContext = {
9
+ cwd: options.cwd || this.ctx.cwd,
10
+ command,
11
+ args,
12
+ env: options.env || this.ctx.env,
13
+ shell: options.shell,
14
+ };
15
+ await this.ctx.hooks.beforeRun(runContext);
16
+ const result = await new Promise((resolve) => {
17
+ const child = spawn(command, args, {
18
+ cwd: runContext.cwd,
19
+ env: runContext.env,
20
+ shell: runContext.shell,
21
+ stdio: options.stdio === 'inherit' ? 'inherit' : 'pipe',
22
+ });
23
+ const stdout = [];
24
+ const stderr = [];
25
+ if (child.stdout)
26
+ child.stdout.on('data', (chunk) => stdout.push(chunk));
27
+ if (child.stderr)
28
+ child.stderr.on('data', (chunk) => stderr.push(chunk));
29
+ child.on('close', (code, signal) => {
30
+ resolve({
31
+ command,
32
+ args,
33
+ cwd: runContext.cwd,
34
+ code,
35
+ signal,
36
+ stdout: Buffer.concat(stdout).toString('utf-8'),
37
+ stderr: Buffer.concat(stderr).toString('utf-8'),
38
+ });
39
+ });
40
+ });
41
+ await this.ctx.hooks.afterRun(runContext, result);
42
+ return result;
43
+ }
44
+ }
@@ -0,0 +1,4 @@
1
+ import { invoiceShortcut } from './invoice/index.js';
2
+ export const builtinShortcuts = [
3
+ invoiceShortcut,
4
+ ];
@@ -0,0 +1,70 @@
1
+ import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { existsSync } from 'node:fs';
3
+ import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
5
+ import { resolve } from 'node:path';
6
+ import { invoiceValidationError } from './errors.js';
7
+ function stateRoot(ctx) {
8
+ const configured = ctx?.config?.path();
9
+ if (configured)
10
+ return resolve(configured, '..');
11
+ return process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
12
+ }
13
+ async function loadKey(ctx) {
14
+ const keysDir = resolve(stateRoot(ctx), 'keys');
15
+ const filename = resolve(keysDir, 'invoice-continuation.key');
16
+ await mkdir(keysDir, { recursive: true, mode: 0o700 });
17
+ if (existsSync(filename))
18
+ return readFile(filename);
19
+ const key = randomBytes(32);
20
+ try {
21
+ await writeFile(filename, key, { flag: 'wx', mode: 0o600 });
22
+ if (process.platform !== 'win32')
23
+ await chmod(filename, 0o600).catch(() => undefined);
24
+ return key;
25
+ }
26
+ catch {
27
+ return readFile(filename);
28
+ }
29
+ }
30
+ function encode(value) {
31
+ return Buffer.from(JSON.stringify(value)).toString('base64url');
32
+ }
33
+ function signature(payload, key) {
34
+ return createHmac('sha256', key).update(payload).digest('base64url');
35
+ }
36
+ export function createInvoiceContinuationSigner(ctx) {
37
+ return {
38
+ async issue(payload, ttlMs = 10 * 60_000) {
39
+ const issuedAt = Date.now();
40
+ const body = encode({ ...payload, issuedAt, expiresAt: issuedAt + ttlMs });
41
+ const sig = signature(body, await loadKey(ctx));
42
+ return `${body}.${sig}`;
43
+ },
44
+ async verify(token, expectedOperation) {
45
+ if (typeof token !== 'string' || !token.includes('.')) {
46
+ throw invoiceValidationError('continuation_invalid', 'continuation 无效');
47
+ }
48
+ const [body, provided] = token.split('.');
49
+ const expected = signature(body, await loadKey(ctx));
50
+ const valid = provided.length === expected.length
51
+ && timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
52
+ if (!valid)
53
+ throw invoiceValidationError('continuation_invalid', 'continuation 签名校验失败');
54
+ let value;
55
+ try {
56
+ value = JSON.parse(Buffer.from(body, 'base64url').toString('utf8'));
57
+ }
58
+ catch {
59
+ throw invoiceValidationError('continuation_invalid', 'continuation 内容无法解析');
60
+ }
61
+ if (value.operation !== expectedOperation) {
62
+ throw invoiceValidationError('continuation_mismatch', 'continuation 不属于当前操作');
63
+ }
64
+ if (!Number.isSafeInteger(value.expiresAt) || value.expiresAt < Date.now()) {
65
+ throw invoiceValidationError('continuation_expired', 'continuation 已过期');
66
+ }
67
+ return value;
68
+ },
69
+ };
70
+ }
@@ -0,0 +1,59 @@
1
+ import { CliError, ExitCode } from '../../core/errors.js';
2
+ export class InvoiceCliError extends CliError {
3
+ type;
4
+ subtype;
5
+ code;
6
+ retryable;
7
+ stage;
8
+ details;
9
+ partialData;
10
+ constructor(type, subtype, message, options = {}) {
11
+ super(message, options.exitCode ?? ExitCode.InternalError);
12
+ this.name = 'InvoiceCliError';
13
+ this.type = type;
14
+ this.subtype = subtype;
15
+ this.code = options.code;
16
+ this.retryable = options.retryable ?? false;
17
+ this.stage = options.stage;
18
+ this.details = options.details;
19
+ this.partialData = options.partialData;
20
+ }
21
+ }
22
+ export function invoiceValidationError(subtype, message, details) {
23
+ return new InvoiceCliError('validation', subtype, message, {
24
+ details,
25
+ exitCode: ExitCode.UsageError,
26
+ });
27
+ }
28
+ export function invoiceAuthError(subtype, message, details) {
29
+ return new InvoiceCliError('authentication', subtype, message, {
30
+ details,
31
+ exitCode: ExitCode.AuthError,
32
+ });
33
+ }
34
+ export function invoiceNetworkError(subtype, message, options = {}) {
35
+ return new InvoiceCliError('network', subtype, message, {
36
+ ...options,
37
+ retryable: options.retryable ?? true,
38
+ exitCode: ExitCode.NetworkError,
39
+ });
40
+ }
41
+ export function invoiceConfirmationError(action, details) {
42
+ return new InvoiceCliError('confirmation', 'required', '该操作需要明确确认', {
43
+ details: { action, ...details },
44
+ exitCode: ExitCode.ConfirmationRequired,
45
+ });
46
+ }
47
+ export function invoicePartialError(message, partialData, details) {
48
+ return new InvoiceCliError('partial', 'write_uncertain', message, {
49
+ details,
50
+ partialData,
51
+ exitCode: ExitCode.Partial,
52
+ });
53
+ }
54
+ export function isInvoiceNetworkError(error) {
55
+ return Boolean(error
56
+ && typeof error === 'object'
57
+ && 'type' in error
58
+ && error.type === 'network');
59
+ }