@kisev/agentomatic 11.0.0-dev.57.gb877c35a2446 → 11.0.0-dev.58.gc136a777fcbb

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/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { apply, defaultSelection, InstallerError, normalizeSelection, preview, S
8
8
  import { LifecycleError } from "./lifecycle.js";
9
9
  import { skillsInstallerSpec } from "./package-metadata.js";
10
10
  import { applyReconcile, previewReconcile } from "./reconcile.js";
11
- import { promptText, selectOption, selectOptions } from "./terminal-wizard.js";
11
+ import { confirmQuestion, promptText, selectOption, selectOptions } from "./terminal-wizard.js";
12
12
  function parseOptions(values) {
13
13
  const options = {
14
14
  scope: "project",
@@ -41,6 +41,16 @@ function parseOptions(values) {
41
41
  throw new InstallerError("invalid_input", "--yes may be supplied once");
42
42
  options.yes = true;
43
43
  }
44
+ else if (value === "--core") {
45
+ if (options.core === true || options.core === false)
46
+ throw new InstallerError("invalid_input", "Use only one core integration option");
47
+ options.core = true;
48
+ }
49
+ else if (value === "--no-core") {
50
+ if (options.core === true || options.core === false)
51
+ throw new InstallerError("invalid_input", "Use only one core integration option");
52
+ options.core = false;
53
+ }
44
54
  else if (value === "--provider") {
45
55
  options.provider = values[++index];
46
56
  if (!options.provider)
@@ -162,6 +172,7 @@ function rootHelp() {
162
172
  ["--skill-commands <list|none>", "Select adapters for installed portable skills."],
163
173
  ["--agents <list|none>", "Select fixed agents."],
164
174
  ["--plugins <list|none>", "Select optional plugin wrappers; rtk is the default."],
175
+ ["--core / --no-core", "Toggle plugin entry wiring and npm dependency provisioning."],
165
176
  ["--targets <list|none>", "Select config targets for the config command."],
166
177
  ["--fragments <list|none>", "Select config fragments for the config command."],
167
178
  ]),
@@ -268,13 +279,16 @@ function contextualHelp(arguments_) {
268
279
  ["--skill-commands <list|none>", "Select installed-skill adapters."],
269
280
  ["--agents <list|none>", "Select fixed agents."],
270
281
  ["--plugins <list|none>", "Select optional plugin wrappers; rtk is preselected."],
282
+ ["--core", "Wire the plugin entry and pin the npm dependency in one run."],
283
+ ["--no-core", "Skip plugin entry wiring and npm dependency provisioning."],
271
284
  ["--json", "Emit stable machine-readable output."],
272
285
  ["--help", "Show this command help and exit."],
273
286
  ], [
274
- "Without selection flags, a TTY opens command, agent, and plugin selectors.",
287
+ "Without selection flags, a TTY opens command, agent, plugin, and core selectors.",
275
288
  "Use Up/Down to move, Space to toggle, A/N for all/none, and Enter to confirm.",
276
289
  "Outside a TTY, provide command selection, --agents, and --plugins.",
277
- "Writes occur only after confirmation; install never edits opencode.json.",
290
+ "Confirmed core integration wires the core-plugin fragment in the same run;",
291
+ "asset writes never touch opencode.json directly.",
278
292
  "Portable skills are installed separately; restart OpenCode after asset changes.",
279
293
  ], [
280
294
  shellCommand(["install", "--global", "--dry-run"]),
@@ -566,10 +580,14 @@ async function interactiveInstallerSelection() {
566
580
  const commands = await group("Skill command adapters", SKILL_COMMANDS, SKILL_COMMANDS);
567
581
  const agents = await group("Fixed agents", defaults.agents, defaults.agents);
568
582
  const plugins = await group("Selectable plugins", SELECTABLE_PLUGINS, defaults.plugins);
583
+ const core = await selectOption("Core integration: wire the plugin entry and pin the npm dependency?", ["Yes", "No"], process.stdin, process.stderr);
584
+ if (core === null)
585
+ throw new InstallerError("cancelled", "Wizard cancelled");
569
586
  return normalizeSelection({
570
587
  commands,
571
588
  agents: agents,
572
589
  plugins: plugins,
590
+ core_activation: core === 0,
573
591
  });
574
592
  }
575
593
  function installerSelection(options) {
@@ -577,6 +595,7 @@ function installerSelection(options) {
577
595
  commands: options.commands,
578
596
  agents: options.agents,
579
597
  plugins: options.plugins,
598
+ ...(options.core === undefined ? {} : { core_activation: options.core }),
580
599
  });
581
600
  }
582
601
  function configSelectionFromOptions(options) {
@@ -641,8 +660,8 @@ function requireApplyMode(options) {
641
660
  throw new InstallerError("invalid_input", "Applying outside a terminal requires --yes; use --dry-run to preview");
642
661
  }
643
662
  async function confirmSummary() {
644
- const choice = await selectOption("Apply these changes?", ["Yes", "No"]);
645
- if (choice !== 0)
663
+ const choice = await confirmQuestion("Apply these changes?");
664
+ if (choice !== true)
646
665
  throw new InstallerError("cancelled", "Cancelled before apply");
647
666
  }
648
667
  function applyHint(command) {
@@ -961,12 +980,36 @@ async function run(arguments_) {
961
980
  ? installerSelection(options)
962
981
  : await interactiveInstallerSelection()
963
982
  : undefined;
983
+ const coreSelection = action === "install" && selection?.core_activation
984
+ ? normalizeConfigSelection(options.scope, {
985
+ targets: ["opencode"],
986
+ fragments: ["core-plugin"],
987
+ })
988
+ : undefined;
989
+ const applyCoreIntegration = async () => {
990
+ if (!coreSelection)
991
+ return null;
992
+ try {
993
+ return await applyConfigSetup(coreSelection, options.scope);
994
+ }
995
+ catch (error) {
996
+ const known = error instanceof LifecycleError
997
+ ? error
998
+ : new InstallerError("internal_error", error instanceof Error ? error.message : String(error));
999
+ process.stderr.write(`Assets applied, but core integration failed [${known.code}]: ${terminalSafe(known.message)}\nRetry with: ${shellCommand(["config", ...scopeArguments(options.scope), "--targets", "opencode", "--fragments", "core-plugin"])}\n`);
1000
+ process.exitCode = 2;
1001
+ return null;
1002
+ }
1003
+ };
964
1004
  requireApplyMode(options);
965
1005
  if (options.dryRun) {
966
1006
  const plan = await preview(action, options.scope, process.cwd(), undefined, selection, !options.noDependency);
1007
+ const corePlan = coreSelection
1008
+ ? await previewConfigSetup(coreSelection, options.scope)
1009
+ : undefined;
967
1010
  if (options.json)
968
- process.stdout.write(`${JSON.stringify({ status: "ok", applied: false, requires_restart: plan.requires_restart, plan }, null, 2)}\n`);
969
- else
1011
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: false, requires_restart: plan.requires_restart, plan, ...(corePlan ? { core_integration: corePlan } : {}) }, null, 2)}\n`);
1012
+ else {
970
1013
  process.stdout.write(renderPlan(plan, {
971
1014
  applied: false,
972
1015
  applyHint: applyHint([
@@ -976,6 +1019,9 @@ async function run(arguments_) {
976
1019
  ...(options.noDependency ? ["--no-dependency"] : []),
977
1020
  ]),
978
1021
  }));
1022
+ if (corePlan)
1023
+ process.stdout.write(renderConfigSetup(corePlan, { applied: false }));
1024
+ }
979
1025
  }
980
1026
  else {
981
1027
  if (!options.yes) {
@@ -984,17 +1030,20 @@ async function run(arguments_) {
984
1030
  await confirmSummary();
985
1031
  }
986
1032
  const plan = await apply(action, options.scope, process.cwd(), undefined, {}, selection, !options.noDependency);
1033
+ const corePlan = await applyCoreIntegration();
987
1034
  if (options.json)
988
- process.stdout.write(`${JSON.stringify({ status: "ok", applied: true, requires_restart: plan.requires_restart, plan }, null, 2)}\n`);
1035
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: true, requires_restart: plan.requires_restart, plan, ...(corePlan ? { core_integration: corePlan } : {}) }, null, 2)}\n`);
989
1036
  else
990
1037
  process.stdout.write(renderPlan(plan, {
991
1038
  applied: true,
992
- ...(action === "install"
1039
+ ...(action === "install" && !corePlan
993
1040
  ? {
994
1041
  hint: `Connect the package into user configs: ${shellCommand(["config", ...scopeArguments(options.scope), "--dry-run"])}`,
995
1042
  }
996
1043
  : {}),
997
1044
  }));
1045
+ if (corePlan && !options.json)
1046
+ process.stdout.write(renderConfigSetup(corePlan, { applied: true }));
998
1047
  }
999
1048
  return;
1000
1049
  }
@@ -1,7 +1,9 @@
1
1
  import { readFileSync } from "node:fs";
2
+ import { lstat } from "node:fs/promises";
2
3
  import { homedir } from "node:os";
3
4
  import { dirname, join, resolve } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
6
+ import { archiveMutations } from "./installer.js";
5
7
  import { requirePackageVersion } from "./package-metadata.js";
6
8
  import { ensureDependency, planDependency } from "./self-install.js";
7
9
  import { applyTransaction, deploymentRoot, LifecycleError, lifecycleRoot, readRegular, recoverTransaction, sha256, withLifecycleLock, } from "./lifecycle.js";
@@ -414,7 +416,27 @@ export async function applyConfigSetup(selection, scope, cwd = process.cwd(), ho
414
416
  }
415
417
  throw new ConfigSetupError("invalid_state", "Config setup plan has no applicable changes");
416
418
  }
417
- await applyTransaction(root, stateRoot, built.mutations, {
419
+ const backups = [];
420
+ for (const mutation of built.mutations) {
421
+ if (mutation.operation !== "write")
422
+ continue;
423
+ const target = join(mutation.root ?? root, mutation.path);
424
+ const current = await readRegular(target).catch(() => undefined);
425
+ if (!current || current.equals(mutation.content))
426
+ continue;
427
+ backups.push({
428
+ path: target,
429
+ record: {
430
+ sha256: sha256(current),
431
+ mode: (await lstat(target)).mode & 0o777,
432
+ },
433
+ content: current,
434
+ reason: "pre-apply config backup",
435
+ kind: "config-backup",
436
+ });
437
+ }
438
+ const backupMutations = await archiveMutations(backups, scope, cwd, home, requirePackageVersion(), new Date().toISOString());
439
+ await applyTransaction(root, stateRoot, [...built.mutations, ...backupMutations], {
418
440
  validateFinal: async () => {
419
441
  for (const mutation of built.mutations) {
420
442
  if (mutation.operation !== "write")
package/dist/doctor.js CHANGED
@@ -505,6 +505,31 @@ export async function collectDoctorFacts(scope, cwd = process.cwd(), home = home
505
505
  status: dependency.status,
506
506
  }, ["Run config with the core-plugin fragment to provision the persistent npm dependency."]));
507
507
  }
508
+ const archiveIndexPath = join(archiveRoot(scope, project, homeRoot), "index.json");
509
+ const archiveIndexRaw = await regular(archiveIndexPath);
510
+ let backupEntries = [];
511
+ if (archiveIndexRaw) {
512
+ try {
513
+ const parsed = JSON.parse(archiveIndexRaw.raw.toString("utf8"));
514
+ if (Array.isArray(parsed.entries))
515
+ backupEntries = parsed.entries.filter((item) => Boolean(item &&
516
+ typeof item === "object" &&
517
+ item.kind === "config-backup"));
518
+ }
519
+ catch {
520
+ partial.push("config.backups");
521
+ }
522
+ }
523
+ const latestBackup = backupEntries
524
+ .map((item) => String(item.timestamp ?? ""))
525
+ .sort()
526
+ .at(-1);
527
+ checks.push(check("config.backups", archiveIndexRaw ? "pass" : "incomplete", "User configuration snapshots are tracked in the archive", {
528
+ archive_index: archiveIndexPath,
529
+ config_backups: backupEntries.length,
530
+ latest_backup_at: latestBackup ?? "never",
531
+ latest_paths: backupEntries.slice(-3).map((item) => String(item.original_path ?? "")),
532
+ }, ["Configuration edits archive the previous content before every apply."]));
508
533
  const rtkWrapper = await regular(join(deployment, "plugins", "rtk.js"));
509
534
  const rtkDeployed = rtkWrapper.status === "present";
510
535
  let rtkStats = undefined;
@@ -31,17 +31,15 @@ export type Plan = {
31
31
  };
32
32
  requires_restart: boolean;
33
33
  };
34
- type ManifestFile = {
35
- sha256: string;
36
- mode: number;
37
- kind: "command" | "agent" | "plugin" | "state";
38
- };
39
34
  export type ArchiveCandidate = {
40
35
  path: string;
41
- record: ManifestFile;
36
+ record: {
37
+ sha256: string;
38
+ mode: number;
39
+ };
42
40
  content: Buffer;
43
41
  reason: string;
44
- kind: "command" | "agent" | "plugin" | "state";
42
+ kind: "command" | "agent" | "plugin" | "state" | "config-backup";
45
43
  };
46
44
  export declare const SELECTABLE_PLUGINS: SelectablePlugin[];
47
45
  export declare const SKILL_COMMANDS: string[];
@@ -49,7 +47,7 @@ export declare function defaultSelection(): InstallerSelection;
49
47
  export declare function normalizeSelection(value?: Partial<InstallerSelection>): InstallerSelection;
50
48
  export declare class InstallerError extends LifecycleError {
51
49
  }
52
- export declare function archiveMutations(candidates: readonly ArchiveCandidate[], scope: Scope, cwd: string, home: string, sourceVersion: string): Promise<FileMutation[]>;
50
+ export declare function archiveMutations(candidates: readonly ArchiveCandidate[], scope: Scope, cwd: string, home: string, sourceVersion: string, timestamp?: string): Promise<FileMutation[]>;
53
51
  export declare function preview(action: Action, scope: Scope, cwd?: string, home?: string, selection?: Partial<InstallerSelection>, provisionDependency?: boolean): Promise<Plan>;
54
52
  export declare function apply(action: Action, scope: Scope, cwd?: string, home?: string, options?: TransactionOptions & {
55
53
  dependencyRunner?: DependencyRunner;
package/dist/installer.js CHANGED
@@ -184,7 +184,7 @@ async function currentManifest(root) {
184
184
  }
185
185
  return {};
186
186
  }
187
- export async function archiveMutations(candidates, scope, cwd, home, sourceVersion) {
187
+ export async function archiveMutations(candidates, scope, cwd, home, sourceVersion, timestamp = new Date(0).toISOString()) {
188
188
  if (!candidates.length)
189
189
  return [];
190
190
  const root = archiveRoot(scope, cwd, home);
@@ -226,7 +226,7 @@ export async function archiveMutations(candidates, scope, cwd, home, sourceVersi
226
226
  const known = new Set(entries.map((entry) => `${String(entry.original_path)}:${String(entry.digest)}`));
227
227
  const mutations = [];
228
228
  const queuedObjects = new Set();
229
- const now = new Date(0).toISOString();
229
+ const now = timestamp;
230
230
  for (const candidate of candidates) {
231
231
  const digestValue = sha256(candidate.content);
232
232
  const key = `${candidate.path}:${digestValue}`;
package/dist/reconcile.js CHANGED
@@ -223,7 +223,7 @@ async function build(scope, cwd = process.cwd(), home = homedir()) {
223
223
  : "state";
224
224
  archiveCandidates.push({
225
225
  path: relativePath(root, target),
226
- record: { sha256: record.sha256, mode: 0o644, kind },
226
+ record: { sha256: record.sha256, mode: 0o644 },
227
227
  content: value.content,
228
228
  reason: "retired inventory asset",
229
229
  kind,
@@ -1,3 +1,5 @@
1
- export declare function selectOption(label: string, options: readonly string[], stdin?: NodeJS.ReadStream, stderr?: NodeJS.WriteStream, initial?: number): Promise<number | null>;
2
- export declare function selectOptions(label: string, options: readonly string[], initialSelected?: readonly string[], stdin?: NodeJS.ReadStream, stderr?: NodeJS.WriteStream): Promise<string[] | null>;
3
- export declare function promptText(label: string, stdin?: NodeJS.ReadStream, stderr?: NodeJS.WriteStream): Promise<string | null>;
1
+ import type { Readable, Writable } from "node:stream";
2
+ export declare function selectOption(label: string, options: readonly string[], stdin?: Readable, stderr?: Writable, initial?: number): Promise<number | null>;
3
+ export declare function selectOptions(label: string, options: readonly string[], initialSelected?: readonly string[], stdin?: Readable, stderr?: Writable): Promise<string[] | null>;
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>;
@@ -1,36 +1,11 @@
1
+ import * as clack from "@clack/prompts";
1
2
  import { InstallerError } from "./installer.js";
2
- function parseKey(buffer) {
3
- const seq = buffer.toString();
4
- if (seq === "\r" || seq === "\n")
5
- return { name: "return", sequence: seq };
6
- if (seq === " ")
7
- return { name: "space", sequence: seq };
8
- if (seq === "a" || seq === "A")
9
- return { name: "all", sequence: seq };
10
- if (seq === "n" || seq === "N")
11
- return { name: "none", sequence: seq };
12
- if (seq === "\x03")
13
- return { name: "ctrl+c", sequence: seq };
14
- if (seq === "\x04")
15
- return { name: "ctrl+d", sequence: seq };
16
- if (seq === "\x1b" || seq === "\x1b\x1b" || seq === "\x1b\x1b\x1b")
17
- return { name: "escape", sequence: seq };
18
- if (seq === "\x1b[A" || seq === "\x1bOA" || seq === "\x1b[OA")
19
- return { name: "up", sequence: seq };
20
- if (seq === "\x1b[B" || seq === "\x1bOB" || seq === "\x1b[OB")
21
- return { name: "down", sequence: seq };
22
- if (seq === "\x1b[C" || seq === "\x1bOC" || seq === "\x1b[OC")
23
- return { name: "right", sequence: seq };
24
- if (seq === "\x1b[D" || seq === "\x1bOD" || seq === "\x1b[OD")
25
- return { name: "left", sequence: seq };
26
- return { name: "unknown", sequence: seq };
3
+ function interactiveStreams(stdin, stderr) {
4
+ if (!isTTY(stdin) || !isTTY(stderr))
5
+ throw new InstallerError("terminal_required", "This operation requires an interactive terminal");
27
6
  }
28
- function nextSequence(remaining) {
29
- if (remaining.startsWith("\x1b[O"))
30
- return remaining.slice(0, 4);
31
- if (remaining.startsWith("\x1b[") || remaining.startsWith("\x1bO"))
32
- return remaining.slice(0, 3);
33
- return remaining[0];
7
+ function isTTY(stream) {
8
+ return Boolean(stream.isTTY);
34
9
  }
35
10
  function validateOptions(options) {
36
11
  if (options.length === 0)
@@ -38,200 +13,52 @@ function validateOptions(options) {
38
13
  if (new Set(options).size !== options.length)
39
14
  throw new InstallerError("invalid_input", "Interactive selector options must be unique");
40
15
  }
41
- function clearLines(stderr, count) {
42
- for (let i = 0; i < count; i++) {
43
- stderr.write("\x1b[A\x1b[K");
44
- }
45
- }
46
16
  export async function selectOption(label, options, stdin = process.stdin, stderr = process.stderr, initial = 0) {
47
- if (!stdin.isTTY || !stderr.isTTY) {
48
- throw new InstallerError("terminal_required", "This operation requires an interactive terminal");
49
- }
17
+ interactiveStreams(stdin, stderr);
50
18
  validateOptions(options);
51
- let selected = Math.max(0, Math.min(initial, options.length - 1));
52
- const lineCount = options.length + 2;
53
- function render() {
54
- stderr.write(`? ${label}\n`);
55
- stderr.write(" Up/Down move | Enter select | Esc cancel\n");
56
- for (let i = 0; i < options.length; i++) {
57
- const cursor = i === selected ? "> " : " ";
58
- stderr.write(`${cursor}${options[i]}\n`);
59
- }
60
- }
61
- render();
62
- return new Promise((resolve) => {
63
- stdin.setRawMode(true);
64
- stdin.resume();
65
- function cleanup() {
66
- stdin.setRawMode(false);
67
- stdin.pause();
68
- stdin.removeListener("data", onData);
69
- }
70
- function onData(data) {
71
- let remaining = data.toString();
72
- while (remaining) {
73
- const sequence = nextSequence(remaining);
74
- remaining = remaining.slice(sequence.length);
75
- const key = parseKey(Buffer.from(sequence));
76
- if (key.name === "ctrl+c" || key.name === "ctrl+d" || key.name === "escape") {
77
- cleanup();
78
- clearLines(stderr, lineCount);
79
- resolve(null);
80
- return;
81
- }
82
- if (key.name === "return") {
83
- cleanup();
84
- clearLines(stderr, lineCount);
85
- resolve(selected);
86
- return;
87
- }
88
- if (key.name === "up") {
89
- clearLines(stderr, lineCount);
90
- selected = (selected - 1 + options.length) % options.length;
91
- render();
92
- }
93
- else if (key.name === "down") {
94
- clearLines(stderr, lineCount);
95
- selected = (selected + 1) % options.length;
96
- render();
97
- }
98
- }
99
- }
100
- stdin.on("data", onData);
19
+ const value = await clack.select({
20
+ message: label,
21
+ options: options.map((option, index) => ({ value: index, label: option })),
22
+ initialValue: Math.max(0, Math.min(initial, options.length - 1)),
23
+ input: stdin,
24
+ output: stderr,
101
25
  });
26
+ if (clack.isCancel(value))
27
+ return null;
28
+ return value;
102
29
  }
103
30
  export async function selectOptions(label, options, initialSelected = [], stdin = process.stdin, stderr = process.stderr) {
104
- if (!stdin.isTTY || !stderr.isTTY) {
105
- throw new InstallerError("terminal_required", "This operation requires an interactive terminal");
106
- }
31
+ interactiveStreams(stdin, stderr);
107
32
  validateOptions(options);
108
33
  const allowed = new Set(options);
109
34
  for (const value of initialSelected) {
110
35
  if (!allowed.has(value))
111
36
  throw new InstallerError("invalid_input", `Unknown initial selector option: ${value}`);
112
37
  }
113
- const selected = new Set(initialSelected);
114
- let active = 0;
115
- const lineCount = options.length + 3;
116
- function render() {
117
- stderr.write(`? ${label}\n`);
118
- stderr.write(" Up/Down move | Space toggle | A all | N none | Enter confirm | Esc cancel\n");
119
- for (let index = 0; index < options.length; index += 1) {
120
- const cursor = index === active ? "> " : " ";
121
- const marker = selected.has(options[index]) ? "[x]" : "[ ]";
122
- stderr.write(`${cursor}${marker} ${options[index]}\n`);
123
- }
124
- stderr.write(` Selected ${selected.size}/${options.length}\n`);
125
- }
126
- render();
127
- return new Promise((resolve) => {
128
- stdin.setRawMode(true);
129
- stdin.resume();
130
- function cleanup() {
131
- stdin.setRawMode(false);
132
- stdin.pause();
133
- stdin.removeListener("data", onData);
134
- }
135
- function finish(value) {
136
- cleanup();
137
- clearLines(stderr, lineCount);
138
- resolve(value);
139
- }
140
- function redraw(update) {
141
- clearLines(stderr, lineCount);
142
- update();
143
- render();
144
- }
145
- function onData(data) {
146
- let remaining = data.toString();
147
- while (remaining) {
148
- const sequence = nextSequence(remaining);
149
- remaining = remaining.slice(sequence.length);
150
- const key = parseKey(Buffer.from(sequence));
151
- if (key.name === "ctrl+c" || key.name === "ctrl+d" || key.name === "escape") {
152
- finish(null);
153
- return;
154
- }
155
- if (key.name === "return") {
156
- finish(options.filter((option) => selected.has(option)));
157
- return;
158
- }
159
- if (key.name === "up") {
160
- redraw(() => {
161
- active = (active - 1 + options.length) % options.length;
162
- });
163
- }
164
- else if (key.name === "down") {
165
- redraw(() => {
166
- active = (active + 1) % options.length;
167
- });
168
- }
169
- else if (key.name === "space") {
170
- redraw(() => {
171
- const option = options[active];
172
- if (selected.has(option))
173
- selected.delete(option);
174
- else
175
- selected.add(option);
176
- });
177
- }
178
- else if (key.name === "all") {
179
- redraw(() => {
180
- for (const option of options)
181
- selected.add(option);
182
- });
183
- }
184
- else if (key.name === "none") {
185
- redraw(() => selected.clear());
186
- }
187
- }
188
- }
189
- stdin.on("data", onData);
38
+ const value = await clack.multiselect({
39
+ message: label,
40
+ options: options.map((option) => ({ value: option, label: option })),
41
+ initialValues: [...initialSelected],
42
+ required: false,
43
+ input: stdin,
44
+ output: stderr,
190
45
  });
46
+ if (clack.isCancel(value))
47
+ return null;
48
+ return value;
191
49
  }
192
50
  export async function promptText(label, stdin = process.stdin, stderr = process.stderr) {
193
- if (!stdin.isTTY || !stderr.isTTY) {
194
- throw new InstallerError("terminal_required", "This operation requires an interactive terminal");
195
- }
196
- stderr.write(`? ${label}\n`);
197
- stderr.write(" Type a value | Enter confirm | Esc cancel\n");
198
- stderr.write("> ");
199
- return new Promise((resolve) => {
200
- let buffer = "";
201
- stdin.setRawMode(true);
202
- stdin.resume();
203
- function cleanup() {
204
- stdin.setRawMode(false);
205
- stdin.pause();
206
- stdin.removeListener("data", onData);
207
- }
208
- function onData(data) {
209
- for (const seq of data.toString()) {
210
- if (seq === "\r" || seq === "\n") {
211
- cleanup();
212
- stderr.write("\n");
213
- resolve(buffer.trim() || null);
214
- return;
215
- }
216
- if (seq === "\x03" || seq === "\x04" || seq === "\x1b") {
217
- cleanup();
218
- stderr.write("\n");
219
- resolve(null);
220
- return;
221
- }
222
- if (seq === "\x7f" || seq === "\b") {
223
- if (buffer.length > 0) {
224
- buffer = buffer.slice(0, -1);
225
- stderr.write("\x1b[D \x1b[D");
226
- }
227
- continue;
228
- }
229
- if (seq >= " ") {
230
- buffer += seq;
231
- stderr.write(seq);
232
- }
233
- }
234
- }
235
- stdin.on("data", onData);
236
- });
51
+ interactiveStreams(stdin, stderr);
52
+ const value = await clack.text({ message: label, input: stdin, output: stderr });
53
+ if (clack.isCancel(value))
54
+ return null;
55
+ const trimmed = value.trim();
56
+ return trimmed.length ? trimmed : null;
57
+ }
58
+ export async function confirmQuestion(label, stdin = process.stdin, stderr = process.stderr) {
59
+ interactiveStreams(stdin, stderr);
60
+ const value = await clack.confirm({ message: label, input: stdin, output: stderr });
61
+ if (clack.isCancel(value))
62
+ return null;
63
+ return value;
237
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kisev/agentomatic",
3
- "version": "11.0.0-dev.57.gb877c35a2446",
3
+ "version": "11.0.0-dev.58.gc136a777fcbb",
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": {
@@ -49,8 +49,9 @@
49
49
  "pack:check": "node test/pack-allowlist.mjs"
50
50
  },
51
51
  "dependencies": {
52
- "@kisev/memomatic": "1.0.0-dev.57.gb877c35a2446",
53
- "@kisev/safe-fs": "1.0.0-dev.57.gb877c35a2446"
52
+ "@clack/prompts": "^1.8.1",
53
+ "@kisev/memomatic": "1.0.0-dev.58.gc136a777fcbb",
54
+ "@kisev/safe-fs": "1.0.0-dev.58.gc136a777fcbb"
54
55
  },
55
56
  "devDependencies": {
56
57
  "@opencode-ai/plugin": "1.18.29",