@kisev/agentomatic 11.0.0-dev.48.gf6e4f0e086aa → 11.0.0-dev.49.gf74dc1d4e538

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 CHANGED
@@ -61,21 +61,26 @@ outdated facts by key, and archives every pre-image.
61
61
 
62
62
  ## Project Install
63
63
 
64
- Install the package in the repository's npm project and preview the managed
65
- assets:
64
+ Run the installer from the repository root (add `--global` for the global
65
+ scope); the confirmed install also provisions the persistent npm dependency:
66
66
 
67
67
  ```shell
68
- npm install --save-exact @kisev/agentomatic
69
68
  npx --yes @kisev/agentomatic@latest install --dry-run
70
69
  ```
71
70
 
72
- Run the exact confirmation command printed by the preview. If the selected
71
+ The confirmed install pins the executing version into the nearest npm project;
72
+ global installs own `~/.config/opencode` and create its `package.json` when
73
+ needed. Offline setups can provision the dependency by hand first:
74
+ `npm install --save-exact @kisev/agentomatic`, then run
75
+ `npx agentomatic install --dry-run` from that project so the executing version
76
+ matches the installed package. Run the exact confirmation command
77
+ printed by the preview. If the selected
73
78
  assets need core integration, connect the package into the user-owned OpenCode
74
79
  configuration yourself or through the confirmed `config` command, which merges
75
80
  the `plugin` entry while preserving existing entries:
76
81
 
77
82
  ```shell
78
- npx --yes @kisev/agentomatic@latest config --global --dry-run
83
+ npx agentomatic config --global --dry-run
79
84
  ```
80
85
 
81
86
  ```json
package/README.ru.md CHANGED
@@ -64,21 +64,26 @@ Memomatic - персональная обучающая память по мот
64
64
 
65
65
  ## Установка в проект
66
66
 
67
- Установите пакет в npm-проект репозитория и предварительно просмотрите
68
- управляемые файлы:
67
+ Запустите установщик из корня репозитория (добавьте `--global` для глобальной
68
+ области); подтверждённая установка также пропишет постоянную npm-зависимость:
69
69
 
70
70
  ```shell
71
- npm install --save-exact @kisev/agentomatic
72
71
  npx --yes @kisev/agentomatic@latest install --dry-run
73
72
  ```
74
73
 
74
+ Подтверждённая установка закрепляет исполняемую версию в ближайшем npm-проекте;
75
+ глобальная установка владеет `~/.config/opencode` и при необходимости создаёт
76
+ там `package.json`. В офлайн-окружении зависимость можно поставить вручную:
77
+ `npm install --save-exact @kisev/agentomatic`, затем запустить
78
+ `npx agentomatic install --dry-run` из этого проекта, чтобы версия исполнения
79
+ совпадала с установленным пакетом.
75
80
  Выполните точную команду подтверждения, которую выведет предварительный просмотр.
76
81
  Если для выбранных файлов нужна основная интеграция, подключите пакет в
77
82
  пользовательскую конфигурацию OpenCode вручную или подтвержденной командой
78
83
  `config`, которая добавляет запись `plugin` с сохранением существующих записей:
79
84
 
80
85
  ```shell
81
- npx --yes @kisev/agentomatic@latest config --global --dry-run
86
+ npx agentomatic config --global --dry-run
82
87
  ```
83
88
 
84
89
  ```json
package/dist/cli.js CHANGED
@@ -67,6 +67,11 @@ function parseOptions(values) {
67
67
  throw new InstallerError("invalid_input", "--json may be supplied once");
68
68
  options.json = true;
69
69
  }
