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.
- package/LICENSE +21 -0
- package/README.md +422 -0
- package/dist/cmd/action.js +35 -0
- package/dist/cmd/commands/index.js +15 -0
- package/dist/cmd/config/index.js +64 -0
- package/dist/cmd/docs/index.js +56 -0
- package/dist/cmd/doctor/index.js +45 -0
- package/dist/cmd/e10/index.js +25 -0
- package/dist/cmd/index.js +18 -0
- package/dist/cmd/register.js +19 -0
- package/dist/cmd/root.js +36 -0
- package/dist/cmd/run/index.js +38 -0
- package/dist/cmd/setup/index.js +77 -0
- package/dist/cmd/skills/index.js +72 -0
- package/dist/core/config-store.js +43 -0
- package/dist/core/context.js +31 -0
- package/dist/core/errors.js +60 -0
- package/dist/core/hooks.js +41 -0
- package/dist/core/logger.js +27 -0
- package/dist/core/output.js +25 -0
- package/dist/core/write-with-retry.js +21 -0
- package/dist/index.js +11 -0
- package/dist/internal/e10/auth/commands.js +594 -0
- package/dist/internal/e10/auth/crypto.js +126 -0
- package/dist/internal/e10/auth/paths.js +12 -0
- package/dist/internal/e10/auth/session.js +449 -0
- package/dist/internal/e10/client.js +104 -0
- package/dist/internal/e10/context.js +20 -0
- package/dist/internal/e10/index.js +2 -0
- package/dist/internal/e10/types.js +1 -0
- package/dist/internal/skills/install.js +99 -0
- package/dist/internal/skills/reader.js +119 -0
- package/dist/services/command-runner.js +44 -0
- package/dist/shortcuts/index.js +4 -0
- package/dist/shortcuts/invoice/continuation.js +70 -0
- package/dist/shortcuts/invoice/errors.js +59 -0
- package/dist/shortcuts/invoice/host.js +337 -0
- package/dist/shortcuts/invoice/index.js +301 -0
- package/dist/shortcuts/invoice/manifest.js +288 -0
- package/dist/shortcuts/invoice/operations/add.js +106 -0
- package/dist/shortcuts/invoice/operations/delete.js +92 -0
- package/dist/shortcuts/invoice/operations/download.js +16 -0
- package/dist/shortcuts/invoice/operations/get.js +7 -0
- package/dist/shortcuts/invoice/operations/import.js +128 -0
- package/dist/shortcuts/invoice/operations/list.js +12 -0
- package/dist/shortcuts/invoice/operations/ocr-preview.js +40 -0
- package/dist/shortcuts/invoice/operations/registry.js +50 -0
- package/dist/shortcuts/invoice/operations/shared.js +640 -0
- package/dist/shortcuts/invoice/operations/types.js +1 -0
- package/dist/shortcuts/invoice/operations/update.js +109 -0
- package/dist/shortcuts/invoice/operations/upload.js +8 -0
- package/dist/shortcuts/invoice/operations/validate-preview.js +39 -0
- package/dist/shortcuts/invoice/operations.js +8 -0
- package/dist/types.js +1 -0
- package/docs/SKILL.md +36 -0
- package/docs/_catalog.md +12 -0
- package/docs/agent-invoice.md +53 -0
- package/docs/e10-auth.md +51 -0
- package/docs/invoice.md +99 -0
- package/package.json +51 -0
- package/scripts/clean.mjs +4 -0
- package/scripts/package-skill.mjs +164 -0
- package/scripts/sync-docs.mjs +17 -0
- package/skill-template/business-info.json +4 -0
- package/skill-template/domains/invoice.md +23 -0
- package/skill-template/domains/shared.md +8 -0
- package/skill-template/master-skill-template.md +29 -0
- package/skill-template/skill-template.md +60 -0
- package/skills/weaver-work-cli-invoice/SKILL.md +103 -0
- package/skills/weaver-work-cli-invoice/product.json +8 -0
- package/skills/weaver-work-cli-invoice/references/invoice-add.md +18 -0
- package/skills/weaver-work-cli-invoice/references/invoice-agent-entry.md +38 -0
- package/skills/weaver-work-cli-invoice/references/invoice-delete.md +20 -0
- package/skills/weaver-work-cli-invoice/references/invoice-detail.md +29 -0
- package/skills/weaver-work-cli-invoice/references/invoice-disabled-capabilities.md +26 -0
- package/skills/weaver-work-cli-invoice/references/invoice-download.md +22 -0
- package/skills/weaver-work-cli-invoice/references/invoice-enterprise-list.md +35 -0
- package/skills/weaver-work-cli-invoice/references/invoice-file-upload.md +28 -0
- package/skills/weaver-work-cli-invoice/references/invoice-import.md +28 -0
- package/skills/weaver-work-cli-invoice/references/invoice-ocr-preview.md +33 -0
- package/skills/weaver-work-cli-invoice/references/invoice-personal-list.md +47 -0
- package/skills/weaver-work-cli-invoice/references/invoice-update.md +38 -0
- package/skills/weaver-work-cli-invoice/references/invoice-validation-preview.md +26 -0
- package/skills/weaver-work-cli-shared/SKILL.md +53 -0
- package/skills/weaver-work-cli-shared/references/e10-auth-and-session.md +83 -0
- package/skills/weaver-work-cli-shared/references/high-risk-write.md +36 -0
- package/skills/weaver-work-cli-shared/references/json-output-contract.md +61 -0
- package/skills/weaver-work-cli-shared/references/weaver-work-cli-installation.md +61 -0
- package/templates/shortcut/index.ts.tpl +19 -0
- package/templates/shortcut/operations/ping.ts.tpl +7 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { commandsCommand } from './commands/index.js';
|
|
2
|
+
import { configCommand } from './config/index.js';
|
|
3
|
+
import { docsCommand } from './docs/index.js';
|
|
4
|
+
import { doctorCommand } from './doctor/index.js';
|
|
5
|
+
import { e10Command } from './e10/index.js';
|
|
6
|
+
import { runCommand } from './run/index.js';
|
|
7
|
+
import { setupCommand } from './setup/index.js';
|
|
8
|
+
import { skillsCommand } from './skills/index.js';
|
|
9
|
+
export const builtinCommands = [
|
|
10
|
+
configCommand,
|
|
11
|
+
docsCommand,
|
|
12
|
+
doctorCommand,
|
|
13
|
+
commandsCommand,
|
|
14
|
+
runCommand,
|
|
15
|
+
e10Command,
|
|
16
|
+
setupCommand,
|
|
17
|
+
skillsCommand,
|
|
18
|
+
];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createAction } from './action.js';
|
|
2
|
+
export async function registerCommands(program, ctx, commands) {
|
|
3
|
+
ctx.commands = commands.map((command) => ({
|
|
4
|
+
name: command.name,
|
|
5
|
+
description: command.description,
|
|
6
|
+
category: command.category,
|
|
7
|
+
}));
|
|
8
|
+
ctx.modules = ctx.commands;
|
|
9
|
+
for (const command of commands) {
|
|
10
|
+
if (command.hooks) {
|
|
11
|
+
ctx.hooks.register(command.hooks);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const action = createAction(ctx);
|
|
15
|
+
for (const command of commands) {
|
|
16
|
+
await command.register({ program, ctx, action });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export const registerModules = registerCommands;
|
package/dist/cmd/root.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { createCliContext } from '../core/context.js';
|
|
6
|
+
import { HookManager } from '../core/hooks.js';
|
|
7
|
+
const CLI_COMMAND_NAME = 'weaver-work-cli';
|
|
8
|
+
function readPackageInfo() {
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8'));
|
|
11
|
+
return {
|
|
12
|
+
name: pkg.name || CLI_COMMAND_NAME,
|
|
13
|
+
version: pkg.version || '0.0.0',
|
|
14
|
+
description: pkg.description,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function createProgram() {
|
|
18
|
+
const packageInfo = readPackageInfo();
|
|
19
|
+
const program = new Command();
|
|
20
|
+
const hooks = new HookManager();
|
|
21
|
+
const ctx = createCliContext(program, hooks, packageInfo);
|
|
22
|
+
program
|
|
23
|
+
.name(CLI_COMMAND_NAME)
|
|
24
|
+
.description(packageInfo.description || 'Extensible TypeScript CLI')
|
|
25
|
+
.version(packageInfo.version)
|
|
26
|
+
.option('--json', 'print command output as JSON')
|
|
27
|
+
.option('--quiet', 'suppress non-error logs')
|
|
28
|
+
.option('--verbose', 'print debug logs')
|
|
29
|
+
.option('--config <path>', 'use a custom weaver-work-cli config file')
|
|
30
|
+
.option('--auth <path>', 'use a specific E10 auth file')
|
|
31
|
+
.option('--profile <name>', 'use a specific E10 profile')
|
|
32
|
+
.option('--base-url <url>', 'override E10 base URL')
|
|
33
|
+
.option('--passport-url <url>', 'override E10 passport URL');
|
|
34
|
+
program.showHelpAfterError();
|
|
35
|
+
return { program, ctx };
|
|
36
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { CliError, ExitCode } from '../../core/errors.js';
|
|
3
|
+
import { CommandRunner } from '../../services/command-runner.js';
|
|
4
|
+
export const runCommand = {
|
|
5
|
+
name: 'run',
|
|
6
|
+
description: 'Generic external command runner with hooks',
|
|
7
|
+
category: 'system',
|
|
8
|
+
register({ program, action }) {
|
|
9
|
+
program.command('run')
|
|
10
|
+
.description('Run an external command through the shared command runner')
|
|
11
|
+
.argument('<command>', 'command to run')
|
|
12
|
+
.argument('[args...]', 'command arguments')
|
|
13
|
+
.option('--cwd <dir>', 'working directory')
|
|
14
|
+
.option('--shell', 'run through the platform shell')
|
|
15
|
+
.option('--inherit', 'inherit stdio instead of capturing output')
|
|
16
|
+
.allowUnknownOption(true)
|
|
17
|
+
.action(action(async (ctx, command, args, options) => {
|
|
18
|
+
if (typeof command !== 'string') {
|
|
19
|
+
throw new CliError('command must be a string', ExitCode.UsageError);
|
|
20
|
+
}
|
|
21
|
+
const opts = options && typeof options === 'object'
|
|
22
|
+
? options
|
|
23
|
+
: {};
|
|
24
|
+
const result = await new CommandRunner(ctx).run(command, Array.isArray(args) ? args.map(String) : [], {
|
|
25
|
+
cwd: opts.cwd ? resolve(opts.cwd) : undefined,
|
|
26
|
+
shell: opts.shell,
|
|
27
|
+
stdio: opts.inherit ? 'inherit' : 'pipe',
|
|
28
|
+
});
|
|
29
|
+
if (!opts.inherit && result.stdout)
|
|
30
|
+
process.stdout.write(result.stdout);
|
|
31
|
+
if (!opts.inherit && result.stderr)
|
|
32
|
+
process.stderr.write(result.stderr);
|
|
33
|
+
if (result.code && result.code !== 0) {
|
|
34
|
+
throw new CliError('command exited with code ' + result.code, result.code);
|
|
35
|
+
}
|
|
36
|
+
}));
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { findAuthFile } from '../../internal/e10/auth/session.js';
|
|
4
|
+
import { defaultCodexSkillsRoot, installPackagedSkills, resolvePackagedSkillNames } from '../../internal/skills/install.js';
|
|
5
|
+
const SKILLS_DIR = fileURLToPath(new URL('../../../skills/', import.meta.url));
|
|
6
|
+
function versionMajor(version) {
|
|
7
|
+
return Number(version.replace(/^v/, '').split('.')[0] || 0);
|
|
8
|
+
}
|
|
9
|
+
export const setupCommand = {
|
|
10
|
+
name: 'setup',
|
|
11
|
+
description: 'One-step setup checks for business shortcuts',
|
|
12
|
+
category: 'system',
|
|
13
|
+
register({ program, action }) {
|
|
14
|
+
const setup = program.command('setup')
|
|
15
|
+
.description('One-step setup checks for business shortcuts');
|
|
16
|
+
setup.command('invoice')
|
|
17
|
+
.description('Check invoice CLI readiness and optionally install Agent skills')
|
|
18
|
+
.option('--install-skill', 'install packaged invoice Agent skill into Codex skills directory')
|
|
19
|
+
.option('--skill-target-dir <path>', 'override target Codex skills directory')
|
|
20
|
+
.action(action((ctx, options) => {
|
|
21
|
+
const opts = options && typeof options === 'object'
|
|
22
|
+
? options
|
|
23
|
+
: {};
|
|
24
|
+
const authFile = findAuthFile(ctx.cwd, ctx.getGlobalOptions());
|
|
25
|
+
const packagedSkills = resolvePackagedSkillNames(SKILLS_DIR, 'invoice');
|
|
26
|
+
const checks = [
|
|
27
|
+
{
|
|
28
|
+
name: 'node',
|
|
29
|
+
ok: versionMajor(process.version) >= 18,
|
|
30
|
+
detail: process.version,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'package',
|
|
34
|
+
ok: true,
|
|
35
|
+
detail: ctx.packageInfo.name + '@' + ctx.packageInfo.version,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'invoiceSkillPackaged',
|
|
39
|
+
ok: packagedSkills.includes('weaver-work-cli-invoice'),
|
|
40
|
+
detail: packagedSkills.join(', '),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'e10Auth',
|
|
44
|
+
ok: Boolean(authFile && existsSync(authFile)),
|
|
45
|
+
detail: authFile || 'not found',
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
const skillInstall = opts.installSkill
|
|
49
|
+
? installPackagedSkills(SKILLS_DIR, opts.skillTargetDir || defaultCodexSkillsRoot(ctx.env), 'invoice')
|
|
50
|
+
: null;
|
|
51
|
+
const nextActions = [];
|
|
52
|
+
if (!checks.find((check) => check.name === 'e10Auth')?.ok) {
|
|
53
|
+
nextActions.push('Run: weaver-work-cli auth login --base-url <E10_BASE_URL>');
|
|
54
|
+
}
|
|
55
|
+
if (!skillInstall) {
|
|
56
|
+
nextActions.push('Optional Agent setup: weaver-work-cli skills install invoice');
|
|
57
|
+
}
|
|
58
|
+
nextActions.push('Verify invoice operations: weaver-work-cli invoice schema');
|
|
59
|
+
const ok = checks.every((check) => check.ok);
|
|
60
|
+
ctx.output.write({
|
|
61
|
+
ok,
|
|
62
|
+
module: 'invoice',
|
|
63
|
+
checks,
|
|
64
|
+
skillInstall,
|
|
65
|
+
nextActions,
|
|
66
|
+
}, {
|
|
67
|
+
text: [
|
|
68
|
+
ok ? 'Invoice setup checks passed.' : 'Invoice setup needs attention.',
|
|
69
|
+
...checks.map((check) => `- ${check.name}: ${check.ok ? 'ok' : 'missing'} (${check.detail})`),
|
|
70
|
+
...(skillInstall ? skillInstall.installed.map((entry) => `- installed skill: ${entry.name} -> ${entry.target}`) : []),
|
|
71
|
+
'Next:',
|
|
72
|
+
...nextActions.map((actionText) => `- ${actionText}`),
|
|
73
|
+
].join('\n'),
|
|
74
|
+
});
|
|
75
|
+
}));
|
|
76
|
+
},
|
|
77
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { CliError } from '../../core/errors.js';
|
|
4
|
+
import { defaultCodexSkillsRoot, installPackagedSkills } from '../../internal/skills/install.js';
|
|
5
|
+
import { SkillReader } from '../../internal/skills/reader.js';
|
|
6
|
+
const SKILLS_DIR = fileURLToPath(new URL('../../../skills/', import.meta.url));
|
|
7
|
+
function reader() {
|
|
8
|
+
if (!existsSync(SKILLS_DIR)) {
|
|
9
|
+
throw new CliError('skills directory does not exist: ' + SKILLS_DIR);
|
|
10
|
+
}
|
|
11
|
+
return new SkillReader(SKILLS_DIR);
|
|
12
|
+
}
|
|
13
|
+
function commandOptions(value) {
|
|
14
|
+
if (!value || typeof value !== 'object' || !('opts' in value))
|
|
15
|
+
return {};
|
|
16
|
+
const opts = value.opts();
|
|
17
|
+
return opts && typeof opts === 'object' ? opts : {};
|
|
18
|
+
}
|
|
19
|
+
export const skillsCommand = {
|
|
20
|
+
name: 'skills',
|
|
21
|
+
description: 'Read packaged agent skill content',
|
|
22
|
+
category: 'skills',
|
|
23
|
+
register({ program, action }) {
|
|
24
|
+
const skills = program.command('skills')
|
|
25
|
+
.description('Read packaged agent skill content');
|
|
26
|
+
skills.command('list')
|
|
27
|
+
.description('List skills, or list one layer under a skill path')
|
|
28
|
+
.argument('[target]', 'skill name or name/path')
|
|
29
|
+
.option('--json', 'accepted for symmetry; output is always JSON')
|
|
30
|
+
.action(action((ctx, target) => {
|
|
31
|
+
const skillReader = reader();
|
|
32
|
+
if (typeof target !== 'string' || !target) {
|
|
33
|
+
const listed = skillReader.list();
|
|
34
|
+
ctx.output.write({ ok: true, skills: listed, count: listed.length }, { json: true });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const listed = skillReader.listPath(target);
|
|
38
|
+
ctx.output.write({ ok: true, ...listed, count: listed.entries.length }, { json: true });
|
|
39
|
+
}));
|
|
40
|
+
skills.command('read')
|
|
41
|
+
.description('Print a skill SKILL.md or a file under the skill')
|
|
42
|
+
.argument('<target>', 'skill name or name/path')
|
|
43
|
+
.argument('[path]', 'path under the skill')
|
|
44
|
+
.option('--json', 'output as a JSON envelope instead of raw markdown')
|
|
45
|
+
.action(action((ctx, target, pathArg, command) => {
|
|
46
|
+
if (typeof target !== 'string')
|
|
47
|
+
throw new CliError('target must be a string');
|
|
48
|
+
const opts = commandOptions(command || pathArg);
|
|
49
|
+
const result = reader().read(target, typeof pathArg === 'string' ? pathArg : '');
|
|
50
|
+
if (opts.json || ctx.getGlobalOptions().json) {
|
|
51
|
+
ctx.output.write(result, { json: true });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
ctx.output.raw(result.content);
|
|
55
|
+
}));
|
|
56
|
+
skills.command('install')
|
|
57
|
+
.description('Install packaged skills into Codex skills directory')
|
|
58
|
+
.argument('[target]', 'skill name, short name such as invoice, or all', 'all')
|
|
59
|
+
.option('--target-dir <path>', 'override target skills directory')
|
|
60
|
+
.action(action((ctx, target, options) => {
|
|
61
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
62
|
+
const targetRoot = opts.targetDir || defaultCodexSkillsRoot(ctx.env);
|
|
63
|
+
const result = installPackagedSkills(SKILLS_DIR, targetRoot, typeof target === 'string' ? target : 'all');
|
|
64
|
+
ctx.output.write(result, {
|
|
65
|
+
text: [
|
|
66
|
+
'Installed packaged skills:',
|
|
67
|
+
...result.installed.map((entry) => `- ${entry.name} -> ${entry.target}`),
|
|
68
|
+
].join('\n'),
|
|
69
|
+
});
|
|
70
|
+
}));
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
export class ConfigStore {
|
|
4
|
+
getPath;
|
|
5
|
+
constructor(getPath) {
|
|
6
|
+
this.getPath = getPath;
|
|
7
|
+
}
|
|
8
|
+
path() {
|
|
9
|
+
return this.getPath();
|
|
10
|
+
}
|
|
11
|
+
all() {
|
|
12
|
+
const filePath = this.path();
|
|
13
|
+
if (!existsSync(filePath))
|
|
14
|
+
return {};
|
|
15
|
+
try {
|
|
16
|
+
const value = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
17
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
get(key) {
|
|
24
|
+
return this.all()[key];
|
|
25
|
+
}
|
|
26
|
+
set(key, value) {
|
|
27
|
+
const data = this.all();
|
|
28
|
+
data[key] = value;
|
|
29
|
+
this.save(data);
|
|
30
|
+
}
|
|
31
|
+
delete(key) {
|
|
32
|
+
const data = this.all();
|
|
33
|
+
const existed = Object.prototype.hasOwnProperty.call(data, key);
|
|
34
|
+
delete data[key];
|
|
35
|
+
this.save(data);
|
|
36
|
+
return existed;
|
|
37
|
+
}
|
|
38
|
+
save(data) {
|
|
39
|
+
const filePath = this.path();
|
|
40
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
41
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { ConfigStore } from './config-store.js';
|
|
4
|
+
import { Logger } from './logger.js';
|
|
5
|
+
import { Output } from './output.js';
|
|
6
|
+
export function createCliContext(program, hooks, packageInfo) {
|
|
7
|
+
const getGlobalOptions = () => program.opts();
|
|
8
|
+
const configStore = new ConfigStore(() => {
|
|
9
|
+
const opts = getGlobalOptions();
|
|
10
|
+
const home = process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
|
|
11
|
+
return opts.config ? resolve(opts.config) : resolve(home, 'config.json');
|
|
12
|
+
});
|
|
13
|
+
const partialCtx = {
|
|
14
|
+
cwd: process.cwd(),
|
|
15
|
+
env: process.env,
|
|
16
|
+
program,
|
|
17
|
+
hooks,
|
|
18
|
+
packageInfo,
|
|
19
|
+
commands: [],
|
|
20
|
+
modules: [],
|
|
21
|
+
getGlobalOptions,
|
|
22
|
+
forCommand(command) {
|
|
23
|
+
return { ...ctx, activeCommand: command };
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const ctx = partialCtx;
|
|
27
|
+
ctx.logger = new Logger(getGlobalOptions);
|
|
28
|
+
ctx.output = new Output(ctx);
|
|
29
|
+
ctx.config = configStore;
|
|
30
|
+
return ctx;
|
|
31
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export var ExitCode;
|
|
2
|
+
(function (ExitCode) {
|
|
3
|
+
ExitCode[ExitCode["Success"] = 0] = "Success";
|
|
4
|
+
ExitCode[ExitCode["GeneralError"] = 1] = "GeneralError";
|
|
5
|
+
ExitCode[ExitCode["UsageError"] = 2] = "UsageError";
|
|
6
|
+
ExitCode[ExitCode["AuthError"] = 3] = "AuthError";
|
|
7
|
+
ExitCode[ExitCode["NetworkError"] = 4] = "NetworkError";
|
|
8
|
+
ExitCode[ExitCode["InternalError"] = 5] = "InternalError";
|
|
9
|
+
ExitCode[ExitCode["PolicyError"] = 6] = "PolicyError";
|
|
10
|
+
ExitCode[ExitCode["ConfirmationRequired"] = 10] = "ConfirmationRequired";
|
|
11
|
+
ExitCode[ExitCode["Partial"] = 11] = "Partial";
|
|
12
|
+
})(ExitCode || (ExitCode = {}));
|
|
13
|
+
export class CliError extends Error {
|
|
14
|
+
exitCode;
|
|
15
|
+
constructor(message, exitCode = ExitCode.GeneralError) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'CliError';
|
|
18
|
+
this.exitCode = exitCode;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function isCliError(error) {
|
|
22
|
+
return error instanceof CliError;
|
|
23
|
+
}
|
|
24
|
+
export function isStructuredError(error) {
|
|
25
|
+
return Boolean(error
|
|
26
|
+
&& typeof error === 'object'
|
|
27
|
+
&& 'type' in error
|
|
28
|
+
&& 'subtype' in error
|
|
29
|
+
&& 'message' in error
|
|
30
|
+
&& typeof error.type === 'string'
|
|
31
|
+
&& typeof error.subtype === 'string'
|
|
32
|
+
&& typeof error.message === 'string');
|
|
33
|
+
}
|
|
34
|
+
export function toErrorEnvelope(error) {
|
|
35
|
+
if (isStructuredError(error)) {
|
|
36
|
+
return {
|
|
37
|
+
type: error.type,
|
|
38
|
+
subtype: error.subtype,
|
|
39
|
+
...(error.code === undefined ? {} : { code: error.code }),
|
|
40
|
+
message: error.message,
|
|
41
|
+
retryable: error.retryable ?? false,
|
|
42
|
+
...(error.stage ? { stage: error.stage } : {}),
|
|
43
|
+
...(error.details === undefined ? {} : { details: error.details }),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (isCliError(error)) {
|
|
47
|
+
return {
|
|
48
|
+
type: error.exitCode === ExitCode.AuthError ? 'authentication' : 'cli',
|
|
49
|
+
subtype: error.exitCode === ExitCode.UsageError ? 'usage' : 'error',
|
|
50
|
+
message: error.message,
|
|
51
|
+
retryable: false,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
type: 'internal',
|
|
56
|
+
subtype: 'unexpected',
|
|
57
|
+
message: error instanceof Error ? error.message : String(error),
|
|
58
|
+
retryable: false,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export class HookManager {
|
|
2
|
+
hooks = {
|
|
3
|
+
beforeCommand: [],
|
|
4
|
+
afterCommand: [],
|
|
5
|
+
onError: [],
|
|
6
|
+
beforeRun: [],
|
|
7
|
+
afterRun: [],
|
|
8
|
+
};
|
|
9
|
+
register(hooks) {
|
|
10
|
+
for (const [name, handler] of Object.entries(hooks)) {
|
|
11
|
+
if (handler) {
|
|
12
|
+
this.hooks[name].push(handler);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async beforeCommand(ctx) {
|
|
17
|
+
for (const hook of this.hooks.beforeCommand) {
|
|
18
|
+
await hook(ctx);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async afterCommand(ctx, result) {
|
|
22
|
+
for (const hook of this.hooks.afterCommand) {
|
|
23
|
+
await hook(ctx, result);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async onError(ctx, error) {
|
|
27
|
+
for (const hook of this.hooks.onError) {
|
|
28
|
+
await hook(ctx, error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async beforeRun(ctx) {
|
|
32
|
+
for (const hook of this.hooks.beforeRun) {
|
|
33
|
+
await hook(ctx);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async afterRun(ctx, result) {
|
|
37
|
+
for (const hook of this.hooks.afterRun) {
|
|
38
|
+
await hook(ctx, result);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export class Logger {
|
|
2
|
+
getOptions;
|
|
3
|
+
constructor(getOptions) {
|
|
4
|
+
this.getOptions = getOptions;
|
|
5
|
+
}
|
|
6
|
+
info(message) {
|
|
7
|
+
const opts = this.getOptions();
|
|
8
|
+
if (!opts.quiet && !opts.json) {
|
|
9
|
+
console.log(message);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
warn(message) {
|
|
13
|
+
const opts = this.getOptions();
|
|
14
|
+
if (!opts.quiet && !opts.json) {
|
|
15
|
+
console.warn(message);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
error(message) {
|
|
19
|
+
console.error(message);
|
|
20
|
+
}
|
|
21
|
+
debug(message) {
|
|
22
|
+
const opts = this.getOptions();
|
|
23
|
+
if (opts.verbose && !opts.json) {
|
|
24
|
+
console.error('[debug] ' + message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
export function writeRawJson(outputPath, value) {
|
|
4
|
+
const absolutePath = resolve(outputPath);
|
|
5
|
+
mkdirSync(dirname(absolutePath), { recursive: true });
|
|
6
|
+
writeFileSync(absolutePath, JSON.stringify(value, null, 2), 'utf-8');
|
|
7
|
+
return absolutePath;
|
|
8
|
+
}
|
|
9
|
+
export class Output {
|
|
10
|
+
ctx;
|
|
11
|
+
constructor(ctx) {
|
|
12
|
+
this.ctx = ctx;
|
|
13
|
+
}
|
|
14
|
+
write(value, options = {}) {
|
|
15
|
+
const globalOptions = this.ctx.getGlobalOptions();
|
|
16
|
+
if (globalOptions.json || options.json || !options.text) {
|
|
17
|
+
console.log(JSON.stringify(value, null, 2));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.log(options.text);
|
|
21
|
+
}
|
|
22
|
+
raw(message) {
|
|
23
|
+
console.log(message);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
const RETRIABLE_WRITE_ERRORS = new Set(['EPERM', 'EACCES', 'EBUSY', 'ENOTEMPTY']);
|
|
3
|
+
function sleepSync(ms) {
|
|
4
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
5
|
+
}
|
|
6
|
+
export function writeWithRetry(path, data, options) {
|
|
7
|
+
const maxAttempts = 4;
|
|
8
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
9
|
+
try {
|
|
10
|
+
writeFileSync(path, data, options);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
15
|
+
if (!RETRIABLE_WRITE_ERRORS.has(code) || attempt === maxAttempts - 1) {
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
sleepSync(80 * (attempt + 1));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { builtinCommands } from './cmd/index.js';
|
|
3
|
+
import { createProgram } from './cmd/root.js';
|
|
4
|
+
import { registerCommands } from './cmd/register.js';
|
|
5
|
+
import { builtinShortcuts } from './shortcuts/index.js';
|
|
6
|
+
const { program, ctx } = createProgram();
|
|
7
|
+
await registerCommands(program, ctx, [
|
|
8
|
+
...builtinCommands,
|
|
9
|
+
...builtinShortcuts,
|
|
10
|
+
]);
|
|
11
|
+
await program.parseAsync();
|