@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 +58 -9
- package/dist/config-setup.js +23 -1
- package/dist/doctor.js +25 -0
- package/dist/installer.d.ts +6 -8
- package/dist/installer.js +2 -2
- package/dist/reconcile.js +1 -1
- package/dist/terminal-wizard.d.ts +5 -3
- package/dist/terminal-wizard.js +40 -213
- package/package.json +4 -3
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
|
|
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
|
-
"
|
|
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
|
|
645
|
-
if (choice !==
|
|
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
|
}
|
package/dist/config-setup.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/dist/installer.d.ts
CHANGED
|
@@ -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:
|
|
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 =
|
|
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
|
|
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
|
-
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function
|
|
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>;
|
package/dist/terminal-wizard.js
CHANGED
|
@@ -1,36 +1,11 @@
|
|
|
1
|
+
import * as clack from "@clack/prompts";
|
|
1
2
|
import { InstallerError } from "./installer.js";
|
|
2
|
-
function
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
29
|
-
|
|
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
|
-
|
|
48
|
-
throw new InstallerError("terminal_required", "This operation requires an interactive terminal");
|
|
49
|
-
}
|
|
17
|
+
interactiveStreams(stdin, stderr);
|
|
50
18
|
validateOptions(options);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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.
|
|
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
|
-
"@
|
|
53
|
-
"@kisev/
|
|
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",
|