70
+ else if (value === "--no-dependency") {
71
+ if (options.noDependency)
72
+ throw new InstallerError("invalid_input", "--no-dependency may be supplied once");
73
+ options.noDependency = true;
74
+ }
70
75
  else if (["--commands", "--skill-commands", "--agents", "--plugins"].includes(value)) {
71
76
  const raw = values[++index];
72
77
  if (!raw)
@@ -258,6 +263,7 @@ function contextualHelp(arguments_) {
258
263
  ["--global", "Use global scope; project scope is the default."],
259
264
  ["--dry-run", "Preview operations and issue a one-time confirmation digest."],
260
265
  ["--confirm <digest>", "Apply the exact unexpired preview."],
266
+ ["--no-dependency", "Skip provisioning the persistent npm dependency for the core plugin."],
261
267
  ["--commands <list|none>", "Select installed-skill adapters."],
262
268
  ["--skill-commands <list|none>", "Select installed-skill adapters."],
263
269
  ["--agents <list|none>", "Select fixed agents."],
@@ -940,7 +946,7 @@ async function run(arguments_) {
940
946
  : undefined;
941
947
  requireConfirmationMode(options);
942
948
  if (options.dryRun) {
943
- const plan = await preview(action, options.scope, process.cwd(), undefined, selection);
949
+ const plan = await preview(action, options.scope, process.cwd(), undefined, selection, !options.noDependency);
944
950
  if (options.json)
945
951
  process.stdout.write(`${JSON.stringify({ status: "ok", applied: false, requires_restart: plan.requires_restart, plan }, null, 2)}\n`);
946
952
  else
@@ -950,13 +956,14 @@ async function run(arguments_) {
950
956
  action,
951
957
  ...scopeArguments(options.scope),
952
958
  ...(action === "install" ? selectionArguments(plan.selection) : []),
959
+ ...(options.noDependency ? ["--no-dependency"] : []),
953
960
  "--confirm",
954
961
  plan.digest,
955
962
  ]),
956
963
  }));
957
964
  }
