hngr 0.9.0 → 0.10.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 +36 -0
- package/dist/{catalog-CmtREMFr.mjs → catalog-CfpeEevk.mjs} +630 -8
- package/dist/{commands-Bjui8-Nt.mjs → commands-UsF7HECR.mjs} +1 -1
- package/dist/index.mjs +138 -7
- package/dist/{mcp-C-oJ6J5B.mjs → mcp-BKyfjqcx.mjs} +1 -1
- package/dist/{scope-8BZjgIXy.mjs → scope-DRg-ddFn.mjs} +16 -1
- package/dist/{skills-CqHGxo_M.mjs → skills-gJqYAVg2.mjs} +260 -5
- package/dist/sync-D25awtDG.mjs +278 -0
- package/package.json +1 -1
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { C as HngrError, g as resolveConnection, n as apiGet, r as apiRequest } from "./http-CLG2jrIE.mjs";
|
|
2
|
+
// src/commands/sync.ts
|
|
3
|
+
const TERMINAL = /* @__PURE__ */ new Set([
|
|
4
|
+
"succeeded",
|
|
5
|
+
"partial",
|
|
6
|
+
"failed",
|
|
7
|
+
"interrupted",
|
|
8
|
+
"skipped_overlap"
|
|
9
|
+
]);
|
|
10
|
+
const POLL_INTERVAL_MS = 2e3;
|
|
11
|
+
const WAIT_TIMEOUT_MS = 6e5;
|
|
12
|
+
function usage(title, detail) {
|
|
13
|
+
return new HngrError({
|
|
14
|
+
type: "usage",
|
|
15
|
+
title,
|
|
16
|
+
detail
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function stringFlag(argv, name) {
|
|
20
|
+
const value = argv[name];
|
|
21
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
22
|
+
}
|
|
23
|
+
function boolFlag(argv, name) {
|
|
24
|
+
return argv[name] === true;
|
|
25
|
+
}
|
|
26
|
+
function numberFlag(argv, name) {
|
|
27
|
+
const value = argv[name];
|
|
28
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
29
|
+
}
|
|
30
|
+
function stringArrayFlag(argv, name) {
|
|
31
|
+
const value = argv[name];
|
|
32
|
+
if (value === void 0) return [];
|
|
33
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
34
|
+
}
|
|
35
|
+
function parseInterval(raw) {
|
|
36
|
+
if (raw === void 0) return void 0;
|
|
37
|
+
if (/^\d+$/.test(raw)) return Number(raw);
|
|
38
|
+
const match = /^(\d+)(m|h)$/.exec(raw);
|
|
39
|
+
if (!match) throw usage("--interval must be minutes or a value such as 5m, 15m, 1h, 6h or 24h");
|
|
40
|
+
const amount = Number(match[1]);
|
|
41
|
+
return match[2] === "h" ? amount * 60 : amount;
|
|
42
|
+
}
|
|
43
|
+
function parseVisibility(raw) {
|
|
44
|
+
if (raw === void 0) return void 0;
|
|
45
|
+
if (raw !== "public" && raw !== "internal" && raw !== "private") throw usage("--visibility must be public, internal or private");
|
|
46
|
+
return raw;
|
|
47
|
+
}
|
|
48
|
+
async function connection(ctx) {
|
|
49
|
+
return resolveConnection(ctx.flags, ctx.env);
|
|
50
|
+
}
|
|
51
|
+
async function requestJSON(conn, method, path, body) {
|
|
52
|
+
const response = await apiRequest(conn, {
|
|
53
|
+
method,
|
|
54
|
+
path,
|
|
55
|
+
body
|
|
56
|
+
});
|
|
57
|
+
if (response.status === 204) return void 0;
|
|
58
|
+
return await response.json();
|
|
59
|
+
}
|
|
60
|
+
function taskLine(task) {
|
|
61
|
+
const last = task.last_run ? `${task.last_run.status} ${task.last_run.started_at}` : "-";
|
|
62
|
+
const next = task.next_run_at ?? "-";
|
|
63
|
+
return [
|
|
64
|
+
task.id,
|
|
65
|
+
`${task.namespace} (${task.namespace_kind})`,
|
|
66
|
+
`${task.interval_minutes}m`,
|
|
67
|
+
task.enabled ? "enabled" : "disabled",
|
|
68
|
+
task.default_visibility,
|
|
69
|
+
last,
|
|
70
|
+
next,
|
|
71
|
+
task.degraded ? "DEGRADED" : "",
|
|
72
|
+
task.deleted_at ? "deleted" : ""
|
|
73
|
+
].join(" ");
|
|
74
|
+
}
|
|
75
|
+
function printTask(ctx, task) {
|
|
76
|
+
if (ctx.flags.json) {
|
|
77
|
+
ctx.writePayload(task);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
ctx.io.stdout.write(`${taskLine(task)}\n`);
|
|
81
|
+
ctx.io.stdout.write(`agent: ${task.agent.username}\n`);
|
|
82
|
+
const counts = Object.entries(task.item_counts).map(([state, count]) => `${state}=${count}`).join(" ");
|
|
83
|
+
if (counts) ctx.io.stdout.write(`items: ${counts}\n`);
|
|
84
|
+
if (task.last_run) ctx.io.stdout.write(`last run: ${runSummaryLine(task.last_run)}\n`);
|
|
85
|
+
}
|
|
86
|
+
function runSummaryLine(run) {
|
|
87
|
+
const counts = run.counts;
|
|
88
|
+
return [
|
|
89
|
+
run.id,
|
|
90
|
+
run.trigger,
|
|
91
|
+
run.dry_run ? "dry-run" : "apply",
|
|
92
|
+
run.status,
|
|
93
|
+
run.started_at,
|
|
94
|
+
`added=${counts.added} updated=${counts.updated} skipped=${counts.skipped} conflict=${counts.conflict} missing=${counts.missing} failed=${counts.failed} deleted_locally=${counts.deleted_locally}`
|
|
95
|
+
].join(" ");
|
|
96
|
+
}
|
|
97
|
+
async function list(ctx, argv) {
|
|
98
|
+
const conn = await connection(ctx);
|
|
99
|
+
const list = await apiGet(conn, "/api/v1/sync-tasks", {
|
|
100
|
+
include_deleted: boolFlag(argv, "includeDeleted") || boolFlag(argv, "include-deleted") || void 0,
|
|
101
|
+
limit: numberFlag(argv, "limit")
|
|
102
|
+
});
|
|
103
|
+
if (ctx.flags.json) {
|
|
104
|
+
ctx.writePayload(list);
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
if (list.items.length === 0) {
|
|
108
|
+
ctx.io.stdout.write("no sync tasks\n");
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
ctx.io.stdout.write("id target interval enabled visibility last run next run flags\n");
|
|
112
|
+
for (const task of list.items) ctx.io.stdout.write(`${taskLine(task)}\n`);
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
async function create(ctx, argv) {
|
|
116
|
+
const namespace = stringFlag(argv, "namespace");
|
|
117
|
+
if (namespace === void 0) throw usage("--namespace is required", "Example: hngr sync create --namespace seek");
|
|
118
|
+
const interval = parseInterval(stringFlag(argv, "interval"));
|
|
119
|
+
const visibility = parseVisibility(stringFlag(argv, "visibility"));
|
|
120
|
+
const enable = boolFlag(argv, "enable");
|
|
121
|
+
const task = await requestJSON(await connection(ctx), "POST", "/api/v1/sync-tasks", {
|
|
122
|
+
namespace,
|
|
123
|
+
interval_minutes: interval,
|
|
124
|
+
default_visibility: visibility,
|
|
125
|
+
enabled: enable
|
|
126
|
+
});
|
|
127
|
+
if (task === void 0) throw new HngrError({
|
|
128
|
+
type: "api",
|
|
129
|
+
title: "the server returned no sync task"
|
|
130
|
+
});
|
|
131
|
+
if (enable) ctx.summary("task created enabled; run a dry-run first if you have not reviewed the diff");
|
|
132
|
+
printTask(ctx, task);
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
async function show(ctx, argv) {
|
|
136
|
+
const id = stringFlag(argv, "taskId") ?? stringFlag(argv, "task-id");
|
|
137
|
+
if (id === void 0) throw usage("a task id is required");
|
|
138
|
+
const task = await requestJSON(await connection(ctx), "GET", `/api/v1/sync-tasks/${id}`);
|
|
139
|
+
if (task === void 0) throw new HngrError({
|
|
140
|
+
type: "api",
|
|
141
|
+
title: "the server returned no sync task"
|
|
142
|
+
});
|
|
143
|
+
printTask(ctx, task);
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
async function update(ctx, argv) {
|
|
147
|
+
const id = stringFlag(argv, "taskId") ?? stringFlag(argv, "task-id");
|
|
148
|
+
if (id === void 0) throw usage("a task id is required");
|
|
149
|
+
const body = {};
|
|
150
|
+
if (boolFlag(argv, "enable")) body.enabled = true;
|
|
151
|
+
if (boolFlag(argv, "disable")) body.enabled = false;
|
|
152
|
+
const interval = parseInterval(stringFlag(argv, "interval"));
|
|
153
|
+
if (interval !== void 0) body.interval_minutes = interval;
|
|
154
|
+
const visibility = parseVisibility(stringFlag(argv, "visibility"));
|
|
155
|
+
if (visibility !== void 0) body.default_visibility = visibility;
|
|
156
|
+
if (Object.keys(body).length === 0) throw usage("pass --enable, --disable, --interval or --visibility");
|
|
157
|
+
const task = await requestJSON(await connection(ctx), "PATCH", `/api/v1/sync-tasks/${id}`, body);
|
|
158
|
+
if (task === void 0) throw new HngrError({
|
|
159
|
+
type: "api",
|
|
160
|
+
title: "the server returned no sync task"
|
|
161
|
+
});
|
|
162
|
+
printTask(ctx, task);
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
async function remove(ctx, argv) {
|
|
166
|
+
const id = stringFlag(argv, "taskId") ?? stringFlag(argv, "task-id");
|
|
167
|
+
if (id === void 0) throw usage("a task id is required");
|
|
168
|
+
if (!ctx.flags.force) {
|
|
169
|
+
const confirm = ctx.io.prompts?.confirm;
|
|
170
|
+
if (!ctx.io.stdin.isTTY || !confirm) throw usage("refusing to delete without --force in a non-interactive session", "The task is soft-deleted and its Capabilities are kept.");
|
|
171
|
+
if (!await confirm(`Delete sync task ${id}? Capabilities are not deleted.`, false)) return 0;
|
|
172
|
+
}
|
|
173
|
+
await requestJSON(await connection(ctx), "DELETE", `/api/v1/sync-tasks/${id}`);
|
|
174
|
+
ctx.summary(`deleted sync task ${id}; its Capabilities were kept`);
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
async function run(ctx, argv) {
|
|
178
|
+
const id = stringFlag(argv, "taskId") ?? stringFlag(argv, "task-id");
|
|
179
|
+
if (id === void 0) throw usage("a task id is required");
|
|
180
|
+
const conn = await connection(ctx);
|
|
181
|
+
const summary = await requestJSON(conn, "POST", `/api/v1/sync-tasks/${id}/runs`, { dry_run: ctx.flags.dryRun });
|
|
182
|
+
if (summary === void 0) throw new HngrError({
|
|
183
|
+
type: "api",
|
|
184
|
+
title: "the server returned no sync run"
|
|
185
|
+
});
|
|
186
|
+
if (!boolFlag(argv, "wait")) {
|
|
187
|
+
if (ctx.flags.json) ctx.writePayload(summary);
|
|
188
|
+
else ctx.io.stdout.write(`${runSummaryLine(summary)}\n`);
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
ctx.progress(`run ${summary.id} started; waiting for completion`);
|
|
192
|
+
const final = await waitForRun(ctx, conn, id, summary.id);
|
|
193
|
+
if (ctx.flags.json) ctx.writePayload(final);
|
|
194
|
+
else {
|
|
195
|
+
ctx.io.stdout.write(`${runSummaryLine(final)}\n`);
|
|
196
|
+
for (const item of final.items) ctx.io.stdout.write(`${item.status}\t${item.upstream_name}\t${item.name ?? ""}\t${item.version ?? ""}\t${item.error ?? ""}\n`);
|
|
197
|
+
}
|
|
198
|
+
return final.status === "failed" ? 1 : 0;
|
|
199
|
+
}
|
|
200
|
+
async function waitForRun(ctx, conn, taskID, runID) {
|
|
201
|
+
const sleep = ctx.io.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
202
|
+
const now = ctx.io.now ?? Date.now;
|
|
203
|
+
const started = now();
|
|
204
|
+
for (;;) {
|
|
205
|
+
const run = await requestJSON(conn, "GET", `/api/v1/sync-tasks/${taskID}/runs/${runID}`);
|
|
206
|
+
if (run === void 0) throw new HngrError({
|
|
207
|
+
type: "api",
|
|
208
|
+
title: "the server returned no sync run"
|
|
209
|
+
});
|
|
210
|
+
if (TERMINAL.has(run.status)) return run;
|
|
211
|
+
if (now() - started > WAIT_TIMEOUT_MS) throw new HngrError({
|
|
212
|
+
type: "network",
|
|
213
|
+
title: "timed out waiting for the sync run"
|
|
214
|
+
});
|
|
215
|
+
await sleep(POLL_INTERVAL_MS);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function runs(ctx, argv) {
|
|
219
|
+
const id = stringFlag(argv, "taskId") ?? stringFlag(argv, "task-id");
|
|
220
|
+
if (id === void 0) throw usage("a task id is required");
|
|
221
|
+
const payload = await requestJSON(await connection(ctx), "GET", `/api/v1/sync-tasks/${id}/runs?limit=${numberFlag(argv, "limit") ?? 20}`);
|
|
222
|
+
if (payload === void 0) throw new HngrError({
|
|
223
|
+
type: "api",
|
|
224
|
+
title: "the server returned no runs"
|
|
225
|
+
});
|
|
226
|
+
if (ctx.flags.json) {
|
|
227
|
+
ctx.writePayload(payload);
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
if (payload.items.length === 0) {
|
|
231
|
+
ctx.io.stdout.write("no runs\n");
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
234
|
+
for (const summary of payload.items) ctx.io.stdout.write(`${runSummaryLine(summary)}\n`);
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
async function runShow(ctx, argv) {
|
|
238
|
+
const id = stringFlag(argv, "taskId") ?? stringFlag(argv, "task-id");
|
|
239
|
+
const runID = stringFlag(argv, "runId") ?? stringFlag(argv, "run-id");
|
|
240
|
+
if (id === void 0 || runID === void 0) throw usage("a task id and a run id are required");
|
|
241
|
+
const run = await requestJSON(await connection(ctx), "GET", `/api/v1/sync-tasks/${id}/runs/${runID}`);
|
|
242
|
+
if (run === void 0) throw new HngrError({
|
|
243
|
+
type: "api",
|
|
244
|
+
title: "the server returned no sync run"
|
|
245
|
+
});
|
|
246
|
+
if (ctx.flags.json) {
|
|
247
|
+
ctx.writePayload(run);
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
ctx.io.stdout.write(`${runSummaryLine(run)}\n`);
|
|
251
|
+
if (run.error_summary) ctx.io.stdout.write(`error: ${run.error_summary}\n`);
|
|
252
|
+
for (const item of run.items) ctx.io.stdout.write(`${item.status}\t${item.upstream_name}\t${item.name ?? ""}\t${item.version ?? ""}\t${item.error ?? ""}\n`);
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
async function items(ctx, argv) {
|
|
256
|
+
const id = stringFlag(argv, "taskId") ?? stringFlag(argv, "task-id");
|
|
257
|
+
if (id === void 0) throw usage("a task id is required");
|
|
258
|
+
const states = stringArrayFlag(argv, "state");
|
|
259
|
+
const limit = numberFlag(argv, "limit") ?? 50;
|
|
260
|
+
const query = states.map((state) => `state=${encodeURIComponent(state)}`).join("&");
|
|
261
|
+
const path = `/api/v1/sync-tasks/${id}/items?limit=${limit}${query ? `&${query}` : ""}`;
|
|
262
|
+
const payload = await requestJSON(await connection(ctx), "GET", path);
|
|
263
|
+
if (payload === void 0) throw new HngrError({
|
|
264
|
+
type: "api",
|
|
265
|
+
title: "the server returned no items"
|
|
266
|
+
});
|
|
267
|
+
if (ctx.flags.json) {
|
|
268
|
+
ctx.writePayload(payload);
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
if (payload.items.length === 0) {
|
|
272
|
+
ctx.io.stdout.write("no items\n");
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
for (const item of payload.items) ctx.io.stdout.write(`${item.state}\t${item.upstream_name}\t${item.name ?? ""}\t${item.path ?? ""}\t${item.last_synced_at ?? "-"}\t${item.error ?? ""}\n`);
|
|
276
|
+
return 0;
|
|
277
|
+
}
|
|
278
|
+
export { create, items, list, remove, run, runShow, runs, show, update };
|