@kisev/agentomatic 11.0.2-dev.101.gae2702b7284e → 11.0.2-dev.103.g258eb838ddb7
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 +9 -6
- package/README.ru.md +10 -7
- package/dist/agent-profiles.d.ts +8 -2
- package/dist/agent-profiles.js +98 -34
- package/dist/cli-output.js +6 -6
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1260
- package/dist/command-cli.d.ts +1 -0
- package/dist/command-cli.js +992 -0
- package/dist/config-setup.d.ts +12 -1
- package/dist/config-setup.js +86 -13
- package/dist/doctor.js +3 -1
- package/dist/installer.d.ts +7 -2
- package/dist/installer.js +43 -19
- package/dist/opencode-config.d.ts +1 -0
- package/dist/opencode-config.js +12 -0
- package/dist/reconcile.d.ts +4 -1
- package/dist/reconcile.js +12 -16
- package/dist/self-install.d.ts +7 -0
- package/dist/self-install.js +52 -1
- package/dist/terminal-wizard.d.ts +1 -1
- package/dist/terminal-wizard.js +2 -2
- package/package.json +3 -3
package/dist/config-setup.d.ts
CHANGED
|
@@ -6,6 +6,11 @@ export declare const CONFIG_TARGETS: readonly ["opencode", "kilo", "mimo"];
|
|
|
6
6
|
export type ConfigTargetName = (typeof CONFIG_TARGETS)[number];
|
|
7
7
|
export type TargetFileKind = "main" | "tui";
|
|
8
8
|
export declare const CONFIG_FRAGMENTS: readonly [{
|
|
9
|
+
readonly name: "core-disable";
|
|
10
|
+
readonly description: "Disconnect only the agentomatic plugin, preserving unrelated configuration";
|
|
11
|
+
readonly targets: readonly ["opencode"];
|
|
12
|
+
readonly file: "main";
|
|
13
|
+
}, {
|
|
9
14
|
readonly name: "core-plugin";
|
|
10
15
|
readonly description: "Register @kisev/agentomatic in the plugin array (OpenCode only)";
|
|
11
16
|
readonly targets: readonly ["opencode"];
|
|
@@ -72,9 +77,15 @@ export declare function recoverConfigSetup(scope: Scope, dryRun: boolean, cwd?:
|
|
|
72
77
|
}>;
|
|
73
78
|
export declare function normalizeConfigSelection(scope: Scope, value?: Partial<ConfigSetupSelection>): ConfigSetupSelection;
|
|
74
79
|
export declare function defaultConfigSelection(scope: Scope, cwd?: string, home?: string): Promise<ConfigSetupSelection>;
|
|
75
|
-
export declare function
|
|
80
|
+
export declare function inspectIntegration(scope: Scope, cwd?: string, home?: string): Promise<{
|
|
81
|
+
path: string;
|
|
82
|
+
connected: boolean;
|
|
83
|
+
problem?: string;
|
|
84
|
+
}>;
|
|
85
|
+
export declare function previewConfigSetup(selection: ConfigSetupSelection, scope: Scope, cwd?: string, home?: string, provisionDependency?: boolean, syncSelection?: boolean): Promise<ConfigSetupPlan>;
|
|
76
86
|
export declare function applyConfigSetup(selection: ConfigSetupSelection, scope: Scope, cwd?: string, home?: string, options?: {
|
|
77
87
|
dependencyRunner?: DependencyRunner;
|
|
78
88
|
provisionDependency?: boolean;
|
|
79
89
|
receipt?: string;
|
|
90
|
+
syncSelection?: boolean;
|
|
80
91
|
}): Promise<ConfigSetupPlan>;
|
package/dist/config-setup.js
CHANGED
|
@@ -2,13 +2,12 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { lstat } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
|
-
import { archiveMutations } from "./installer.js";
|
|
5
|
+
import { archiveMutations, coreSelectionMutation } from "./installer.js";
|
|
6
6
|
import { requirePackageVersion } from "./package-metadata.js";
|
|
7
7
|
import { ensureDependency, planDependency } from "./self-install.js";
|
|
8
8
|
import { applyTransaction, archiveRoot, deploymentRoot, LifecycleError, lifecycleRoot, inspectTransaction, readRegular, recoverTransaction, sha256, withLifecycleLock, } from "./lifecycle.js";
|
|
9
9
|
import { applyJsoncEdits, parseJsonc } from "./jsonc.js";
|
|
10
|
-
import { corePluginEdits, permissionEdits } from "./opencode-config.js";
|
|
11
|
-
const PACKAGE_NAME = "@kisev/agentomatic";
|
|
10
|
+
import { corePluginEdits, corePluginRemovalEdits, permissionEdits, } from "./opencode-config.js";
|
|
12
11
|
const XDG_STATE_GLOB = "~/.local/state/agent-skills/**";
|
|
13
12
|
const OPENCODE_SKILLS_GLOB = "~/.config/opencode/skills/**";
|
|
14
13
|
const AGENTS_SKILLS_GLOB = "~/.agents/skills/**";
|
|
@@ -38,6 +37,12 @@ export class ConfigSetupError extends LifecycleError {
|
|
|
38
37
|
}
|
|
39
38
|
export const CONFIG_TARGETS = ["opencode", "kilo", "mimo"];
|
|
40
39
|
export const CONFIG_FRAGMENTS = [
|
|
40
|
+
{
|
|
41
|
+
name: "core-disable",
|
|
42
|
+
description: "Disconnect only the agentomatic plugin, preserving unrelated configuration",
|
|
43
|
+
targets: ["opencode"],
|
|
44
|
+
file: "main",
|
|
45
|
+
},
|
|
41
46
|
{
|
|
42
47
|
name: "core-plugin",
|
|
43
48
|
description: "Register @kisev/agentomatic in the plugin array (OpenCode only)",
|
|
@@ -86,16 +91,43 @@ async function configSnapshot(built) {
|
|
|
86
91
|
}
|
|
87
92
|
async function requireNoRecovery(stateRoot) {
|
|
88
93
|
if (await readRegular(join(stateRoot, "transaction-journal.json")))
|
|
89
|
-
throw new ConfigSetupError("recovery_required", "An interrupted transaction requires
|
|
94
|
+
throw new ConfigSetupError("recovery_required", "An interrupted transaction requires maintenance recover --dry-run, then confirmed maintenance recover before a fresh preview");
|
|
90
95
|
}
|
|
91
96
|
export async function recoverConfigSetup(scope, dryRun, cwd = process.cwd(), home = homedir(), expectedDigest) {
|
|
92
97
|
const root = deploymentRoot(scope, cwd, home);
|
|
93
98
|
const state = lifecycleRoot(scope, cwd, home);
|
|
94
|
-
const built = await build({ targets: [...CONFIG_TARGETS], fragments: [...FRAGMENT_NAMES] }, scope, cwd, home);
|
|
95
|
-
const allowed = [...built.allowedRoots, archiveRoot(scope, cwd, home)];
|
|
96
|
-
const paths = await inspectTransaction(root, state, allowed);
|
|
97
99
|
const journalPath = join(state, "transaction-journal.json");
|
|
98
100
|
const journal = await readRegular(journalPath);
|
|
101
|
+
const scopeRoot = resolve(scope === "global" ? home : cwd);
|
|
102
|
+
let recoveryRoot = root;
|
|
103
|
+
if (journal) {
|
|
104
|
+
let candidate;
|
|
105
|
+
try {
|
|
106
|
+
candidate = JSON.parse(journal.toString("utf8"));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
throw new ConfigSetupError("invalid_journal", "Transaction journal is not valid JSON");
|
|
110
|
+
}
|
|
111
|
+
if (candidate.root === scopeRoot && scopeRoot !== root) {
|
|
112
|
+
const prefix = scope === "global" ? ".config/opencode/" : ".opencode/";
|
|
113
|
+
if (!Array.isArray(candidate.operations) ||
|
|
114
|
+
candidate.operations.some((operation) => !operation.root && !operation.path?.startsWith(prefix)))
|
|
115
|
+
throw new ConfigSetupError("invalid_journal", "Cleanup journal contains paths outside the deployment");
|
|
116
|
+
recoveryRoot = scopeRoot;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const allowed = [
|
|
120
|
+
root,
|
|
121
|
+
archiveRoot(scope, cwd, home),
|
|
122
|
+
...(scope === "project"
|
|
123
|
+
? [resolve(cwd)]
|
|
124
|
+
: [
|
|
125
|
+
join(home, ".config", "opencode"),
|
|
126
|
+
join(home, ".config", "kilo"),
|
|
127
|
+
join(home, ".config", "mimocode"),
|
|
128
|
+
]),
|
|
129
|
+
];
|
|
130
|
+
const paths = await inspectTransaction(recoveryRoot, state, allowed);
|
|
99
131
|
const digest = journal ? sha256(journal) : null;
|
|
100
132
|
if (dryRun || !paths.length)
|
|
101
133
|
return { paths, recovered: false, digest };
|
|
@@ -103,7 +135,7 @@ export async function recoverConfigSetup(scope, dryRun, cwd = process.cwd(), hom
|
|
|
103
135
|
const current = await readRegular(journalPath);
|
|
104
136
|
if (!current || !expectedDigest || sha256(current) !== expectedDigest)
|
|
105
137
|
throw new ConfigSetupError("stale_receipt", "Recovery journal changed; preview recovery again");
|
|
106
|
-
return recoverTransaction(
|
|
138
|
+
return recoverTransaction(recoveryRoot, state, allowed);
|
|
107
139
|
});
|
|
108
140
|
return { paths, recovered, digest };
|
|
109
141
|
}
|
|
@@ -168,6 +200,8 @@ function fragmentEdits(fragment, target, current) {
|
|
|
168
200
|
if (fragment === "core-plugin") {
|
|
169
201
|
return corePluginEdits(value);
|
|
170
202
|
}
|
|
203
|
+
if (fragment === "core-disable")
|
|
204
|
+
return corePluginRemovalEdits(value);
|
|
171
205
|
if (fragment === "skills-state-permissions") {
|
|
172
206
|
const statePaths = target === "opencode"
|
|
173
207
|
? [XDG_STATE_GLOB, OPENCODE_SKILLS_GLOB, AGENTS_SKILLS_GLOB]
|
|
@@ -269,6 +303,8 @@ export function normalizeConfigSelection(scope, value = {}) {
|
|
|
269
303
|
if (scope === "project" && (targets.length !== 1 || targets[0] !== "opencode"))
|
|
270
304
|
throw new ConfigSetupError("invalid_selection", "Project scope supports only the opencode target");
|
|
271
305
|
const requestedFragments = value.fragments ?? [];
|
|
306
|
+
if (requestedFragments.includes("core-plugin") && requestedFragments.includes("core-disable"))
|
|
307
|
+
throw new ConfigSetupError("invalid_selection", "Choose either core-plugin or core-disable");
|
|
272
308
|
const fragments = FRAGMENT_NAMES.filter((fragment) => requestedFragments.includes(fragment));
|
|
273
309
|
if (fragments.length !== requestedFragments.length)
|
|
274
310
|
throw new ConfigSetupError("invalid_selection", "Unknown config fragment selection");
|
|
@@ -289,9 +325,26 @@ export async function defaultConfigSelection(scope, cwd = process.cwd(), home =
|
|
|
289
325
|
}
|
|
290
326
|
return normalizeConfigSelection(scope, {
|
|
291
327
|
targets: [...new Set(targets)],
|
|
292
|
-
fragments:
|
|
328
|
+
fragments: FRAGMENT_NAMES.filter((name) => name !== "core-disable"),
|
|
293
329
|
});
|
|
294
330
|
}
|
|
331
|
+
export async function inspectIntegration(scope, cwd = process.cwd(), home = homedir()) {
|
|
332
|
+
const file = await resolveTargetFile("opencode", scope, cwd, home);
|
|
333
|
+
const raw = await readRegular(file.absolute);
|
|
334
|
+
if (!raw)
|
|
335
|
+
return { path: file.absolute, connected: false };
|
|
336
|
+
try {
|
|
337
|
+
const current = parseJsonc(raw.toString("utf8"));
|
|
338
|
+
return { path: file.absolute, connected: corePluginRemovalEdits(current).length > 0 };
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
return {
|
|
342
|
+
path: file.absolute,
|
|
343
|
+
connected: false,
|
|
344
|
+
problem: error instanceof Error ? error.message : "Invalid configuration",
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
295
348
|
async function resolveTargetFile(target, scope, cwd, home, kind = "main") {
|
|
296
349
|
const existing = async (root, path) => Boolean(await readRegular(join(root, path)).catch(() => undefined));
|
|
297
350
|
const globalRoot = deploymentRoot("global", cwd, home);
|
|
@@ -376,7 +429,7 @@ async function resolveTargetFile(target, scope, cwd, home, kind = "main") {
|
|
|
376
429
|
exists: false,
|
|
377
430
|
};
|
|
378
431
|
}
|
|
379
|
-
async function build(selection, scope, cwd = process.cwd(), home = homedir()) {
|
|
432
|
+
async function build(selection, scope, cwd = process.cwd(), home = homedir(), syncSelection = true) {
|
|
380
433
|
const root = deploymentRoot(scope, cwd, home);
|
|
381
434
|
const operations = [];
|
|
382
435
|
const mutations = [];
|
|
@@ -492,6 +545,23 @@ async function build(selection, scope, cwd = process.cwd(), home = homedir()) {
|
|
|
492
545
|
? "TUI files are global-only"
|
|
493
546
|
: "no selected target supports this fragment",
|
|
494
547
|
}));
|
|
548
|
+
const core = selection.fragments.find((fragment) => fragment === "core-plugin" || fragment === "core-disable");
|
|
549
|
+
if (syncSelection &&
|
|
550
|
+
core &&
|
|
551
|
+
selection.targets.includes("opencode") &&
|
|
552
|
+
!operations.some((operation) => operation.fragment === core && operation.operation === "conflict")) {
|
|
553
|
+
const mutation = await coreSelectionMutation(root, core === "core-plugin");
|
|
554
|
+
if (mutation) {
|
|
555
|
+
mutations.push(mutation);
|
|
556
|
+
operations.push({
|
|
557
|
+
target: "opencode",
|
|
558
|
+
path: join(root, mutation.path),
|
|
559
|
+
fragment: core,
|
|
560
|
+
operation: "update",
|
|
561
|
+
reason: "Save connection choice for repeat install and repair",
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
495
565
|
const sorted = operations.sort((left, right) => CONFIG_TARGETS.indexOf(left.target) - CONFIG_TARGETS.indexOf(right.target) ||
|
|
496
566
|
FRAGMENT_NAMES.indexOf(left.fragment) -
|
|
497
567
|
FRAGMENT_NAMES.indexOf(right.fragment) ||
|
|
@@ -519,11 +589,11 @@ async function build(selection, scope, cwd = process.cwd(), home = homedir()) {
|
|
|
519
589
|
allowedRoots: [...allowedRoots],
|
|
520
590
|
};
|
|
521
591
|
}
|
|
522
|
-
export async function previewConfigSetup(selection, scope, cwd = process.cwd(), home = homedir(), provisionDependency = true) {
|
|
592
|
+
export async function previewConfigSetup(selection, scope, cwd = process.cwd(), home = homedir(), provisionDependency = true, syncSelection = true) {
|
|
523
593
|
const stateRoot = lifecycleRoot(scope, cwd, home);
|
|
524
594
|
try {
|
|
525
595
|
await requireNoRecovery(stateRoot);
|
|
526
|
-
const built = await build(selection, scope, cwd, home);
|
|
596
|
+
const built = await build(selection, scope, cwd, home, syncSelection);
|
|
527
597
|
if (provisionDependency &&
|
|
528
598
|
selection.fragments.includes("core-plugin") &&
|
|
529
599
|
selection.targets.includes("opencode"))
|
|
@@ -535,6 +605,7 @@ export async function previewConfigSetup(selection, scope, cwd = process.cwd(),
|
|
|
535
605
|
resolve(cwd),
|
|
536
606
|
resolve(home),
|
|
537
607
|
provisionDependency,
|
|
608
|
+
syncSelection,
|
|
538
609
|
]);
|
|
539
610
|
for (const [id, prior] of configReceipts)
|
|
540
611
|
if (prior.key === key || prior.expires < Date.now())
|
|
@@ -560,7 +631,8 @@ export async function applyConfigSetup(selection, scope, cwd = process.cwd(), ho
|
|
|
560
631
|
try {
|
|
561
632
|
return await withLifecycleLock(stateRoot, async () => {
|
|
562
633
|
await requireNoRecovery(stateRoot);
|
|
563
|
-
const
|
|
634
|
+
const syncSelection = options.syncSelection !== false;
|
|
635
|
+
const built = await build(selection, scope, cwd, home, syncSelection);
|
|
564
636
|
const provisionDependency = options.provisionDependency !== false;
|
|
565
637
|
if (provisionDependency &&
|
|
566
638
|
selection.fragments.includes("core-plugin") &&
|
|
@@ -572,6 +644,7 @@ export async function applyConfigSetup(selection, scope, cwd = process.cwd(), ho
|
|
|
572
644
|
resolve(cwd),
|
|
573
645
|
resolve(home),
|
|
574
646
|
provisionDependency,
|
|
647
|
+
syncSelection,
|
|
575
648
|
]);
|
|
576
649
|
const id = options.receipt ?? [...configReceipts].find(([, entry]) => entry.key === key)?.[0];
|
|
577
650
|
const receipt = id ? configReceipts.get(id) : undefined;
|
package/dist/doctor.js
CHANGED
|
@@ -447,7 +447,9 @@ export async function collectDoctorFacts(scope, cwd = process.cwd(), home = home
|
|
|
447
447
|
collisions: inventory?.collisions.length ?? 0,
|
|
448
448
|
drift: inventory?.drift.length ?? 0,
|
|
449
449
|
user_owned: inventory?.user_owned.length ?? 0,
|
|
450
|
-
}, [
|
|
450
|
+
}, [
|
|
451
|
+
"Resolve ownership conflicts and restore modified bytes; preview maintenance repair for missing selected files.",
|
|
452
|
+
]));
|
|
451
453
|
const config = await localConfig(scope, project, homeRoot);
|
|
452
454
|
if (config.incomplete)
|
|
453
455
|
partial.push("config.local");
|
package/dist/installer.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AgentProfileRequest } from "./agent-profiles.js";
|
|
1
2
|
import { LifecycleError, type FileMutation, type Scope, type TransactionOptions } from "./lifecycle.js";
|
|
2
3
|
import { CATALOG } from "./catalog.js";
|
|
3
4
|
import { type FixedAgentRole } from "./agent-profiles.js";
|
|
@@ -30,6 +31,7 @@ export type Plan = {
|
|
|
30
31
|
applied?: "changed" | "unchanged" | "skipped";
|
|
31
32
|
};
|
|
32
33
|
requires_restart: boolean;
|
|
34
|
+
digest: string;
|
|
33
35
|
};
|
|
34
36
|
export type ArchiveCandidate = {
|
|
35
37
|
path: string;
|
|
@@ -44,12 +46,15 @@ export type ArchiveCandidate = {
|
|
|
44
46
|
export declare const SELECTABLE_PLUGINS: SelectablePlugin[];
|
|
45
47
|
export declare const SKILL_COMMANDS: string[];
|
|
46
48
|
export declare function defaultSelection(): InstallerSelection;
|
|
49
|
+
export declare function installedSelection(scope: Scope, cwd?: string, home?: string): Promise<InstallerSelection | null>;
|
|
50
|
+
export declare function coreSelectionMutation(root: string, enabled: boolean): Promise<FileMutation | undefined>;
|
|
47
51
|
export declare function normalizeSelection(value?: Partial<InstallerSelection>): InstallerSelection;
|
|
48
52
|
export declare class InstallerError extends LifecycleError {
|
|
49
53
|
}
|
|
50
54
|
export declare function archiveMutations(candidates: readonly ArchiveCandidate[], scope: Scope, cwd: string, home: string, sourceVersion: string, timestamp?: string): Promise<FileMutation[]>;
|
|
51
|
-
export declare function preview(action: Action, scope: Scope, cwd?: string, home?: string, selection?: Partial<InstallerSelection>, provisionDependency?: boolean): Promise<Plan>;
|
|
55
|
+
export declare function preview(action: Action, scope: Scope, cwd?: string, home?: string, selection?: Partial<InstallerSelection>, provisionDependency?: boolean, profileChanges?: AgentProfileRequest[]): Promise<Plan>;
|
|
52
56
|
export declare function apply(action: Action, scope: Scope, cwd?: string, home?: string, options?: TransactionOptions & {
|
|
53
57
|
dependencyRunner?: DependencyRunner;
|
|
54
|
-
|
|
58
|
+
expectedDigest?: string;
|
|
59
|
+
}, selection?: Partial<InstallerSelection>, provisionDependency?: boolean, profileChanges?: AgentProfileRequest[]): Promise<Plan>;
|
|
55
60
|
export declare function result(plan: Plan, applied: boolean): string;
|
package/dist/installer.js
CHANGED
|
@@ -4,7 +4,7 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { buildAgentProfilePlan, listAgentProfiles, validateBuiltAgentProfilePlan, } from "./agent-profiles.js";
|
|
7
|
-
import { applyTransaction, archiveRoot, deploymentRoot, destination, LifecycleError, lifecycleRoot, migrateLegacyDeploymentNamespace, migrateLegacyNamespaces, readRegular,
|
|
7
|
+
import { applyTransaction, archiveRoot, deploymentRoot, destination, LifecycleError, lifecycleRoot, migrateLegacyDeploymentNamespace, migrateLegacyNamespaces, readRegular, sha256, stable, withLifecycleLock, } from "./lifecycle.js";
|
|
8
8
|
import { CATALOG } from "./catalog.js";
|
|
9
9
|
import { FIXED_AGENT_ROLES } from "./agent-profiles.js";
|
|
10
10
|
import { readPackageVersion } from "./package-metadata.js";
|
|
@@ -33,6 +33,29 @@ export function defaultSelection() {
|
|
|
33
33
|
core_activation: true,
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
+
export async function installedSelection(scope, cwd = process.cwd(), home = homedir()) {
|
|
37
|
+
const { manifest } = await currentManifest(deploymentRoot(scope, cwd, home));
|
|
38
|
+
return manifest
|
|
39
|
+
? normalizeSelection({
|
|
40
|
+
commands: manifest.commands,
|
|
41
|
+
agents: manifest.agents,
|
|
42
|
+
plugins: manifest.plugins,
|
|
43
|
+
core_activation: manifest.core_activation,
|
|
44
|
+
})
|
|
45
|
+
: null;
|
|
46
|
+
}
|
|
47
|
+
export async function coreSelectionMutation(root, enabled) {
|
|
48
|
+
const owned = await currentManifest(root);
|
|
49
|
+
if (!owned.manifest || !owned.raw || owned.manifest.core_activation === enabled)
|
|
50
|
+
return undefined;
|
|
51
|
+
return {
|
|
52
|
+
path: MANIFEST_NAME,
|
|
53
|
+
operation: "write",
|
|
54
|
+
content: Buffer.from(`${stable({ ...owned.manifest, core_activation: enabled })}\n`),
|
|
55
|
+
mode: 0o600,
|
|
56
|
+
expected: { sha256: sha256(owned.raw) },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
36
59
|
export function normalizeSelection(value = {}) {
|
|
37
60
|
const allCommands = new Set(SKILL_COMMANDS);
|
|
38
61
|
const commands = [...new Set(value.commands ?? defaultSelection().commands)].sort();
|
|
@@ -325,7 +348,7 @@ function asLegacy(manifest) {
|
|
|
325
348
|
? manifest
|
|
326
349
|
: undefined;
|
|
327
350
|
}
|
|
328
|
-
async function build(action, scope, cwd = process.cwd(), home = homedir(), requestedSelection, provisionDependency = true) {
|
|
351
|
+
async function build(action, scope, cwd = process.cwd(), home = homedir(), requestedSelection, provisionDependency = true, profileChanges = []) {
|
|
329
352
|
const root = deploymentRoot(scope, cwd, home);
|
|
330
353
|
const owned = await currentManifest(root);
|
|
331
354
|
const selection = normalizeSelection(requestedSelection ??
|
|
@@ -346,7 +369,7 @@ async function build(action, scope, cwd = process.cwd(), home = homedir(), reque
|
|
|
346
369
|
manifestSha256: sha256(owned.raw),
|
|
347
370
|
}
|
|
348
371
|
: undefined;
|
|
349
|
-
const profiles = await buildAgentProfilePlan({ action }, scope, cwd, home, legacyRecord, selection.agents);
|
|
372
|
+
const profiles = await buildAgentProfilePlan({ action, changes: profileChanges }, scope, cwd, home, legacyRecord, selection.agents);
|
|
350
373
|
const dependency = action === "install" && provisionDependency && selection.core_activation
|
|
351
374
|
? planDependency(scope, cwd, home)
|
|
352
375
|
: undefined;
|
|
@@ -628,24 +651,23 @@ async function build(action, scope, cwd = process.cwd(), home = homedir(), reque
|
|
|
628
651
|
item.path.startsWith("plugins/")),
|
|
629
652
|
};
|
|
630
653
|
return {
|
|
631
|
-
plan: {
|
|
654
|
+
plan: {
|
|
655
|
+
...base,
|
|
656
|
+
digest: sha256(stable({ base, mutations, manifest: owned.raw, profiles: profiles.plan.digest })),
|
|
657
|
+
},
|
|
632
658
|
mutations,
|
|
633
659
|
expectedManifest: manifestContent,
|
|
634
660
|
profiles,
|
|
635
661
|
};
|
|
636
662
|
}
|
|
637
|
-
export async function preview(action, scope, cwd = process.cwd(), home = homedir(), selection, provisionDependency = true) {
|
|
638
|
-
//
|
|
639
|
-
// preserves every legacy byte, and never touches user configuration.
|
|
640
|
-
await migrateLegacyNamespaces(home);
|
|
663
|
+
export async function preview(action, scope, cwd = process.cwd(), home = homedir(), selection, provisionDependency = true, profileChanges = []) {
|
|
664
|
+
// Preview never migrates namespaces, creates locks, or recovers transactions.
|
|
641
665
|
const stateRoot = lifecycleRoot(scope, cwd, home);
|
|
642
|
-
const root = deploymentRoot(scope, cwd, home);
|
|
643
666
|
try {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
});
|
|
667
|
+
if (await readRegular(join(stateRoot, "transaction-journal.json")))
|
|
668
|
+
throw new InstallerError("recovery_required", "Run maintenance recover before a fresh preview");
|
|
669
|
+
return (await build(action, scope, cwd, home, selection, provisionDependency, profileChanges))
|
|
670
|
+
.plan;
|
|
649
671
|
}
|
|
650
672
|
catch (error) {
|
|
651
673
|
if (error instanceof InstallerError)
|
|
@@ -655,7 +677,7 @@ export async function preview(action, scope, cwd = process.cwd(), home = homedir
|
|
|
655
677
|
throw error;
|
|
656
678
|
}
|
|
657
679
|
}
|
|
658
|
-
export async function apply(action, scope, cwd = process.cwd(), home = homedir(), options = {}, selection, provisionDependency = true) {
|
|
680
|
+
export async function apply(action, scope, cwd = process.cwd(), home = homedir(), options = {}, selection, provisionDependency = true, profileChanges = []) {
|
|
659
681
|
const { dependencyRunner, ...transactionOptions } = options;
|
|
660
682
|
const stateRoot = lifecycleRoot(scope, cwd, home);
|
|
661
683
|
const root = deploymentRoot(scope, cwd, home);
|
|
@@ -663,9 +685,11 @@ export async function apply(action, scope, cwd = process.cwd(), home = homedir()
|
|
|
663
685
|
try {
|
|
664
686
|
return await withLifecycleLock(stateRoot, async () => {
|
|
665
687
|
await migrateLegacyDeploymentNamespace(root);
|
|
666
|
-
if (await
|
|
667
|
-
throw new InstallerError("
|
|
668
|
-
const built = await build(action, scope, cwd, home, selection, provisionDependency);
|
|
688
|
+
if (await readRegular(join(stateRoot, "transaction-journal.json")))
|
|
689
|
+
throw new InstallerError("recovery_required", "Run maintenance recover before a fresh preview");
|
|
690
|
+
const built = await build(action, scope, cwd, home, selection, provisionDependency, profileChanges);
|
|
691
|
+
if (options.expectedDigest && built.plan.digest !== options.expectedDigest)
|
|
692
|
+
throw new InstallerError("stale_plan", "Installation changed; request a fresh preview");
|
|
669
693
|
if (built.plan.operations.some((item) => item.operation === "conflict" &&
|
|
670
694
|
(item.reason === "unmanaged_file" ||
|
|
671
695
|
item.reason === "v1.0.0_agent_ownership_mismatch" ||
|
|
@@ -689,7 +713,7 @@ export async function apply(action, scope, cwd = process.cwd(), home = homedir()
|
|
|
689
713
|
inventory.drift.length ||
|
|
690
714
|
inventory.profiles
|
|
691
715
|
.filter((item) => item.ownership !== "user-owned")
|
|
692
|
-
.some((item) => item.state !== "current"))) {
|
|
716
|
+
.some((item) => item.state !== "current" && item.state !== "not-installed"))) {
|
|
693
717
|
throw new InstallerError("final_validation_failed", "Final installed agent inventory is invalid");
|
|
694
718
|
}
|
|
695
719
|
},
|
|
@@ -6,4 +6,5 @@ export type PermissionRule = {
|
|
|
6
6
|
};
|
|
7
7
|
export declare function permissionEdits(config: Record<string, unknown>, additions: PermissionRule[]): JsoncEdit[];
|
|
8
8
|
export declare function matchesWildcard(pattern: string, value: string): boolean;
|
|
9
|
+
export declare function corePluginRemovalEdits(config: Record<string, unknown>): JsoncEdit[];
|
|
9
10
|
export declare function corePluginEdits(config: Record<string, unknown>): JsoncEdit[];
|
package/dist/opencode-config.js
CHANGED
|
@@ -122,6 +122,18 @@ function ourEntry(entry) {
|
|
|
122
122
|
return OUR_PACKAGE.test(entry);
|
|
123
123
|
return object(entry) && typeof entry.package === "string" && OUR_PACKAGE.test(entry.package);
|
|
124
124
|
}
|
|
125
|
+
export function corePluginRemovalEdits(config) {
|
|
126
|
+
return ["plugin", "plugins"].flatMap((key) => {
|
|
127
|
+
if (!(key in config))
|
|
128
|
+
return [];
|
|
129
|
+
plugins(config[key], key === "plugin");
|
|
130
|
+
const entries = config[key];
|
|
131
|
+
const remaining = entries.filter((entry) => !ourEntry(Array.isArray(entry) ? entry[0] : entry));
|
|
132
|
+
return remaining.length === entries.length
|
|
133
|
+
? []
|
|
134
|
+
: [{ kind: "set-value", path: [key], value: remaining }];
|
|
135
|
+
});
|
|
136
|
+
}
|
|
125
137
|
export function corePluginEdits(config) {
|
|
126
138
|
const edits = [
|
|
127
139
|
{ kind: "set-if-absent", path: ["$schema"], value: "https://opencode.ai/config.json" },
|
package/dist/reconcile.d.ts
CHANGED
|
@@ -27,13 +27,16 @@ export type ReconcilePlan = {
|
|
|
27
27
|
sha256?: string;
|
|
28
28
|
}>;
|
|
29
29
|
confirmable: boolean;
|
|
30
|
+
digest: string;
|
|
30
31
|
};
|
|
31
32
|
export type ReconcileResult = {
|
|
32
33
|
status: "ok";
|
|
33
34
|
applied: true;
|
|
34
35
|
plan: ReconcilePlan;
|
|
35
36
|
};
|
|
36
|
-
export type ReconcileOptions = TransactionOptions
|
|
37
|
+
export type ReconcileOptions = TransactionOptions & {
|
|
38
|
+
expectedDigest?: string;
|
|
39
|
+
};
|
|
37
40
|
export declare class ReconcileError extends LifecycleError {
|
|
38
41
|
}
|
|
39
42
|
export declare function previewReconcile(scope: Scope, cwd?: string, home?: string): Promise<ReconcilePlan>;
|
package/dist/reconcile.js
CHANGED
|
@@ -3,7 +3,7 @@ import { lstat, readdir, readFile } from "node:fs/promises";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { applyTransaction,
|
|
6
|
+
import { applyTransaction, assertSafePath, destination, LifecycleError, lifecycleRoot, readRegular, sha256, stable, withLifecycleLock, } from "./lifecycle.js";
|
|
7
7
|
import { archiveMutations } from "./installer.js";
|
|
8
8
|
const PACKAGE_NAME = "@kisev/agentomatic";
|
|
9
9
|
const GENERIC_MANIFEST = ".agentomatic-manifest.json";
|
|
@@ -35,9 +35,6 @@ function loadInventory() {
|
|
|
35
35
|
function scopeRoot(scope, cwd, home) {
|
|
36
36
|
return scope === "global" ? resolve(home) : resolve(cwd);
|
|
37
37
|
}
|
|
38
|
-
function reconcileAllowedRoots(scope, cwd, home) {
|
|
39
|
-
return [archiveRoot(scope, cwd, home)];
|
|
40
|
-
}
|
|
41
38
|
function relativePath(root, target) {
|
|
42
39
|
const value = relative(resolve(root), resolve(target));
|
|
43
40
|
if (!value || value.startsWith(`..${sep}`) || value === "..")
|
|
@@ -398,22 +395,19 @@ async function build(scope, cwd = process.cwd(), home = homedir()) {
|
|
|
398
395
|
groups["modified-managed"].length === 0 &&
|
|
399
396
|
groups.conflict.length === 0;
|
|
400
397
|
return {
|
|
401
|
-
plan: { ...base, confirmable },
|
|
398
|
+
plan: { ...base, confirmable, digest: sha256(stable({ base, mutations })) },
|
|
402
399
|
mutations,
|
|
403
400
|
};
|
|
404
401
|
}
|
|
405
402
|
export async function previewReconcile(scope, cwd = process.cwd(), home = homedir()) {
|
|
406
403
|
const stateRoot = lifecycleRoot(scope, cwd, home);
|
|
407
|
-
const root = scopeRoot(scope, cwd, home);
|
|
408
404
|
try {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
return { ...built.plan, confirmable: !blocked && actionable };
|
|
416
|
-
});
|
|
405
|
+
if (await readRegular(join(stateRoot, "transaction-journal.json")))
|
|
406
|
+
throw new ReconcileError("recovery_required", "Run maintenance recover before a fresh preview");
|
|
407
|
+
const built = await build(scope, cwd, home);
|
|
408
|
+
const blocked = built.plan.modified_managed.length > 0 || built.plan.conflicts.length > 0;
|
|
409
|
+
const actionable = built.plan.operations.length > 0;
|
|
410
|
+
return { ...built.plan, confirmable: !blocked && actionable };
|
|
417
411
|
}
|
|
418
412
|
catch (error) {
|
|
419
413
|
if (error instanceof ReconcileError)
|
|
@@ -432,9 +426,11 @@ export async function applyReconcile(scope, cwd = process.cwd(), home = homedir(
|
|
|
432
426
|
const root = scopeRoot(scope, cwd, home);
|
|
433
427
|
try {
|
|
434
428
|
return await withLifecycleLock(stateRoot, async () => {
|
|
435
|
-
if (await
|
|
436
|
-
throw new ReconcileError("
|
|
429
|
+
if (await readRegular(join(stateRoot, "transaction-journal.json")))
|
|
430
|
+
throw new ReconcileError("recovery_required", "Run maintenance recover before a fresh preview");
|
|
437
431
|
const built = await build(scope, cwd, home);
|
|
432
|
+
if (options.expectedDigest && options.expectedDigest !== built.plan.digest)
|
|
433
|
+
throw new ReconcileError("stale_plan", "Cleanup sources changed; request a fresh preview");
|
|
438
434
|
if (built.plan.conflicts.length || built.plan.modified_managed.length)
|
|
439
435
|
throw new ReconcileError("conflict", "Reconcile contains unsafe ownership conflicts");
|
|
440
436
|
await applyTransaction(root, stateRoot, built.mutations, {
|
package/dist/self-install.d.ts
CHANGED
|
@@ -24,3 +24,10 @@ export declare function planDependency(scope: "project" | "global", cwd?: string
|
|
|
24
24
|
export declare function ensureDependency(plan: DependencyPlan, runner?: DependencyRunner): Promise<DependencyPlan & {
|
|
25
25
|
applied: "changed" | "unchanged" | "skipped";
|
|
26
26
|
}>;
|
|
27
|
+
export type DependencyRemovalPlan = {
|
|
28
|
+
dir: string | null;
|
|
29
|
+
names: string[];
|
|
30
|
+
sources: Record<string, string | null>;
|
|
31
|
+
};
|
|
32
|
+
export declare function previewDependencyRemoval(scope: "project" | "global", cwd?: string, home?: string): Promise<DependencyRemovalPlan>;
|
|
33
|
+
export declare function removeDependency(plan: DependencyRemovalPlan, runner?: DependencyRunner): Promise<void>;
|
package/dist/self-install.js
CHANGED
|
@@ -3,7 +3,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
-
import { LifecycleError, writeAtomic } from "@kisev/safe-fs";
|
|
6
|
+
import { LifecycleError, writeAtomic, readRegular } from "@kisev/safe-fs";
|
|
7
|
+
import { sha256 } from "./lifecycle.js";
|
|
7
8
|
import { requirePackageVersion } from "./package-metadata.js";
|
|
8
9
|
const execFileAsync = promisify(execFile);
|
|
9
10
|
export const SELF_PACKAGE_NAME = "@kisev/agentomatic";
|
|
@@ -127,3 +128,53 @@ export async function ensureDependency(plan, runner = execFileAsync) {
|
|
|
127
128
|
}
|
|
128
129
|
return { ...plan, applied: "changed", ...(removedLegacy ? { removed_legacy: true } : {}) };
|
|
129
130
|
}
|
|
131
|
+
export async function previewDependencyRemoval(scope, cwd = process.cwd(), home = homedir()) {
|
|
132
|
+
const dir = owningProjectDir(scope, cwd, home);
|
|
133
|
+
if (!dir)
|
|
134
|
+
return { dir, names: [], sources: {} };
|
|
135
|
+
const sources = {};
|
|
136
|
+
for (const name of ["package.json", "package-lock.json"]) {
|
|
137
|
+
const raw = await readRegular(join(dir, name));
|
|
138
|
+
sources[name] = raw ? sha256(raw) : null;
|
|
139
|
+
}
|
|
140
|
+
const raw = await readRegular(join(dir, "package.json"));
|
|
141
|
+
if (!raw)
|
|
142
|
+
return { dir, names: [], sources };
|
|
143
|
+
let parsed;
|
|
144
|
+
try {
|
|
145
|
+
parsed = JSON.parse(raw.toString("utf8"));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
throw new SelfInstallError("invalid_configuration", "Cannot inspect dependency removal: invalid package.json");
|
|
149
|
+
}
|
|
150
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
151
|
+
throw new SelfInstallError("invalid_configuration", "Cannot inspect dependency removal: package.json must be an object");
|
|
152
|
+
const names = [SELF_PACKAGE_NAME, LEGACY_SELF_PACKAGE_NAME].filter((name) => ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"].some((key) => {
|
|
153
|
+
const section = parsed[key];
|
|
154
|
+
return section && typeof section === "object" && name in section;
|
|
155
|
+
}));
|
|
156
|
+
return { dir, names, sources };
|
|
157
|
+
}
|
|
158
|
+
export async function removeDependency(plan, runner = execFileAsync) {
|
|
159
|
+
if (!plan.dir || !plan.names.length)
|
|
160
|
+
return;
|
|
161
|
+
for (const [name, expected] of Object.entries(plan.sources)) {
|
|
162
|
+
const raw = await readRegular(join(plan.dir, name));
|
|
163
|
+
if ((raw ? sha256(raw) : null) !== expected)
|
|
164
|
+
throw new SelfInstallError("stale_plan", "npm files changed; preview uninstall again");
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
await runner("npm", ["rm", ...plan.names, "--no-audit", "--no-fund"], {
|
|
168
|
+
cwd: plan.dir,
|
|
169
|
+
timeout: NPM_INSTALL_TIMEOUT_MS,
|
|
170
|
+
});
|
|
171
|
+
const raw = await readRegular(join(plan.dir, "package.json"));
|
|
172
|
+
const parsed = raw ? JSON.parse(raw.toString("utf8")) : undefined;
|
|
173
|
+
if (!parsed ||
|
|
174
|
+
plan.names.some((name) => ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"].some((key) => parsed[key] && name in parsed[key])))
|
|
175
|
+
throw new Error("npm did not remove the selected dependencies");
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
throw new SelfInstallError("npm_dependency_failed", "npm removal failed after local stages; inspect package.json and retry uninstall --remove-dependency");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -2,4 +2,4 @@ import type { Readable, Writable } from "node:stream";
|
|
|
2
2
|
export declare function selectOption(label: string, options: readonly string[], stdin?: Readable, stderr?: Writable, initial?: number): Promise<number | null>;
|
|
3
3
|
export declare function selectOptions(label: string, options: readonly string[], initialSelected?: readonly string[], stdin?: Readable, stderr?: Writable): Promise<string[] | null>;
|
|
4
4
|
export declare function promptText(label: string, stdin?: Readable, stderr?: Writable): Promise<string | null>;
|
|
5
|
-
export declare function confirmQuestion(label: string, stdin?: Readable, stderr?: Writable): Promise<boolean | null>;
|
|
5
|
+
export declare function confirmQuestion(label: string, stdin?: Readable, stderr?: Writable, initialValue?: boolean): Promise<boolean | null>;
|
package/dist/terminal-wizard.js
CHANGED
|
@@ -55,9 +55,9 @@ export async function promptText(label, stdin = process.stdin, stderr = process.
|
|
|
55
55
|
const trimmed = value.trim();
|
|
56
56
|
return trimmed.length ? trimmed : null;
|
|
57
57
|
}
|
|
58
|
-
export async function confirmQuestion(label, stdin = process.stdin, stderr = process.stderr) {
|
|
58
|
+
export async function confirmQuestion(label, stdin = process.stdin, stderr = process.stderr, initialValue = true) {
|
|
59
59
|
interactiveStreams(stdin, stderr);
|
|
60
|
-
const value = await clack.confirm({ message: label, input: stdin, output: stderr });
|
|
60
|
+
const value = await clack.confirm({ message: label, input: stdin, output: stderr, initialValue });
|
|
61
61
|
if (clack.isCancel(value))
|
|
62
62
|
return null;
|
|
63
63
|
return value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kisev/agentomatic",
|
|
3
|
-
"version": "11.0.2-dev.
|
|
3
|
+
"version": "11.0.2-dev.103.g258eb838ddb7",
|
|
4
4
|
"description": "OpenCode integration and opt-in installer for portable Agent Skills.",
|
|
5
5
|
"homepage": "https://github.com/kisev/skills#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -40,13 +40,13 @@
|
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "node scripts/clean-dist.mjs && tsc --project tsconfig.json && node dist/generate-assets.js",
|
|
43
|
-
"test": "node --test test/agent-profiles.test.mjs test/config-setup.test.mjs test/legacy-migration.test.mjs test/doctor.test.mjs test/package.test.mjs test/rtk.test.mjs test/self-install.test.mjs test/stage19.test.mjs test/surface-coverage.test.mjs test/opencode-compatibility.test.mjs",
|
|
43
|
+
"test": "node --test test/agent-profiles.test.mjs test/command-cli.test.mjs test/config-setup.test.mjs test/legacy-migration.test.mjs test/doctor.test.mjs test/package.test.mjs test/rtk.test.mjs test/self-install.test.mjs test/stage19.test.mjs test/surface-coverage.test.mjs test/opencode-compatibility.test.mjs",
|
|
44
44
|
"smoke": "node test/smoke.mjs",
|
|
45
45
|
"pack:check": "node test/pack-allowlist.mjs"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@clack/prompts": "^1.8.1",
|
|
49
|
-
"@kisev/safe-fs": "1.0.0-dev.
|
|
49
|
+
"@kisev/safe-fs": "1.0.0-dev.103.g258eb838ddb7",
|
|
50
50
|
"commander": "15.0.0",
|
|
51
51
|
"zod": "4.1.8"
|
|
52
52
|
},
|