@harmonyos-arkts/d2h 0.1.1 → 0.1.3

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.
@@ -1,57 +0,0 @@
1
- #!/usr/bin/env node
2
- import { fileURLToPath } from 'node:url';
3
- import path from 'node:path';
4
- import os from 'node:os';
5
- import { installOpenCode } from '../src/install-opencode.mjs';
6
-
7
- const help = `Install this package's skills and agents into OpenCode or DevEco Code.
8
-
9
- Usage: d2h [options]
10
-
11
- --source-dir <path> Source root (defaults to this package)
12
- --agent <name> opencode (default) or deveco
13
- --config-dir <path> Override the selected agent's config root
14
- OPENCODE_CONFIG_DIR / DEVECO_CONFIG_DIR, or
15
- $XDG_CONFIG_HOME/<agent>, or ~/.config/<agent>
16
- --link Create absolute symbolic links (development only)
17
- --force Replace conflicting installed skills and agents
18
- --dry-run Print operations without writing files
19
- -h, --help Show this help
20
-
21
- Only skills/*/SKILL.md trees and agents/*.md are installed.
22
- Existing application configuration and AGENTS.md are not modified.
23
- With npx, prefer the default copy mode: its cache can be removed.
24
- `;
25
-
26
- try {
27
- const options = {
28
- sourceDir: path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'),
29
- agent: 'opencode',
30
- };
31
- const args = process.argv.slice(2);
32
- for (let index = 0; index < args.length; index++) {
33
- const arg = args[index];
34
- if (arg === '-h' || arg === '--help') {
35
- console.log(help);
36
- process.exit(0);
37
- }
38
- if (arg === '--source-dir' || arg === '--config-dir' || arg === '--agent') {
39
- const value = args[++index];
40
- if (!value || value.startsWith('--')) throw new Error(`${arg} requires a value`);
41
- options[{ '--source-dir': 'sourceDir', '--config-dir': 'configDir', '--agent': 'agent' }[arg]] = value;
42
- } else if (arg === '--link' || arg === '--force' || arg === '--dry-run') {
43
- options[{ '--link': 'link', '--force': 'force', '--dry-run': 'dryRun' }[arg]] = true;
44
- } else {
45
- throw new Error(`unknown option: ${arg}`);
46
- }
47
- }
48
- if (!['opencode', 'deveco'].includes(options.agent)) {
49
- throw new Error(`unsupported agent: ${options.agent}; choose opencode or deveco`);
50
- }
51
- options.configDir ??= process.env[options.agent === 'deveco' ? 'DEVECO_CONFIG_DIR' : 'OPENCODE_CONFIG_DIR'] || path.join(
52
- process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), options.agent);
53
- installOpenCode(options);
54
- } catch (error) {
55
- console.error(`install-opencode: ${error.message}`);
56
- process.exitCode = 1;
57
- }
@@ -1,110 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import os from 'node:os';
4
-
5
- function stat(file) {
6
- try { return fs.lstatSync(file); } catch (error) {
7
- if (error.code === 'ENOENT') return null;
8
- throw error;
9
- }
10
- }
11
-
12
- function sameTree(source, destination) {
13
- const left = stat(source);
14
- const right = stat(destination);
15
- if (!left || !right) return false;
16
- if (left.isSymbolicLink() || right.isSymbolicLink()) {
17
- if (!left.isSymbolicLink() || !right.isSymbolicLink()) return false;
18
- return fs.readlinkSync(source) === fs.readlinkSync(destination);
19
- }
20
- if (left.isDirectory() && right.isDirectory()) {
21
- const names = fs.readdirSync(source).sort();
22
- const otherNames = fs.readdirSync(destination).sort();
23
- return names.length === otherNames.length && names.every((name, index) =>
24
- name === otherNames[index] && sameTree(path.join(source, name), path.join(destination, name)));
25
- }
26
- return left.isFile() && right.isFile() && fs.readFileSync(source).equals(fs.readFileSync(destination));
27
- }
28
-
29
- function contains(parent, child) {
30
- const relative = path.relative(parent, child);
31
- return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
32
- }
33
-
34
- // Resolve existing ancestors too, including a config directory beneath a junction.
35
- function resolvedLocation(directory) {
36
- if (fs.existsSync(directory)) return fs.realpathSync(directory);
37
- const parent = path.dirname(directory);
38
- return parent === directory ? directory : path.join(resolvedLocation(parent), path.basename(directory));
39
- }
40
-
41
- export function installOpenCode({ sourceDir, configDir, agent = 'opencode', link = false, force = false, dryRun = false, log = console.log }) {
42
- if (!['opencode', 'deveco'].includes(agent)) throw new Error(`unsupported agent: ${agent}`);
43
- const application = agent === 'deveco' ? 'DevEco Code' : 'OpenCode';
44
- if (!configDir || ['.', '..'].includes(configDir)) throw new Error(`refusing unsafe OpenCode config directory: ${configDir}`);
45
- const sourceRoot = fs.realpathSync(path.resolve(sourceDir));
46
- const configRoot = resolvedLocation(path.resolve(configDir));
47
- if (configRoot === path.parse(configRoot).root || configRoot === fs.realpathSync(os.homedir()) ||
48
- contains(sourceRoot, configRoot) || contains(configRoot, sourceRoot)) {
49
- throw new Error(`refusing unsafe OpenCode config directory: ${configDir}`);
50
- }
51
- const entries = [];
52
- for (const kind of ['skills', 'agents']) {
53
- const source = path.join(sourceRoot, kind);
54
- if (!fs.statSync(source).isDirectory()) throw new Error(`${kind} directory not found: ${source}`);
55
- const destinationRoot = path.join(configRoot, kind);
56
- if (stat(destinationRoot)?.isSymbolicLink()) throw new Error(`refusing linked installation directory: ${destinationRoot}`);
57
- let count = 0;
58
- for (const name of fs.readdirSync(source).sort()) {
59
- const entrySource = path.join(source, name);
60
- if (kind === 'skills' ? !fs.existsSync(path.join(entrySource, 'SKILL.md')) :
61
- !name.endsWith('.md') || !fs.statSync(entrySource).isFile()) continue;
62
- count++;
63
- const destination = path.join(destinationRoot, name);
64
- let unchanged = false;
65
- if (link && stat(destination)?.isSymbolicLink()) {
66
- unchanged = path.resolve(path.dirname(destination), fs.readlinkSync(destination)) === entrySource;
67
- } else if (!link) {
68
- unchanged = sameTree(entrySource, destination);
69
- }
70
- if (!unchanged && stat(destination) && !force) {
71
- throw new Error(`${kind} '${name}' already exists at ${destination}; rerun with --force to replace it`);
72
- }
73
- entries.push({ kind, name, source: entrySource, destination, unchanged });
74
- }
75
- if (!count) throw new Error(`no ${kind} found under ${source}`);
76
- }
77
- for (const entry of entries) {
78
- if (entry.unchanged) { log(`unchanged ${entry.kind}: ${entry.name}`); continue; }
79
- if (dryRun) { log(`would install ${entry.kind}: ${entry.source} -> ${entry.destination} (${link ? 'link' : 'copy'})`); continue; }
80
- const parent = path.dirname(entry.destination);
81
- fs.mkdirSync(parent, { recursive: true });
82
- if (!contains(configRoot, fs.realpathSync(parent))) throw new Error(`installation escaped config directory: ${parent}`);
83
- const staging = fs.mkdtempSync(path.join(parent, '.migration-install-'));
84
- const staged = path.join(staging, 'new');
85
- const backup = path.join(staging, 'backup');
86
- try {
87
- if (link) fs.symlinkSync(entry.source, staged, process.platform === 'win32' && entry.kind === 'skills' ? 'junction' : undefined);
88
- else fs.cpSync(entry.source, staged, { recursive: true });
89
- if (stat(entry.destination)) fs.renameSync(entry.destination, backup);
90
- try { fs.renameSync(staged, entry.destination); } catch (error) {
91
- if (stat(backup)) fs.renameSync(backup, entry.destination);
92
- throw error;
93
- }
94
- } finally {
95
- // If restoring a replaced entry also failed, retain its backup for recovery.
96
- if (stat(backup) && !stat(entry.destination)) {
97
- log(`installation backup retained: ${backup}`);
98
- } else {
99
- fs.rmSync(staging, { recursive: true, force: true });
100
- }
101
- }
102
- log(`installed ${entry.kind}: ${entry.destination}`);
103
- }
104
- const skills = entries.filter(entry => entry.kind === 'skills').length;
105
- const agents = entries.filter(entry => entry.kind === 'agents').length;
106
- log(`${application} installation ${dryRun ? 'preview' : 'complete'}: ${skills} skill(s), ${agents} agent(s).`);
107
- log(`Config directory: ${configRoot}`);
108
- if (!dryRun) log(`Quit and restart ${application} to load the installed definitions.`);
109
- return { skills, agents, configRoot };
110
- }