@usezombie/zombiectl 0.3.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/README.md +76 -0
- package/bin/zombiectl.js +11 -0
- package/bun.lock +29 -0
- package/package.json +28 -0
- package/scripts/run-tests.mjs +38 -0
- package/src/cli.js +275 -0
- package/src/commands/admin.js +39 -0
- package/src/commands/agent.js +98 -0
- package/src/commands/agent_harness.js +43 -0
- package/src/commands/agent_improvement_report.js +42 -0
- package/src/commands/agent_profile.js +39 -0
- package/src/commands/agent_proposals.js +158 -0
- package/src/commands/agent_scores.js +44 -0
- package/src/commands/core-ops.js +108 -0
- package/src/commands/core.js +537 -0
- package/src/commands/harness.js +35 -0
- package/src/commands/harness_activate.js +53 -0
- package/src/commands/harness_active.js +32 -0
- package/src/commands/harness_compile.js +40 -0
- package/src/commands/harness_source.js +72 -0
- package/src/commands/run_preview.js +212 -0
- package/src/commands/run_preview_walk.js +1 -0
- package/src/commands/runs.js +35 -0
- package/src/commands/spec_init.js +287 -0
- package/src/commands/workspace_billing.js +26 -0
- package/src/constants/error-codes.js +1 -0
- package/src/lib/agent-loop.js +106 -0
- package/src/lib/analytics.js +114 -0
- package/src/lib/api-paths.js +2 -0
- package/src/lib/browser.js +96 -0
- package/src/lib/http.js +149 -0
- package/src/lib/sse-parser.js +50 -0
- package/src/lib/state.js +67 -0
- package/src/lib/tool-executors.js +110 -0
- package/src/lib/walk-dir.js +41 -0
- package/src/program/args.js +95 -0
- package/src/program/auth-guard.js +12 -0
- package/src/program/auth-token.js +44 -0
- package/src/program/banner.js +46 -0
- package/src/program/command-registry.js +17 -0
- package/src/program/http-client.js +38 -0
- package/src/program/io.js +83 -0
- package/src/program/routes.js +20 -0
- package/src/program/suggest.js +76 -0
- package/src/program/validate.js +24 -0
- package/src/ui-progress.js +59 -0
- package/src/ui-theme.js +62 -0
- package/test/admin_config.unit.test.js +25 -0
- package/test/agent-loop.unit.test.js +497 -0
- package/test/agent_harness.unit.test.js +52 -0
- package/test/agent_improvement_report.unit.test.js +74 -0
- package/test/agent_profile.unit.test.js +156 -0
- package/test/agent_proposals.unit.test.js +167 -0
- package/test/agent_scores.unit.test.js +220 -0
- package/test/analytics.unit.test.js +41 -0
- package/test/args.unit.test.js +69 -0
- package/test/auth-guard.test.js +33 -0
- package/test/auth-token.unit.test.js +112 -0
- package/test/banner.unit.test.js +442 -0
- package/test/browser.unit.test.js +16 -0
- package/test/cli-analytics.unit.test.js +296 -0
- package/test/did-you-mean.integration.test.js +76 -0
- package/test/doctor-json.test.js +81 -0
- package/test/error-codes.unit.test.js +7 -0
- package/test/harness-command.unit.test.js +180 -0
- package/test/harness-compile.test.js +81 -0
- package/test/harness-lifecycle.integration.test.js +339 -0
- package/test/harness-source-put.test.js +72 -0
- package/test/harness_activate.unit.test.js +48 -0
- package/test/harness_active.unit.test.js +53 -0
- package/test/harness_compile.unit.test.js +54 -0
- package/test/harness_source.unit.test.js +59 -0
- package/test/help.test.js +276 -0
- package/test/helpers-fs.js +32 -0
- package/test/helpers.js +31 -0
- package/test/io.unit.test.js +57 -0
- package/test/login.unit.test.js +115 -0
- package/test/logout.unit.test.js +65 -0
- package/test/parse.test.js +16 -0
- package/test/run-preview.edge.test.js +422 -0
- package/test/run-preview.integration.test.js +135 -0
- package/test/run-preview.security.test.js +246 -0
- package/test/run-preview.unit.test.js +131 -0
- package/test/run.unit.test.js +149 -0
- package/test/runs-cancel.unit.test.js +288 -0
- package/test/runs-list.unit.test.js +105 -0
- package/test/skill-secret.unit.test.js +94 -0
- package/test/spec-init.edge.test.js +232 -0
- package/test/spec-init.integration.test.js +128 -0
- package/test/spec-init.security.test.js +285 -0
- package/test/spec-init.unit.test.js +160 -0
- package/test/specs-sync.unit.test.js +164 -0
- package/test/sse-parser.unit.test.js +54 -0
- package/test/state.unit.test.js +34 -0
- package/test/streamfetch.unit.test.js +211 -0
- package/test/suggest.test.js +75 -0
- package/test/tool-executors.unit.test.js +165 -0
- package/test/validate.test.js +81 -0
- package/test/workspace-add.test.js +106 -0
- package/test/workspace.unit.test.js +230 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AGENTS_PATH } from "../lib/api-paths.js";
|
|
2
|
+
import { queueCliAnalyticsEvent, setCliAnalyticsContext } from "../lib/analytics.js";
|
|
3
|
+
|
|
4
|
+
export async function commandAgentProfile(ctx, parsed, agentId, deps) {
|
|
5
|
+
const { request, apiHeaders, printJson, printKeyValue, printSection = () => {} } = deps;
|
|
6
|
+
|
|
7
|
+
const res = await request(ctx, `${AGENTS_PATH}${encodeURIComponent(agentId)}`, {
|
|
8
|
+
method: "GET",
|
|
9
|
+
headers: apiHeaders(ctx),
|
|
10
|
+
});
|
|
11
|
+
setCliAnalyticsContext(ctx, {
|
|
12
|
+
agent_id: res.agent_id,
|
|
13
|
+
workspace_id: res.workspace_id,
|
|
14
|
+
trust_level: res.trust_level,
|
|
15
|
+
agent_status: res.status,
|
|
16
|
+
});
|
|
17
|
+
queueCliAnalyticsEvent(ctx, "agent_profile_viewed", {
|
|
18
|
+
agent_id: res.agent_id,
|
|
19
|
+
workspace_id: res.workspace_id,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (ctx.jsonMode) {
|
|
23
|
+
printJson(ctx.stdout, res);
|
|
24
|
+
} else {
|
|
25
|
+
printSection(ctx.stdout, `Agent profile · ${res.agent_id}`);
|
|
26
|
+
printKeyValue(ctx.stdout, {
|
|
27
|
+
agent_id: res.agent_id,
|
|
28
|
+
name: res.name,
|
|
29
|
+
status: res.status,
|
|
30
|
+
workspace_id: res.workspace_id,
|
|
31
|
+
trust_level: res.trust_level,
|
|
32
|
+
trust_streak_runs: res.trust_streak_runs,
|
|
33
|
+
improvement_stalled_warning: res.improvement_stalled_warning,
|
|
34
|
+
created_at: res.created_at,
|
|
35
|
+
updated_at: res.updated_at,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { AGENTS_PATH } from "../lib/api-paths.js";
|
|
2
|
+
import { queueCliAnalyticsEvent, setCliAnalyticsContext } from "../lib/analytics.js";
|
|
3
|
+
|
|
4
|
+
export async function commandAgentProposals(ctx, parsed, agentId, deps) {
|
|
5
|
+
const { request, apiHeaders, printJson, printSection = () => {}, printTable, ui, writeLine } = deps;
|
|
6
|
+
|
|
7
|
+
const subaction = parsed.positionals[1] || "list";
|
|
8
|
+
const proposalId = parsed.positionals[2] || null;
|
|
9
|
+
|
|
10
|
+
if (subaction === "list") {
|
|
11
|
+
const res = await request(ctx, `${AGENTS_PATH}${encodeURIComponent(agentId)}/proposals`, {
|
|
12
|
+
method: "GET",
|
|
13
|
+
headers: apiHeaders(ctx),
|
|
14
|
+
});
|
|
15
|
+
const items = Array.isArray(res.data) ? res.data : [];
|
|
16
|
+
setCliAnalyticsContext(ctx, {
|
|
17
|
+
agent_id: agentId,
|
|
18
|
+
proposal_count: items.length,
|
|
19
|
+
});
|
|
20
|
+
queueCliAnalyticsEvent(ctx, "agent_proposals_viewed", {
|
|
21
|
+
agent_id: agentId,
|
|
22
|
+
proposal_count: items.length,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (ctx.jsonMode) {
|
|
26
|
+
printJson(ctx.stdout, res);
|
|
27
|
+
} else {
|
|
28
|
+
if (items.length === 0) {
|
|
29
|
+
writeLine(ctx.stdout, ui.info("no open proposals"));
|
|
30
|
+
} else {
|
|
31
|
+
printSection(ctx.stdout, `Agent proposals · ${agentId}`);
|
|
32
|
+
printTable(ctx.stdout, [
|
|
33
|
+
{ key: "proposal_id", label: "PROPOSAL_ID" },
|
|
34
|
+
{ key: "status", label: "STATUS" },
|
|
35
|
+
{ key: "trigger_reason", label: "TRIGGER" },
|
|
36
|
+
{ key: "action", label: "ACTION" },
|
|
37
|
+
{ key: "config_version_id", label: "CONFIG_VERSION_ID" },
|
|
38
|
+
{ key: "created_at", label: "CREATED_AT" },
|
|
39
|
+
], items.map((item) => ({
|
|
40
|
+
...item,
|
|
41
|
+
action: describeProposalAction(item),
|
|
42
|
+
})));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!proposalId) {
|
|
49
|
+
writeLine(ctx.stderr, ui.err(`agent proposals ${subaction} requires <proposal-id>`));
|
|
50
|
+
return 2;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (subaction === "approve") {
|
|
54
|
+
const res = await request(
|
|
55
|
+
ctx,
|
|
56
|
+
`${AGENTS_PATH}${encodeURIComponent(agentId)}/proposals/${encodeURIComponent(proposalId)}:approve`,
|
|
57
|
+
{
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: apiHeaders(ctx),
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
setCliAnalyticsContext(ctx, {
|
|
63
|
+
agent_id: agentId,
|
|
64
|
+
proposal_id: res.proposal_id,
|
|
65
|
+
proposal_status: res.status,
|
|
66
|
+
});
|
|
67
|
+
queueCliAnalyticsEvent(ctx, "agent_proposal_approved", {
|
|
68
|
+
agent_id: agentId,
|
|
69
|
+
proposal_id: res.proposal_id,
|
|
70
|
+
});
|
|
71
|
+
if (ctx.jsonMode) {
|
|
72
|
+
printJson(ctx.stdout, res);
|
|
73
|
+
} else {
|
|
74
|
+
writeLine(ctx.stdout, ui.ok(`approved ${res.proposal_id} -> ${res.status}`));
|
|
75
|
+
}
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (subaction === "reject") {
|
|
80
|
+
const reason = parsed.options.reason || null;
|
|
81
|
+
const res = await request(
|
|
82
|
+
ctx,
|
|
83
|
+
`${AGENTS_PATH}${encodeURIComponent(agentId)}/proposals/${encodeURIComponent(proposalId)}:reject`,
|
|
84
|
+
{
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: apiHeaders(ctx),
|
|
87
|
+
body: JSON.stringify(reason ? { reason } : {}),
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
setCliAnalyticsContext(ctx, {
|
|
91
|
+
agent_id: agentId,
|
|
92
|
+
proposal_id: res.proposal_id,
|
|
93
|
+
proposal_status: res.status,
|
|
94
|
+
rejection_reason: res.rejection_reason,
|
|
95
|
+
});
|
|
96
|
+
queueCliAnalyticsEvent(ctx, "agent_proposal_rejected", {
|
|
97
|
+
agent_id: agentId,
|
|
98
|
+
proposal_id: res.proposal_id,
|
|
99
|
+
});
|
|
100
|
+
if (ctx.jsonMode) {
|
|
101
|
+
printJson(ctx.stdout, res);
|
|
102
|
+
} else {
|
|
103
|
+
writeLine(ctx.stdout, ui.ok(`rejected ${res.proposal_id} (${res.rejection_reason})`));
|
|
104
|
+
}
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (subaction === "veto") {
|
|
109
|
+
const reason = parsed.options.reason || null;
|
|
110
|
+
const res = await request(
|
|
111
|
+
ctx,
|
|
112
|
+
`${AGENTS_PATH}${encodeURIComponent(agentId)}/proposals/${encodeURIComponent(proposalId)}:veto`,
|
|
113
|
+
{
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: apiHeaders(ctx),
|
|
116
|
+
body: JSON.stringify(reason ? { reason } : {}),
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
setCliAnalyticsContext(ctx, {
|
|
120
|
+
agent_id: agentId,
|
|
121
|
+
proposal_id: res.proposal_id,
|
|
122
|
+
proposal_status: res.status,
|
|
123
|
+
rejection_reason: res.rejection_reason,
|
|
124
|
+
});
|
|
125
|
+
queueCliAnalyticsEvent(ctx, "agent_proposal_vetoed", {
|
|
126
|
+
agent_id: agentId,
|
|
127
|
+
proposal_id: res.proposal_id,
|
|
128
|
+
});
|
|
129
|
+
if (ctx.jsonMode) {
|
|
130
|
+
printJson(ctx.stdout, res);
|
|
131
|
+
} else {
|
|
132
|
+
writeLine(ctx.stdout, ui.ok(`vetoed ${res.proposal_id} (${res.rejection_reason})`));
|
|
133
|
+
}
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
writeLine(ctx.stderr, ui.err("usage: agent proposals <agent-id>"));
|
|
138
|
+
writeLine(ctx.stderr, ui.err(" agent proposals <agent-id> approve <proposal-id>"));
|
|
139
|
+
writeLine(ctx.stderr, ui.err(" agent proposals <agent-id> reject <proposal-id> [--reason TEXT]"));
|
|
140
|
+
writeLine(ctx.stderr, ui.err(" agent proposals <agent-id> veto <proposal-id> [--reason TEXT]"));
|
|
141
|
+
return 2;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function describeProposalAction(item) {
|
|
145
|
+
if (item?.status === "VETO_WINDOW") {
|
|
146
|
+
return `${formatCountdown(item.auto_apply_at)} - zombiectl agent proposals veto ${item.proposal_id} to cancel`;
|
|
147
|
+
}
|
|
148
|
+
return "manual review required";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function formatCountdown(autoApplyAt) {
|
|
152
|
+
if (!Number.isFinite(autoApplyAt)) return "Auto-apply scheduled";
|
|
153
|
+
const diffMs = Math.max(0, autoApplyAt - Date.now());
|
|
154
|
+
const totalMinutes = Math.floor(diffMs / 60000);
|
|
155
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
156
|
+
const minutes = totalMinutes % 60;
|
|
157
|
+
return `Auto-applies in ${hours}h ${minutes}m`;
|
|
158
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AGENTS_PATH } from "../lib/api-paths.js";
|
|
2
|
+
import { queueCliAnalyticsEvent, setCliAnalyticsContext } from "../lib/analytics.js";
|
|
3
|
+
|
|
4
|
+
export async function commandAgentScores(ctx, parsed, agentId, deps) {
|
|
5
|
+
const { request, apiHeaders, ui, printJson, printSection = () => {}, printTable, writeLine } = deps;
|
|
6
|
+
|
|
7
|
+
const limit = parsed.options.limit || 20;
|
|
8
|
+
const startingAfter = parsed.options["starting-after"] || null;
|
|
9
|
+
|
|
10
|
+
let url = `${AGENTS_PATH}${encodeURIComponent(agentId)}/scores?limit=${limit}`;
|
|
11
|
+
if (startingAfter) url += `&starting_after=${encodeURIComponent(startingAfter)}`;
|
|
12
|
+
|
|
13
|
+
const res = await request(ctx, url, { method: "GET", headers: apiHeaders(ctx) });
|
|
14
|
+
const items = Array.isArray(res.data) ? res.data : [];
|
|
15
|
+
setCliAnalyticsContext(ctx, {
|
|
16
|
+
agent_id: agentId,
|
|
17
|
+
score_count: items.length,
|
|
18
|
+
next_cursor: res.next_cursor ?? "",
|
|
19
|
+
});
|
|
20
|
+
queueCliAnalyticsEvent(ctx, "agent_scores_viewed", {
|
|
21
|
+
agent_id: agentId,
|
|
22
|
+
score_count: items.length,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (ctx.jsonMode) {
|
|
26
|
+
printJson(ctx.stdout, res);
|
|
27
|
+
} else {
|
|
28
|
+
if (items.length === 0) {
|
|
29
|
+
writeLine(ctx.stdout, ui.info("no scores"));
|
|
30
|
+
} else {
|
|
31
|
+
printSection(ctx.stdout, `Agent scores · ${agentId}`);
|
|
32
|
+
printTable(ctx.stdout, [
|
|
33
|
+
{ key: "score_id", label: "SCORE_ID" },
|
|
34
|
+
{ key: "run_id", label: "RUN_ID" },
|
|
35
|
+
{ key: "score", label: "SCORE" },
|
|
36
|
+
{ key: "scored_at", label: "SCORED_AT" },
|
|
37
|
+
], items);
|
|
38
|
+
}
|
|
39
|
+
if (res.has_more && res.next_cursor) {
|
|
40
|
+
writeLine(ctx.stdout, ui.dim(`next: --starting-after ${res.next_cursor}`));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
function createCoreOpsHandlers(ctx, workspaces, deps) {
|
|
2
|
+
const {
|
|
3
|
+
apiHeaders,
|
|
4
|
+
parseFlags,
|
|
5
|
+
printJson,
|
|
6
|
+
request,
|
|
7
|
+
ui,
|
|
8
|
+
writeLine,
|
|
9
|
+
} = deps;
|
|
10
|
+
|
|
11
|
+
async function commandDoctor() {
|
|
12
|
+
const checks = [];
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const healthz = await request(ctx, "/healthz", { method: "GET" });
|
|
16
|
+
checks.push({ name: "healthz", ok: healthz.status === "ok", detail: healthz });
|
|
17
|
+
} catch (err) {
|
|
18
|
+
checks.push({ name: "healthz", ok: false, detail: String(err) });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const readyz = await request(ctx, "/readyz", { method: "GET" });
|
|
23
|
+
checks.push({ name: "readyz", ok: readyz.ready === true, detail: readyz });
|
|
24
|
+
} catch (err) {
|
|
25
|
+
checks.push({ name: "readyz", ok: false, detail: String(err) });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
checks.push({ name: "credentials", ok: Boolean(ctx.token || ctx.apiKey), detail: ctx.token ? "token" : ctx.apiKey ? "api_key" : "missing" });
|
|
29
|
+
checks.push({ name: "workspace", ok: Boolean(workspaces.current_workspace_id), detail: workspaces.current_workspace_id || "missing" });
|
|
30
|
+
|
|
31
|
+
const ok = checks.every((c) => c.ok);
|
|
32
|
+
const report = { ok, api_url: ctx.apiUrl, checks };
|
|
33
|
+
|
|
34
|
+
if (ctx.jsonMode) {
|
|
35
|
+
printJson(ctx.stdout, report);
|
|
36
|
+
} else {
|
|
37
|
+
writeLine(ctx.stdout, ui.head("zombiectl doctor"));
|
|
38
|
+
writeLine(ctx.stdout);
|
|
39
|
+
for (const c of checks) {
|
|
40
|
+
const tag = c.ok ? "[OK]" : "[FAIL]";
|
|
41
|
+
writeLine(ctx.stdout, c.ok ? ui.ok(`${tag} ${c.name}`) : ui.err(`${tag} ${c.name}`));
|
|
42
|
+
}
|
|
43
|
+
writeLine(ctx.stdout);
|
|
44
|
+
const passed = checks.filter((c) => c.ok).length;
|
|
45
|
+
if (ok) {
|
|
46
|
+
writeLine(ctx.stdout, ui.ok(`All checks passed.`));
|
|
47
|
+
} else {
|
|
48
|
+
writeLine(ctx.stdout, ui.err(`${passed}/${checks.length} checks passed`));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return ok ? 0 : 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function commandSkillSecret(args) {
|
|
55
|
+
const action = args[0];
|
|
56
|
+
const parsed = parseFlags(args.slice(1));
|
|
57
|
+
const workspaceId = parsed.options["workspace-id"] || workspaces.current_workspace_id;
|
|
58
|
+
const skillRef = parsed.options["skill-ref"];
|
|
59
|
+
const key = parsed.options.key;
|
|
60
|
+
|
|
61
|
+
if (!workspaceId || !skillRef || !key) {
|
|
62
|
+
writeLine(ctx.stderr, ui.err("skill-secret requires --workspace-id --skill-ref --key"));
|
|
63
|
+
return 2;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const route = `/v1/workspaces/${encodeURIComponent(workspaceId)}/skills/${encodeURIComponent(skillRef)}/secrets/${encodeURIComponent(key)}`;
|
|
67
|
+
|
|
68
|
+
if (action === "put") {
|
|
69
|
+
if (!parsed.options.value) {
|
|
70
|
+
writeLine(ctx.stderr, ui.err("skill-secret put requires --value"));
|
|
71
|
+
return 2;
|
|
72
|
+
}
|
|
73
|
+
const body = {
|
|
74
|
+
value: String(parsed.options.value),
|
|
75
|
+
scope: parsed.options.scope || "sandbox",
|
|
76
|
+
meta: {},
|
|
77
|
+
};
|
|
78
|
+
const res = await request(ctx, route, {
|
|
79
|
+
method: "PUT",
|
|
80
|
+
headers: apiHeaders(ctx),
|
|
81
|
+
body: JSON.stringify(body),
|
|
82
|
+
});
|
|
83
|
+
if (ctx.jsonMode) printJson(ctx.stdout, res);
|
|
84
|
+
else writeLine(ctx.stdout, ui.ok("skill secret stored"));
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (action === "delete") {
|
|
89
|
+
const res = await request(ctx, route, {
|
|
90
|
+
method: "DELETE",
|
|
91
|
+
headers: apiHeaders(ctx),
|
|
92
|
+
});
|
|
93
|
+
if (ctx.jsonMode) printJson(ctx.stdout, res);
|
|
94
|
+
else writeLine(ctx.stdout, ui.ok("skill secret deleted"));
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
writeLine(ctx.stderr, ui.err("usage: skill-secret put|delete ..."));
|
|
99
|
+
return 2;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
commandDoctor,
|
|
104
|
+
commandSkillSecret,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { createCoreOpsHandlers };
|