@gmickel/gno 1.38.0 → 1.39.0
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/assets/skill/README.md +2 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.38.0.zip → gno-browser-clipper-v1.39.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.39.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +208 -11
- package/spec/output-schemas/agents-mutation.schema.json +108 -0
- package/spec/output-schemas/agents-verify.schema.json +89 -0
- package/src/cli/commands/agents/block.ts +164 -0
- package/src/cli/commands/agents/commands.ts +413 -0
- package/src/cli/commands/agents/engine.ts +417 -0
- package/src/cli/commands/agents/harnesses.ts +298 -0
- package/src/cli/commands/agents/index.ts +35 -0
- package/src/cli/commands/completion/scripts.ts +5 -0
- package/src/cli/program.ts +104 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.38.0.zip.sha256 +0 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness matrix for `gno agents`: which global (user-scope) instruction
|
|
3
|
+
* file each supported harness reads, how the harness is detected, and which
|
|
4
|
+
* import chains make a separate install redundant.
|
|
5
|
+
*
|
|
6
|
+
* Discovery is standard documented locations only — nonstandard/multi-
|
|
7
|
+
* instance layouts are served by the explicit `--extra-dir` flag, never by
|
|
8
|
+
* guessed discovery.
|
|
9
|
+
*
|
|
10
|
+
* @module src/cli/commands/agents/harnesses
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// node:fs: detection needs synchronous stat/realpath (symlink-aware identity)
|
|
14
|
+
// with no Bun equivalent.
|
|
15
|
+
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import {
|
|
18
|
+
basename,
|
|
19
|
+
dirname,
|
|
20
|
+
isAbsolute,
|
|
21
|
+
join,
|
|
22
|
+
normalize,
|
|
23
|
+
resolve,
|
|
24
|
+
} from "node:path";
|
|
25
|
+
|
|
26
|
+
import { CliError } from "../../errors.js";
|
|
27
|
+
|
|
28
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
29
|
+
// Environment Variables
|
|
30
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
/** Override home dir for testing / sandboxed live verification. */
|
|
33
|
+
export const ENV_AGENTS_HOME_OVERRIDE = "GNO_AGENTS_HOME_OVERRIDE";
|
|
34
|
+
|
|
35
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
36
|
+
// Types
|
|
37
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
export type HarnessId =
|
|
40
|
+
| "claude"
|
|
41
|
+
| "codex"
|
|
42
|
+
| "cursor"
|
|
43
|
+
| "opencode"
|
|
44
|
+
| "grok"
|
|
45
|
+
| "hermes"
|
|
46
|
+
| "openclaw";
|
|
47
|
+
|
|
48
|
+
export const HARNESS_IDS: HarnessId[] = [
|
|
49
|
+
"claude",
|
|
50
|
+
"codex",
|
|
51
|
+
"cursor",
|
|
52
|
+
"opencode",
|
|
53
|
+
"grok",
|
|
54
|
+
"hermes",
|
|
55
|
+
"openclaw",
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
interface HarnessDef {
|
|
59
|
+
id: HarnessId;
|
|
60
|
+
label: string;
|
|
61
|
+
/** Harness config dir (detection root), relative to home. */
|
|
62
|
+
configDir: string;
|
|
63
|
+
/** Instruction file: inside the config dir, or directly under home. */
|
|
64
|
+
instructionFile: { dir: "config" | "home"; name: string };
|
|
65
|
+
/**
|
|
66
|
+
* Env var naming the harness's documented config-dir override
|
|
67
|
+
* (honored only when no explicit homeDir override is active).
|
|
68
|
+
*/
|
|
69
|
+
configDirEnvVar?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Import chain: this harness reads another harness's instruction file, so
|
|
72
|
+
* a separate install would double the block. Data-driven so future chains
|
|
73
|
+
* are matrix entries, not code changes.
|
|
74
|
+
*/
|
|
75
|
+
coveredBy?: HarnessId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const HARNESS_DEFS: Record<HarnessId, HarnessDef> = {
|
|
79
|
+
claude: {
|
|
80
|
+
id: "claude",
|
|
81
|
+
label: "Claude Code",
|
|
82
|
+
configDir: ".claude",
|
|
83
|
+
instructionFile: { dir: "config", name: "CLAUDE.md" },
|
|
84
|
+
configDirEnvVar: "CLAUDE_CONFIG_DIR",
|
|
85
|
+
},
|
|
86
|
+
codex: {
|
|
87
|
+
id: "codex",
|
|
88
|
+
label: "Codex",
|
|
89
|
+
configDir: ".codex",
|
|
90
|
+
instructionFile: { dir: "config", name: "AGENTS.md" },
|
|
91
|
+
configDirEnvVar: "CODEX_HOME",
|
|
92
|
+
},
|
|
93
|
+
cursor: {
|
|
94
|
+
id: "cursor",
|
|
95
|
+
label: "Cursor Agent",
|
|
96
|
+
configDir: ".cursor",
|
|
97
|
+
// Cursor Agent discovers AGENTS.md walking from the working directory
|
|
98
|
+
// towards the home directory; ~/AGENTS.md is its user-global surface.
|
|
99
|
+
instructionFile: { dir: "home", name: "AGENTS.md" },
|
|
100
|
+
},
|
|
101
|
+
opencode: {
|
|
102
|
+
id: "opencode",
|
|
103
|
+
label: "OpenCode",
|
|
104
|
+
configDir: ".config/opencode",
|
|
105
|
+
instructionFile: { dir: "config", name: "AGENTS.md" },
|
|
106
|
+
},
|
|
107
|
+
grok: {
|
|
108
|
+
id: "grok",
|
|
109
|
+
label: "Grok Build",
|
|
110
|
+
configDir: ".grok",
|
|
111
|
+
// Grok imports the Claude global instruction file — no file of its own;
|
|
112
|
+
// `coveredBy` makes resolution report Claude's file for it.
|
|
113
|
+
instructionFile: { dir: "config", name: "AGENTS.md" },
|
|
114
|
+
coveredBy: "claude",
|
|
115
|
+
},
|
|
116
|
+
hermes: {
|
|
117
|
+
id: "hermes",
|
|
118
|
+
label: "Hermes",
|
|
119
|
+
configDir: ".hermes",
|
|
120
|
+
instructionFile: { dir: "config", name: "SOUL.md" },
|
|
121
|
+
},
|
|
122
|
+
openclaw: {
|
|
123
|
+
id: "openclaw",
|
|
124
|
+
label: "OpenClaw",
|
|
125
|
+
configDir: ".openclaw/workspace",
|
|
126
|
+
instructionFile: { dir: "config", name: "AGENTS.md" },
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export interface ResolvedTarget {
|
|
131
|
+
/** Harness id, or "extra-dir" for explicit --extra-dir paths. */
|
|
132
|
+
id: HarnessId | "extra-dir";
|
|
133
|
+
label: string;
|
|
134
|
+
/** Detection root (harness config dir, or the extra dir itself). */
|
|
135
|
+
configDir: string;
|
|
136
|
+
/** Absolute path to the instruction file the harness reads. */
|
|
137
|
+
file: string;
|
|
138
|
+
/** Real (symlink-resolved) identity of the file, for dedupe + writes. */
|
|
139
|
+
realFile: string;
|
|
140
|
+
/** Whether the harness is detected on this machine. */
|
|
141
|
+
detected: boolean;
|
|
142
|
+
/** Import chain target this harness is covered by, when applicable. */
|
|
143
|
+
coveredBy?: HarnessId;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface ResolveOptions {
|
|
147
|
+
/** Explicit home override (testing / sandboxed verification). When set,
|
|
148
|
+
* harness config-dir env overrides are ignored for determinism. */
|
|
149
|
+
homeDir?: string;
|
|
150
|
+
/** Explicit extra instruction dirs (nonstandard/multi-instance layouts). */
|
|
151
|
+
extraDirs?: string[];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
155
|
+
// Resolution
|
|
156
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Symlink-resolved identity of an instruction file. An existing file resolves
|
|
160
|
+
* fully (so a canonical file linked into several harnesses is written once,
|
|
161
|
+
* through the link); a file that does not exist yet resolves its parent dir,
|
|
162
|
+
* so two harnesses whose config dirs are links to one place still share an
|
|
163
|
+
* identity. Anything unresolvable falls back to the normalized path.
|
|
164
|
+
*/
|
|
165
|
+
export function realIdentity(file: string): string {
|
|
166
|
+
try {
|
|
167
|
+
return realpathSync(file);
|
|
168
|
+
} catch {
|
|
169
|
+
const normalized = normalize(file);
|
|
170
|
+
try {
|
|
171
|
+
return join(realpathSync(dirname(normalized)), basename(normalized));
|
|
172
|
+
} catch {
|
|
173
|
+
return normalized;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function isDirectory(path: string): boolean {
|
|
179
|
+
try {
|
|
180
|
+
return statSync(path).isDirectory();
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function resolveHarness(
|
|
187
|
+
def: HarnessDef,
|
|
188
|
+
home: string,
|
|
189
|
+
explicitHome: boolean
|
|
190
|
+
): ResolvedTarget {
|
|
191
|
+
let configDir = join(home, def.configDir);
|
|
192
|
+
|
|
193
|
+
if (!explicitHome && def.configDirEnvVar) {
|
|
194
|
+
const envOverride = process.env[def.configDirEnvVar];
|
|
195
|
+
if (envOverride) {
|
|
196
|
+
if (!isAbsolute(envOverride)) {
|
|
197
|
+
throw new CliError(
|
|
198
|
+
"VALIDATION",
|
|
199
|
+
`${def.configDirEnvVar} must be an absolute path`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
configDir = normalize(envOverride);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// A covered harness (grok → claude) reads its covering harness's file and
|
|
207
|
+
// has none of its own, so that is the file its rows report.
|
|
208
|
+
const file = def.coveredBy
|
|
209
|
+
? resolveHarness(HARNESS_DEFS[def.coveredBy], home, explicitHome).file
|
|
210
|
+
: def.instructionFile.dir === "home"
|
|
211
|
+
? join(home, def.instructionFile.name)
|
|
212
|
+
: join(configDir, def.instructionFile.name);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
id: def.id,
|
|
216
|
+
label: def.label,
|
|
217
|
+
configDir,
|
|
218
|
+
file,
|
|
219
|
+
realFile: realIdentity(file),
|
|
220
|
+
detected: isDirectory(configDir),
|
|
221
|
+
coveredBy: def.coveredBy,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Instruction file candidates for --extra-dir, in priority order. */
|
|
226
|
+
const EXTRA_DIR_FILE_CANDIDATES = ["CLAUDE.md", "AGENTS.md", "SOUL.md"];
|
|
227
|
+
const DEFAULT_EXTRA_DIR_FILE = "AGENTS.md";
|
|
228
|
+
|
|
229
|
+
function resolveExtraDir(dir: string): ResolvedTarget {
|
|
230
|
+
const abs = resolve(dir);
|
|
231
|
+
if (!isDirectory(abs)) {
|
|
232
|
+
throw new CliError(
|
|
233
|
+
"VALIDATION",
|
|
234
|
+
`--extra-dir ${dir} does not exist or is not a directory. The installer never fabricates harness directories.`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
const existing = EXTRA_DIR_FILE_CANDIDATES.find((name) =>
|
|
238
|
+
existsSync(join(abs, name))
|
|
239
|
+
);
|
|
240
|
+
const file = join(abs, existing ?? DEFAULT_EXTRA_DIR_FILE);
|
|
241
|
+
return {
|
|
242
|
+
id: "extra-dir",
|
|
243
|
+
label: `extra dir ${abs}`,
|
|
244
|
+
configDir: abs,
|
|
245
|
+
file,
|
|
246
|
+
realFile: realIdentity(file),
|
|
247
|
+
detected: true,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Covering chain for an explicit target (e.g. grok → claude), covering first. */
|
|
252
|
+
function coveringChain(id: HarnessId): HarnessId[] {
|
|
253
|
+
const chain: HarnessId[] = [];
|
|
254
|
+
let cursor = HARNESS_DEFS[id].coveredBy;
|
|
255
|
+
while (cursor && !chain.includes(cursor) && cursor !== id) {
|
|
256
|
+
chain.unshift(cursor);
|
|
257
|
+
cursor = HARNESS_DEFS[cursor].coveredBy;
|
|
258
|
+
}
|
|
259
|
+
return chain;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Resolve all requested targets to concrete instruction files.
|
|
264
|
+
* `target: "all"` = every supported harness (detection filters at plan time).
|
|
265
|
+
* An explicit covered target (e.g. `grok`) also resolves its covering
|
|
266
|
+
* target(s) so the file it actually reads is planned/verified — but only when
|
|
267
|
+
* the requested target itself is detected; an absent one is reported
|
|
268
|
+
* `not-detected` without touching the covering harness's file.
|
|
269
|
+
*/
|
|
270
|
+
export function resolveTargets(
|
|
271
|
+
target: HarnessId | "all",
|
|
272
|
+
opts: ResolveOptions = {}
|
|
273
|
+
): ResolvedTarget[] {
|
|
274
|
+
// Any home override (option or env) suppresses harness config-dir env
|
|
275
|
+
// overrides too — an overridden home is an isolation request.
|
|
276
|
+
const explicitHome =
|
|
277
|
+
opts.homeDir !== undefined ||
|
|
278
|
+
process.env[ENV_AGENTS_HOME_OVERRIDE] !== undefined;
|
|
279
|
+
const home =
|
|
280
|
+
opts.homeDir ?? process.env[ENV_AGENTS_HOME_OVERRIDE] ?? homedir();
|
|
281
|
+
const one = (id: HarnessId): ResolvedTarget =>
|
|
282
|
+
resolveHarness(HARNESS_DEFS[id], home, explicitHome);
|
|
283
|
+
|
|
284
|
+
let results: ResolvedTarget[];
|
|
285
|
+
if (target === "all") {
|
|
286
|
+
results = HARNESS_IDS.map(one);
|
|
287
|
+
} else {
|
|
288
|
+
const leaf = one(target);
|
|
289
|
+
results = leaf.detected
|
|
290
|
+
? [...coveringChain(target).map(one), leaf]
|
|
291
|
+
: [leaf];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for (const dir of opts.extraDirs ?? []) {
|
|
295
|
+
results.push(resolveExtraDir(dir));
|
|
296
|
+
}
|
|
297
|
+
return results;
|
|
298
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agents command exports.
|
|
3
|
+
*
|
|
4
|
+
* @module src/cli/commands/agents
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
BEGIN_MARKER,
|
|
9
|
+
BLOCK_VERSION,
|
|
10
|
+
END_MARKER,
|
|
11
|
+
extractBlock,
|
|
12
|
+
hashBlockBody,
|
|
13
|
+
renderBlock,
|
|
14
|
+
renderBlockBody,
|
|
15
|
+
} from "./block.js";
|
|
16
|
+
export {
|
|
17
|
+
type AgentsOptions,
|
|
18
|
+
installAgents,
|
|
19
|
+
parseTargetOption,
|
|
20
|
+
uninstallAgents,
|
|
21
|
+
verifyAgents,
|
|
22
|
+
} from "./commands.js";
|
|
23
|
+
export {
|
|
24
|
+
applyPlan,
|
|
25
|
+
planTargets,
|
|
26
|
+
type TargetPlan,
|
|
27
|
+
unifiedDiff,
|
|
28
|
+
} from "./engine.js";
|
|
29
|
+
export {
|
|
30
|
+
ENV_AGENTS_HOME_OVERRIDE,
|
|
31
|
+
type HarnessId,
|
|
32
|
+
HARNESS_IDS,
|
|
33
|
+
type ResolvedTarget,
|
|
34
|
+
resolveTargets,
|
|
35
|
+
} from "./harnesses.js";
|
package/src/cli/program.ts
CHANGED
|
@@ -364,6 +364,7 @@ export function createProgram(): Command {
|
|
|
364
364
|
wireKnowledgeDeltaCommands(program);
|
|
365
365
|
wireMcpCommand(program);
|
|
366
366
|
wireSkillCommands(program);
|
|
367
|
+
wireAgentsCommands(program);
|
|
367
368
|
wireDaemonCommand(program);
|
|
368
369
|
wireServeCommand(program);
|
|
369
370
|
wireCompletionCommand(program);
|
|
@@ -3320,6 +3321,109 @@ function wireSkillCommands(program: Command): void {
|
|
|
3320
3321
|
});
|
|
3321
3322
|
}
|
|
3322
3323
|
|
|
3324
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3325
|
+
// Agents Commands (install, update, verify, uninstall)
|
|
3326
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3327
|
+
|
|
3328
|
+
function wireAgentsCommands(program: Command): void {
|
|
3329
|
+
const agentsCmd = program
|
|
3330
|
+
.command("agents")
|
|
3331
|
+
.description(
|
|
3332
|
+
"Manage the GNO protocol block in global harness instruction files"
|
|
3333
|
+
);
|
|
3334
|
+
|
|
3335
|
+
const targetDescription =
|
|
3336
|
+
"target harness (claude, codex, cursor, opencode, grok, hermes, openclaw, all)";
|
|
3337
|
+
|
|
3338
|
+
const collectExtraDir = (value: string, previous: string[]): string[] => [
|
|
3339
|
+
...previous,
|
|
3340
|
+
value,
|
|
3341
|
+
];
|
|
3342
|
+
|
|
3343
|
+
const parseCommonOpts = async (cmdOpts: Record<string, unknown>) => {
|
|
3344
|
+
const { parseTargetOption } = await import("./commands/agents/commands.js");
|
|
3345
|
+
return {
|
|
3346
|
+
target: parseTargetOption((cmdOpts.target as string) ?? "all"),
|
|
3347
|
+
extraDirs: (cmdOpts.extraDir as string[] | undefined) ?? [],
|
|
3348
|
+
dryRun: Boolean(cmdOpts.dryRun),
|
|
3349
|
+
// The root program also defines a global --json and may consume the
|
|
3350
|
+
// flag; leave undefined here so the runner falls back to globals.
|
|
3351
|
+
json: cmdOpts.json ? true : undefined,
|
|
3352
|
+
};
|
|
3353
|
+
};
|
|
3354
|
+
|
|
3355
|
+
agentsCmd
|
|
3356
|
+
.command("install")
|
|
3357
|
+
.description(
|
|
3358
|
+
"Install the GNO protocol block into detected harnesses' global instruction files"
|
|
3359
|
+
)
|
|
3360
|
+
.option("-t, --target <target>", targetDescription, "all")
|
|
3361
|
+
.option(
|
|
3362
|
+
"--extra-dir <path>",
|
|
3363
|
+
"additional instruction dir (repeatable, for nonstandard layouts)",
|
|
3364
|
+
collectExtraDir,
|
|
3365
|
+
[] as string[]
|
|
3366
|
+
)
|
|
3367
|
+
.option("--dry-run", "print the unified diff and write nothing")
|
|
3368
|
+
.option("--json", "JSON output")
|
|
3369
|
+
.action(async (cmdOpts: Record<string, unknown>) => {
|
|
3370
|
+
const { installAgents } = await import("./commands/agents/commands.js");
|
|
3371
|
+
await installAgents(await parseCommonOpts(cmdOpts), "install");
|
|
3372
|
+
});
|
|
3373
|
+
|
|
3374
|
+
agentsCmd
|
|
3375
|
+
.command("update")
|
|
3376
|
+
.description("Refresh an installed GNO protocol block in place")
|
|
3377
|
+
.option("-t, --target <target>", targetDescription, "all")
|
|
3378
|
+
.option(
|
|
3379
|
+
"--extra-dir <path>",
|
|
3380
|
+
"additional instruction dir (repeatable, for nonstandard layouts)",
|
|
3381
|
+
collectExtraDir,
|
|
3382
|
+
[] as string[]
|
|
3383
|
+
)
|
|
3384
|
+
.option("--dry-run", "print the unified diff and write nothing")
|
|
3385
|
+
.option("--json", "JSON output")
|
|
3386
|
+
.action(async (cmdOpts: Record<string, unknown>) => {
|
|
3387
|
+
const { installAgents } = await import("./commands/agents/commands.js");
|
|
3388
|
+
await installAgents(await parseCommonOpts(cmdOpts), "update");
|
|
3389
|
+
});
|
|
3390
|
+
|
|
3391
|
+
agentsCmd
|
|
3392
|
+
.command("verify")
|
|
3393
|
+
.description(
|
|
3394
|
+
"Verify installed GNO protocol blocks (one block, version + hash)"
|
|
3395
|
+
)
|
|
3396
|
+
.option("-t, --target <target>", targetDescription, "all")
|
|
3397
|
+
.option(
|
|
3398
|
+
"--extra-dir <path>",
|
|
3399
|
+
"additional instruction dir (repeatable, for nonstandard layouts)",
|
|
3400
|
+
collectExtraDir,
|
|
3401
|
+
[] as string[]
|
|
3402
|
+
)
|
|
3403
|
+
.option("--json", "JSON output")
|
|
3404
|
+
.action(async (cmdOpts: Record<string, unknown>) => {
|
|
3405
|
+
const { verifyAgents } = await import("./commands/agents/commands.js");
|
|
3406
|
+
await verifyAgents(await parseCommonOpts(cmdOpts));
|
|
3407
|
+
});
|
|
3408
|
+
|
|
3409
|
+
agentsCmd
|
|
3410
|
+
.command("uninstall")
|
|
3411
|
+
.description("Remove the GNO protocol block and markers from harness files")
|
|
3412
|
+
.option("-t, --target <target>", targetDescription, "all")
|
|
3413
|
+
.option(
|
|
3414
|
+
"--extra-dir <path>",
|
|
3415
|
+
"additional instruction dir (repeatable, for nonstandard layouts)",
|
|
3416
|
+
collectExtraDir,
|
|
3417
|
+
[] as string[]
|
|
3418
|
+
)
|
|
3419
|
+
.option("--dry-run", "print the unified diff and write nothing")
|
|
3420
|
+
.option("--json", "JSON output")
|
|
3421
|
+
.action(async (cmdOpts: Record<string, unknown>) => {
|
|
3422
|
+
const { uninstallAgents } = await import("./commands/agents/commands.js");
|
|
3423
|
+
await uninstallAgents(await parseCommonOpts(cmdOpts));
|
|
3424
|
+
});
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3323
3427
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
3324
3428
|
// Tags Commands (list, add, rm)
|
|
3325
3429
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
59f8dc960d1910f7b9dec3c4fea286c46bfe63487a43212fbb8ba61a207753ae gno-browser-clipper-v1.38.0.zip
|