@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.
- package/README.md +2 -2
- package/agents/android-to-hmos-00-orchestrator.md +1 -1
- package/agents/d2h.md +1 -1
- package/agents/d2h_v2.md +351 -0
- package/dist/bin/install-opencode.js +60 -0
- package/dist/src/install-opencode.js +135 -0
- package/package.json +51 -45
- package/skills/android2hmos_resources_convert/SKILL.md +37 -19
- package/skills/appgraph-rule-audit/SKILL.md +24 -5
- package/skills/harmonyos-migration-playbook/SKILL.md +30 -3
- package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +4 -4
- package/skills/harmonyos-migration-playbook/references/windows-environment.md +1 -1
- package/skills/maintaining-migration-report/SKILL.md +88 -99
- package/skills/maintaining-migration-report/references/final-acceptance.md +48 -0
- package/skills/maintaining-migration-report/references/source-provenance.md +66 -0
- package/skills/preparing-migration-workspace/SKILL.md +72 -67
- package/skills/preparing-migration-workspace/toolchain.json +1 -25
- package/bin/install-opencode.mjs +0 -57
- package/src/install-opencode.mjs +0 -110
package/bin/install-opencode.mjs
DELETED
|
@@ -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
|
-
}
|
package/src/install-opencode.mjs
DELETED
|
@@ -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
|
-
}
|