958
965
  else {
959
- const plan = await apply(action, options.scope, options.confirm, process.cwd(), undefined, {}, selection);
966
+ const plan = await apply(action, options.scope, options.confirm, process.cwd(), undefined, {}, selection, !options.noDependency);
960
967
  if (options.json)
961
968
  process.stdout.write(`${JSON.stringify({ status: "ok", applied: true, requires_restart: plan.requires_restart, plan }, null, 2)}\n`);
962
969
  else
@@ -1,6 +1,7 @@
1
1
  import { LifecycleError, type FileMutation, type Scope, type SupersededPlan, type TransactionOptions } from "./lifecycle.js";
2
2
  import { CATALOG } from "./catalog.js";
3
3
  import { type FixedAgentRole } from "./agent-profiles.js";
4
+ import { type DependencyPlan, type DependencyRunner } from "./self-install.js";
4
5
  export type { Scope } from "./lifecycle.js";
5
6
  export type Action = "install" | "uninstall";
6
7
  export type Operation = "create" | "update" | "remove" | "unchanged" | "missing" | "conflict" | "archive-pending";
@@ -25,6 +26,9 @@ export type Plan = {
25
26
  package_version: string;
26
27
  selection: InstallerSelection;
27
28
  operations: PlanItem[];
29
+ dependency?: DependencyPlan & {
30
+ applied?: "changed" | "unchanged" | "skipped";
31
+ };
28
32
  plan_digest: string;
29
33
  confirmation_digest?: string;
30
34
  superseded_plan?: SupersededPlan;
@@ -51,6 +55,8 @@ export declare function normalizeSelection(value?: Partial<InstallerSelection>):
51
55
  export declare class InstallerError extends LifecycleError {
52
56
  }
53
57
  export declare function archiveMutations(candidates: readonly ArchiveCandidate[], scope: Scope, cwd: string, home: string, sourceVersion: string): Promise<FileMutation[]>;
54
- export declare function preview(action: Action, scope: Scope, cwd?: string, home?: string, selection?: Partial<InstallerSelection>): Promise<Plan>;
55
- export declare function apply(action: Action, scope: Scope, confirmationDigest: string, cwd?: string, home?: string, options?: TransactionOptions, selection?: Partial<InstallerSelection>): Promise<Plan>;
58
+ export declare function preview(action: Action, scope: Scope, cwd?: string, home?: string, selection?: Partial<InstallerSelection>, provisionDependency?: boolean): Promise<Plan>;
59
+ export declare function apply(action: Action, scope: Scope, confirmationDigest: string, cwd?: string, home?: string, options?: TransactionOptions & {
60
+ dependencyRunner?: DependencyRunner;
61
+ }, selection?: Partial<InstallerSelection>, provisionDependency?: boolean): Promise<Plan>;
56
62
  export declare function result(plan: Plan, applied: boolean): string;
package/dist/installer.js CHANGED
@@ -8,6 +8,7 @@ import { applyTransaction, archiveRoot, consumeReceipt, deploymentRoot, destinat
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";
11
+ import { ensureDependency, planDependency, } from "./self-install.js";
11
12
  const PACKAGE_NAME = "@kisev/agentomatic";
12
13
  const MANIFEST_NAME = ".agentomatic-manifest.json";
13
14
  const LEGACY_MANIFEST_NAME = ".skills-opencode-manifest.json";
@@ -324,7 +325,7 @@ function asLegacy(manifest) {
324
325
  ? manifest
325
326
  : undefined;
326
327
  }
327
- async function build(action, scope, cwd = process.cwd(), home = homedir(), requestedSelection) {
328
+ async function build(action, scope, cwd = process.cwd(), home = homedir(), requestedSelection, provisionDependency = true) {
328
329
  const root = deploymentRoot(scope, cwd, home);
329
330
  const owned = await currentManifest(root);
330
331
  const selection = normalizeSelection(requestedSelection ??
@@ -346,6 +347,9 @@ async function build(action, scope, cwd = process.cwd(), home = homedir(), reque
346
347
  }
347
348
  : undefined;
348
349
  const profiles = await buildAgentProfilePlan({ action }, scope, cwd, home, legacyRecord, selection.agents);
350
+ const dependency = action === "install" && provisionDependency && selection.core_activation
351
+ ? planDependency(scope, cwd, home)
352
+ : undefined;
349
353
  const operations = profiles.plan.operations.map((item) => ({
350
354
  path: item.path,
351
355
  operation: item.operation,
@@ -602,6 +606,7 @@ async function build(action, scope, cwd = process.cwd(), home = homedir(), reque
602
606
  package_version: packageVersion(),
603
607
  selection,
604
608
  operations: sorted,
609
+ ...(dependency ? { dependency } : {}),
605
610
  requires_restart: (action === "install" && owned.manifest?.package_version !== packageVersion()) ||
606
611
  profiles.plan.requires_restart ||
607
612
  mutations.some((item) => item.path.startsWith("agents/") ||
@@ -616,7 +621,7 @@ async function build(action, scope, cwd = process.cwd(), home = homedir(), reque
616
621
  profiles,
617
622
  };
618
623
  }
619
- export async function preview(action, scope, cwd = process.cwd(), home = homedir(), selection) {
624
+ export async function preview(action, scope, cwd = process.cwd(), home = homedir(), selection, provisionDependency = true) {
620
625
  // Infrastructure normalization like stale-lock reclamation: idempotent,
621
626
  // preserves every legacy byte, and never touches user configuration.
622
627
  await migrateLegacyNamespaces(home);
@@ -626,7 +631,7 @@ export async function preview(action, scope, cwd = process.cwd(), home = homedir
626
631
  return await withLifecycleLock(stateRoot, async () => {
627
632
  if (await recoverTransaction(root, stateRoot, [archiveRoot(scope, cwd, home)]))
628
633
  throw new InstallerError("recovered_transaction", "Recovered an interrupted transaction; request a fresh plan");
629
- const built = await build(action, scope, cwd, home, selection);
634
+ const built = await build(action, scope, cwd, home, selection, provisionDependency);
630
635
  const receipt = await saveReceipt(stateRoot, `installer:${action}`, scope, root, { plan_digest: built.plan.digest }, Date.now(), built.plan.digest);
631
636
  return {
632
637
  ...built.plan,
@@ -646,7 +651,8 @@ export async function preview(action, scope, cwd = process.cwd(), home = homedir
646
651
  throw error;
647
652
  }
648
653
  }
649
- export async function apply(action, scope, confirmationDigest, cwd = process.cwd(), home = homedir(), options = {}, selection) {
654
+ export async function apply(action, scope, confirmationDigest, cwd = process.cwd(), home = homedir(), options = {}, selection, provisionDependency = true) {
655
+ const { dependencyRunner, ...transactionOptions } = options;
650
656
  const stateRoot = lifecycleRoot(scope, cwd, home);
651
657
  const root = deploymentRoot(scope, cwd, home);
652
658
  await migrateLegacyNamespaces(home);
@@ -661,7 +667,7 @@ export async function apply(action, scope, confirmationDigest, cwd = process.cwd
661
667
  scope,
662
668
  root,
663
669
  }));
664
- const built = await build(action, scope, cwd, home, selection);
670
+ const built = await build(action, scope, cwd, home, selection, provisionDependency);
665
671
  const savedPlanDigest = receipt.plan_digest ??
666
672
  receipt.digest;
667
673
  if (built.plan.digest !== savedPlanDigest)
@@ -676,7 +682,7 @@ export async function apply(action, scope, confirmationDigest, cwd = process.cwd
676
682
  built.plan.operations.some((item) => item.operation === "conflict"))
677
683
  throw new InstallerError("conflict", "Installer plan contains managed drift");
678
684
  await applyTransaction(root, stateRoot, built.mutations, {
679
- ...options,
685
+ ...transactionOptions,
680
686
  validateFinal: async () => {
681
687
  await options.validateFinal?.();
682
688
  await validateBuiltAgentProfilePlan(built.profiles);
@@ -694,7 +700,14 @@ export async function apply(action, scope, confirmationDigest, cwd = process.cwd
694
700
  }
695
701
  },
696
702
  });
697
- return { ...built.plan, digest: confirmationDigest };
703
+ const dependencyOutcome = action === "install" && built.plan.dependency
704
+ ? await ensureDependency(built.plan.dependency, dependencyRunner)
705
+ : undefined;
706
+ return {
707
+ ...built.plan,
708
+ digest: confirmationDigest,
709
+ ...(dependencyOutcome ? { dependency: dependencyOutcome } : {}),
710
+ };
698
711
  });
699
712
  }
700
713
  catch (error) {
@@ -0,0 +1,24 @@
1
+ import { LifecycleError } from "@kisev/safe-fs";
2
+ export declare const SELF_PACKAGE_NAME = "@kisev/agentomatic";
3
+ export type DependencyRunner = (command: string, args: string[], options: {
4
+ cwd: string;
5
+ timeout: number;
6
+ }) => Promise<{
7
+ stdout: string;
8
+ stderr: string;
9
+ }>;
10
+ export type DependencyPlan = {
11
+ dir: string | null;
12
+ name: string;
13
+ version: string;
14
+ status: "install" | "update" | "satisfied" | "manual";
15
+ package_json: "present" | "create" | "missing";
16
+ reason?: string;
17
+ };
18
+ export declare class SelfInstallError extends LifecycleError {
19
+ }
20
+ export declare function owningProjectDir(scope: "project" | "global", cwd?: string, home?: string): string | null;
21
+ export declare function planDependency(scope: "project" | "global", cwd?: string, home?: string, version?: string): DependencyPlan;
22
+ export declare function ensureDependency(plan: DependencyPlan, runner?: DependencyRunner): Promise<DependencyPlan & {
23
+ applied: "changed" | "unchanged" | "skipped";
24
+ }>;
@@ -0,0 +1,100 @@
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { LifecycleError, writeAtomic } from "@kisev/safe-fs";
7
+ import { requirePackageVersion } from "./package-metadata.js";
8
+ const execFileAsync = promisify(execFile);
9
+ export const SELF_PACKAGE_NAME = "@kisev/agentomatic";
10
+ const NPM_INSTALL_TIMEOUT_MS = 180_000;
11
+ export class SelfInstallError extends LifecycleError {
12
+ }
13
+ export function owningProjectDir(scope, cwd = process.cwd(), home = homedir()) {
14
+ if (scope === "global")
15
+ return join(home, ".config", "opencode");
16
+ let dir = resolve(cwd);
17
+ for (;;) {
18
+ if (existsSync(join(dir, "package.json")))
19
+ return dir;
20
+ const parent = dirname(dir);
21
+ if (parent === dir)
22
+ return null;
23
+ dir = parent;
24
+ }
25
+ }
26
+ function readDependencyVersion(dir) {
27
+ try {
28
+ const value = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
29
+ if (!value || typeof value !== "object" || Array.isArray(value))
30
+ return null;
31
+ const dependency = value.dependencies;
32
+ if (!dependency || typeof dependency !== "object" || Array.isArray(dependency))
33
+ return null;
34
+ const pinned = dependency[SELF_PACKAGE_NAME];
35
+ return typeof pinned === "string" && pinned.length > 0 ? pinned : null;
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ export function planDependency(scope, cwd = process.cwd(), home = homedir(), version = requirePackageVersion()) {
42
+ if (scope === "global") {
43
+ const dir = join(home, ".config", "opencode");
44
+ const hasPackageJson = existsSync(join(dir, "package.json"));
45
+ if (!hasPackageJson)
46
+ return { dir, name: SELF_PACKAGE_NAME, version, status: "install", package_json: "create" };
47
+ const pinned = readDependencyVersion(dir);
48
+ if (pinned === version)
49
+ return {
50
+ dir,
51
+ name: SELF_PACKAGE_NAME,
52
+ version,
53
+ status: "satisfied",
54
+ package_json: "present",
55
+ };
56
+ return {
57
+ dir,
58
+ name: SELF_PACKAGE_NAME,
59
+ version,
60
+ status: pinned ? "update" : "install",
61
+ package_json: "present",
62
+ };
63
+ }
64
+ const dir = owningProjectDir("project", cwd, home);
65
+ if (!dir)
66
+ return {
67
+ dir: null,
68
+ name: SELF_PACKAGE_NAME,
69
+ version,
70
+ status: "manual",
71
+ package_json: "missing",
72
+ reason: "no package.json found upward from the working directory; create an npm project or use --global",
73
+ };
74
+ const pinned = readDependencyVersion(dir);
75
+ if (pinned === version)
76
+ return { dir, name: SELF_PACKAGE_NAME, version, status: "satisfied", package_json: "present" };
77
+ return {
78
+ dir,
79
+ name: SELF_PACKAGE_NAME,
80
+ version,
81
+ status: pinned ? "update" : "install",
82
+ package_json: "present",
83
+ };
84
+ }
85
+ export async function ensureDependency(plan, runner = execFileAsync) {
86
+ if (plan.status === "manual" || plan.status === "satisfied")
87
+ return { ...plan, applied: plan.status === "satisfied" ? "unchanged" : "skipped" };
88
+ const dir = plan.dir;
89
+ if (plan.package_json === "create" && !existsSync(join(dir, "package.json"))) {
90
+ await writeAtomic(join(dir, "package.json"), Buffer.from('{"name":"opencode-agentomatic","private":true}\n'), 0o644);
91
+ }
92
+ try {
93
+ await runner("npm", ["install", "--save-exact", `${plan.name}@${plan.version}`, "--no-audit", "--no-fund"], { cwd: dir, timeout: NPM_INSTALL_TIMEOUT_MS });
94
+ }
95
+ catch (error) {
96
+ const detail = error instanceof Error ? error.message.split("\n").slice(-6).join("\n") : "";
97
+ throw new SelfInstallError("npm_dependency_failed", `npm install for ${plan.name}@${plan.version} in ${dir} failed: ${detail}`);
98
+ }
99
+ return { ...plan, applied: "changed" };
100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kisev/agentomatic",
3
- "version": "11.0.0-dev.48.gf6e4f0e086aa",
3
+ "version": "11.0.0-dev.49.gf74dc1d4e538",
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": {
@@ -44,13 +44,13 @@
44
44
  },
45
45
  "scripts": {
46
46
  "build": "node scripts/clean-dist.mjs && tsc --project tsconfig.json && node dist/generate-assets.js",
47
- "test": "node --test test/agent-profiles.test.mjs test/config-setup.test.mjs test/legacy-migration.test.mjs test/doctor.test.mjs test/memomatic-plugin.test.mjs test/package.test.mjs test/rtk.test.mjs test/stage19.test.mjs",
47
+ "test": "node --test test/agent-profiles.test.mjs test/config-setup.test.mjs test/legacy-migration.test.mjs test/doctor.test.mjs test/memomatic-plugin.test.mjs test/package.test.mjs test/rtk.test.mjs test/self-install.test.mjs test/stage19.test.mjs",
48
48
  "smoke": "node test/smoke.mjs",
49
49
  "pack:check": "node test/pack-allowlist.mjs"
50
50
  },
51
51
  "dependencies": {
52
- "@kisev/memomatic": "1.0.0-dev.48.gf6e4f0e086aa",
53
- "@kisev/safe-fs": "1.0.0-dev.48.gf6e4f0e086aa"
52
+ "@kisev/memomatic": "1.0.0-dev.49.gf74dc1d4e538",
53
+ "@kisev/safe-fs": "1.0.0-dev.49.gf74dc1d4e538"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@opencode-ai/plugin": "1.18.29",