@swarmcraftai/cli 0.1.0 → 0.1.5
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 +10 -8
- package/dist/main.cjs +511 -112
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# SwarmCraft CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
SwarmCraft CLI prepares a local repository for a SwarmCraft project board and runs packet-based agent workflows from the terminal.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use it to authenticate with SwarmCraft, list available projects, seed workspace guidance files, materialize ticket packets, run one-shot implementation passes, resume or inspect runs, and create support bundles.
|
|
6
6
|
|
|
7
7
|
## Package Shape
|
|
8
8
|
|
|
@@ -57,17 +57,19 @@ Core commands:
|
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
swarmcraft auth login --email <email>
|
|
60
|
-
swarmcraft
|
|
61
|
-
swarmcraft
|
|
62
|
-
swarmcraft
|
|
60
|
+
swarmcraft delegation start --user <customer-email-or-id> --reason <text> --expires-in-minutes 600
|
|
61
|
+
swarmcraft projects list --delegation <delegation-id>
|
|
62
|
+
swarmcraft init --project <project-id> --workspace <path> --delegation <delegation-id>
|
|
63
|
+
swarmcraft one-shot --project <project-id> --workspace <path> --agent-provider codex --delegation <delegation-id>
|
|
63
64
|
swarmcraft one-shot --project <project-id> --workspace <path> --agent-provider codex --run-id <known-run-id>
|
|
64
|
-
swarmcraft resume --run <run-id> --workspace <path>
|
|
65
|
-
swarmcraft status --run <run-id> --workspace <path>
|
|
65
|
+
swarmcraft resume --run <run-id> --workspace <path> --delegation <delegation-id>
|
|
66
|
+
swarmcraft status --run <run-id> --workspace <path> --delegation <delegation-id>
|
|
67
|
+
swarmcraft delegation end --delegation <delegation-id>
|
|
66
68
|
```
|
|
67
69
|
|
|
68
70
|
`swarmcraft init` validates the workspace git state, seeds the shared SwarmCraft repo guidance files, materializes non-done ticket packets, links those packets back to SwarmCraft, and prints the next commands.
|
|
69
71
|
|
|
70
|
-
`swarmcraft one-shot` bootstraps the workspace, runs doing
|
|
72
|
+
`swarmcraft one-shot` bootstraps the workspace, runs the doing lane through the selected agent provider, syncs packet updates back to SwarmCraft, writes `.swarmcraft/runs/<run-id>/manifest.json`, streams progress to the terminal, writes agent logs under `.swarmcraft/runs/<run-id>/logs/`, and moves accepted work to `done` by default. Customer one-shots should normally pair this with `--commit-policy done` so each completed ticket creates a local commit. Use `--checking on` or `--reviewing on` only for internal validation runs that deliberately need those extra lanes.
|
|
71
73
|
|
|
72
74
|
Useful safety and support commands:
|
|
73
75
|
|
package/dist/main.cjs
CHANGED
|
@@ -9,6 +9,10 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
|
9
9
|
var __commonJS = (cb, mod) => function __require() {
|
|
10
10
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
11
|
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
12
16
|
var __copyProps = (to, from, except, desc) => {
|
|
13
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
18
|
for (let key of __getOwnPropNames(from))
|
|
@@ -25,6 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
29
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
30
|
mod
|
|
27
31
|
));
|
|
32
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
33
|
|
|
29
34
|
// ../../packages/packet-workflow/dist/assistantActions.js
|
|
30
35
|
var require_assistantActions = __commonJS({
|
|
@@ -2922,7 +2927,8 @@ var require_seedProjectFiles = __commonJS({
|
|
|
2922
2927
|
const existingEndIndex = existingContents.indexOf(renderProjectGuide_1.MANAGED_SECTION_END);
|
|
2923
2928
|
if (existingStartIndex >= 0 && existingEndIndex >= 0 && existingEndIndex > existingStartIndex) {
|
|
2924
2929
|
const replacementEndIndex = existingEndIndex + renderProjectGuide_1.MANAGED_SECTION_END.length;
|
|
2925
|
-
const
|
|
2930
|
+
const afterManagedSection = existingContents.slice(replacementEndIndex).replace(/^\n+/, "\n");
|
|
2931
|
+
const updatedContents = `${existingContents.slice(0, existingStartIndex)}${managedSection}${afterManagedSection}`;
|
|
2926
2932
|
return normalizeTrailingNewline(updatedContents);
|
|
2927
2933
|
}
|
|
2928
2934
|
const separator = existingContents.endsWith("\n\n") ? "" : existingContents.endsWith("\n") ? "\n" : "\n\n";
|
|
@@ -2971,15 +2977,66 @@ var require_dist2 = __commonJS({
|
|
|
2971
2977
|
});
|
|
2972
2978
|
|
|
2973
2979
|
// src/main.ts
|
|
2980
|
+
var main_exports = {};
|
|
2981
|
+
__export(main_exports, {
|
|
2982
|
+
findCommand: () => findCommand,
|
|
2983
|
+
main: () => main,
|
|
2984
|
+
parseArgs: () => parseArgs
|
|
2985
|
+
});
|
|
2986
|
+
module.exports = __toCommonJS(main_exports);
|
|
2974
2987
|
var import_promises4 = require("node:fs/promises");
|
|
2975
2988
|
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
2976
2989
|
var import_promises5 = require("node:readline/promises");
|
|
2977
2990
|
var import_node_process = require("node:process");
|
|
2978
2991
|
var import_packet_workflow3 = __toESM(require_dist(), 1);
|
|
2979
2992
|
|
|
2993
|
+
// package.json
|
|
2994
|
+
var package_default = {
|
|
2995
|
+
name: "@swarmcraftai/cli",
|
|
2996
|
+
version: "0.1.5",
|
|
2997
|
+
description: "Prepare repositories and run packet-based SwarmCraft agent workflows from the terminal.",
|
|
2998
|
+
license: "SEE LICENSE IN LICENSE.txt",
|
|
2999
|
+
type: "module",
|
|
3000
|
+
homepage: "https://swarmcraft.ai",
|
|
3001
|
+
bugs: {
|
|
3002
|
+
url: "https://swarmcraft.ai/login?next=%2Fapp%2Fsupport"
|
|
3003
|
+
},
|
|
3004
|
+
bin: {
|
|
3005
|
+
swarmcraft: "dist/main.cjs"
|
|
3006
|
+
},
|
|
3007
|
+
files: [
|
|
3008
|
+
"dist",
|
|
3009
|
+
"README.md",
|
|
3010
|
+
"LICENSE.txt"
|
|
3011
|
+
],
|
|
3012
|
+
publishConfig: {
|
|
3013
|
+
access: "public"
|
|
3014
|
+
},
|
|
3015
|
+
engines: {
|
|
3016
|
+
node: ">=20.19.0"
|
|
3017
|
+
},
|
|
3018
|
+
scripts: {
|
|
3019
|
+
clean: `node -e "require('node:fs').rmSync('dist',{recursive:true,force:true})"`,
|
|
3020
|
+
typecheck: "tsc --noEmit -p tsconfig.json",
|
|
3021
|
+
build: "pnpm --filter @swarmcraft/repo-seed build && pnpm run typecheck && pnpm run clean && esbuild src/main.ts --bundle --platform=node --format=cjs --target=node20.19 --outfile=dist/main.cjs",
|
|
3022
|
+
check: "pnpm --filter @swarmcraft/repo-seed build && pnpm run typecheck && vitest run && pnpm run build && node ./dist/main.cjs --help",
|
|
3023
|
+
dev: "pnpm run build && node ./dist/main.cjs",
|
|
3024
|
+
"release:smoke": "pnpm run build && node ./scripts/smoke-packed-cli.mjs"
|
|
3025
|
+
},
|
|
3026
|
+
devDependencies: {
|
|
3027
|
+
"@swarmcraft/packet-workflow": "workspace:*",
|
|
3028
|
+
"@swarmcraft/repo-seed": "workspace:*",
|
|
3029
|
+
"@types/node": "^24.10.1",
|
|
3030
|
+
esbuild: "^0.25.11",
|
|
3031
|
+
typescript: "^5.9.3",
|
|
3032
|
+
vitest: "^3.2.4"
|
|
3033
|
+
}
|
|
3034
|
+
};
|
|
3035
|
+
|
|
2980
3036
|
// src/apiClient.ts
|
|
2981
3037
|
var API_REQUEST_TIMEOUT_MS = 15e3;
|
|
2982
3038
|
var CLIENT_SOURCE_HEADER = "X-SwarmCraft-Client";
|
|
3039
|
+
var DELEGATION_HEADER = "X-SwarmCraft-Delegation";
|
|
2983
3040
|
var REFRESH_TOKEN_COOKIE_NAME = "swarmcraft_refresh_token";
|
|
2984
3041
|
var SwarmCraftApiError = class extends Error {
|
|
2985
3042
|
status;
|
|
@@ -3031,56 +3088,88 @@ var SwarmCraftClient = class {
|
|
|
3031
3088
|
accessToken
|
|
3032
3089
|
);
|
|
3033
3090
|
}
|
|
3034
|
-
|
|
3035
|
-
return this.requestJson(
|
|
3091
|
+
startDelegation(accessToken, request) {
|
|
3092
|
+
return this.requestJson(
|
|
3093
|
+
"/support/delegations",
|
|
3094
|
+
{
|
|
3095
|
+
method: "POST",
|
|
3096
|
+
body: JSON.stringify(request)
|
|
3097
|
+
},
|
|
3098
|
+
accessToken
|
|
3099
|
+
);
|
|
3100
|
+
}
|
|
3101
|
+
loadDelegation(accessToken, delegationId) {
|
|
3102
|
+
return this.requestJson(
|
|
3103
|
+
`/support/delegations/${encodeURIComponent(delegationId)}`,
|
|
3104
|
+
{ method: "GET" },
|
|
3105
|
+
accessToken
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3108
|
+
endDelegation(accessToken, delegationId) {
|
|
3109
|
+
return this.requestJson(
|
|
3110
|
+
`/support/delegations/${encodeURIComponent(delegationId)}`,
|
|
3111
|
+
{ method: "DELETE" },
|
|
3112
|
+
accessToken
|
|
3113
|
+
);
|
|
3114
|
+
}
|
|
3115
|
+
listProjects(accessToken, delegationId) {
|
|
3116
|
+
return this.requestJson("/projects", { method: "GET" }, accessToken, void 0, delegationId);
|
|
3036
3117
|
}
|
|
3037
|
-
loadProject(projectId, accessToken) {
|
|
3038
|
-
return this.requestJson(`/projects/${encodeURIComponent(projectId)}`, { method: "GET" }, accessToken);
|
|
3118
|
+
loadProject(projectId, accessToken, delegationId) {
|
|
3119
|
+
return this.requestJson(`/projects/${encodeURIComponent(projectId)}`, { method: "GET" }, accessToken, void 0, delegationId);
|
|
3039
3120
|
}
|
|
3040
|
-
loadBoard(projectId, accessToken) {
|
|
3041
|
-
return this.requestJson(`/projects/${encodeURIComponent(projectId)}/board`, { method: "GET" }, accessToken);
|
|
3121
|
+
loadBoard(projectId, accessToken, delegationId) {
|
|
3122
|
+
return this.requestJson(`/projects/${encodeURIComponent(projectId)}/board`, { method: "GET" }, accessToken, void 0, delegationId);
|
|
3042
3123
|
}
|
|
3043
|
-
upsertTicketPacketLink(projectId, ticketId, request, accessToken) {
|
|
3124
|
+
upsertTicketPacketLink(projectId, ticketId, request, accessToken, delegationId) {
|
|
3044
3125
|
return this.requestJson(
|
|
3045
3126
|
`/projects/${encodeURIComponent(projectId)}/tickets/${encodeURIComponent(ticketId)}/packet-link`,
|
|
3046
3127
|
{
|
|
3047
3128
|
method: "PUT",
|
|
3048
3129
|
body: JSON.stringify(request)
|
|
3049
3130
|
},
|
|
3050
|
-
accessToken
|
|
3131
|
+
accessToken,
|
|
3132
|
+
void 0,
|
|
3133
|
+
delegationId
|
|
3051
3134
|
);
|
|
3052
3135
|
}
|
|
3053
|
-
moveTicket(projectId, ticketId, request, accessToken) {
|
|
3136
|
+
moveTicket(projectId, ticketId, request, accessToken, delegationId) {
|
|
3054
3137
|
return this.requestJson(
|
|
3055
3138
|
`/projects/${encodeURIComponent(projectId)}/tickets/${encodeURIComponent(ticketId)}/move`,
|
|
3056
3139
|
{
|
|
3057
3140
|
method: "POST",
|
|
3058
3141
|
body: JSON.stringify(request)
|
|
3059
3142
|
},
|
|
3060
|
-
accessToken
|
|
3143
|
+
accessToken,
|
|
3144
|
+
void 0,
|
|
3145
|
+
delegationId
|
|
3061
3146
|
);
|
|
3062
3147
|
}
|
|
3063
|
-
updateTicketNotes(projectId, ticketId, request, accessToken) {
|
|
3148
|
+
updateTicketNotes(projectId, ticketId, request, accessToken, delegationId) {
|
|
3064
3149
|
return this.requestJson(
|
|
3065
3150
|
`/projects/${encodeURIComponent(projectId)}/tickets/${encodeURIComponent(ticketId)}/notes`,
|
|
3066
3151
|
{
|
|
3067
3152
|
method: "PATCH",
|
|
3068
3153
|
body: JSON.stringify(request)
|
|
3069
3154
|
},
|
|
3070
|
-
accessToken
|
|
3155
|
+
accessToken,
|
|
3156
|
+
void 0,
|
|
3157
|
+
delegationId
|
|
3071
3158
|
);
|
|
3072
3159
|
}
|
|
3073
|
-
updateChecklistItem(projectId, ticketId, itemId, request, accessToken) {
|
|
3160
|
+
updateChecklistItem(projectId, ticketId, itemId, request, accessToken, delegationId) {
|
|
3074
3161
|
return this.requestJson(
|
|
3075
3162
|
`/projects/${encodeURIComponent(projectId)}/tickets/${encodeURIComponent(ticketId)}/checklist-items/${encodeURIComponent(itemId)}`,
|
|
3076
3163
|
{
|
|
3077
3164
|
method: "PATCH",
|
|
3078
3165
|
body: JSON.stringify(request)
|
|
3079
3166
|
},
|
|
3080
|
-
accessToken
|
|
3167
|
+
accessToken,
|
|
3168
|
+
void 0,
|
|
3169
|
+
delegationId
|
|
3081
3170
|
);
|
|
3082
3171
|
}
|
|
3083
|
-
async requestJson(path6, init, accessToken, refreshToken) {
|
|
3172
|
+
async requestJson(path6, init, accessToken, refreshToken, delegationId) {
|
|
3084
3173
|
const url = `${this.apiBaseUrl}${path6.startsWith("/") ? path6 : `/${path6}`}`;
|
|
3085
3174
|
const method = (init.method ?? "GET").toUpperCase();
|
|
3086
3175
|
const headers = new Headers(init.headers);
|
|
@@ -3097,6 +3186,9 @@ var SwarmCraftClient = class {
|
|
|
3097
3186
|
if (refreshToken) {
|
|
3098
3187
|
headers.set("Cookie", `${REFRESH_TOKEN_COOKIE_NAME}=${refreshToken}`);
|
|
3099
3188
|
}
|
|
3189
|
+
if (delegationId) {
|
|
3190
|
+
headers.set(DELEGATION_HEADER, delegationId);
|
|
3191
|
+
}
|
|
3100
3192
|
let response;
|
|
3101
3193
|
try {
|
|
3102
3194
|
response = await fetch(url, {
|
|
@@ -3175,6 +3267,7 @@ var import_packet_workflow = __toESM(require_dist(), 1);
|
|
|
3175
3267
|
var import_repo_seed = __toESM(require_dist2(), 1);
|
|
3176
3268
|
|
|
3177
3269
|
// src/workspace.ts
|
|
3270
|
+
var import_node_fs = require("node:fs");
|
|
3178
3271
|
var import_promises = require("node:fs/promises");
|
|
3179
3272
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
3180
3273
|
var import_node_child_process = require("node:child_process");
|
|
@@ -3216,7 +3309,11 @@ async function writeWorkspaceFile(workspacePath, relativePath, contents) {
|
|
|
3216
3309
|
await (0, import_promises.mkdir)(import_node_path.default.dirname(absolutePath), { recursive: true });
|
|
3217
3310
|
await (0, import_promises.writeFile)(absolutePath, contents, "utf8");
|
|
3218
3311
|
}
|
|
3219
|
-
async function runCommand(command, cwd, env = process.env) {
|
|
3312
|
+
async function runCommand(command, cwd, env = process.env, options = {}) {
|
|
3313
|
+
if (options.logPath) {
|
|
3314
|
+
await (0, import_promises.mkdir)(import_node_path.default.dirname(options.logPath), { recursive: true });
|
|
3315
|
+
}
|
|
3316
|
+
const logStream = options.logPath ? (0, import_node_fs.createWriteStream)(options.logPath, { flags: "a" }) : null;
|
|
3220
3317
|
return new Promise((resolve) => {
|
|
3221
3318
|
const child = (0, import_node_child_process.spawn)(command, {
|
|
3222
3319
|
cwd,
|
|
@@ -3226,14 +3323,40 @@ async function runCommand(command, cwd, env = process.env) {
|
|
|
3226
3323
|
});
|
|
3227
3324
|
const stdoutChunks = [];
|
|
3228
3325
|
const stderrChunks = [];
|
|
3229
|
-
child.stdout.on("data", (chunk) =>
|
|
3230
|
-
|
|
3326
|
+
child.stdout.on("data", (chunk) => {
|
|
3327
|
+
stdoutChunks.push(chunk);
|
|
3328
|
+
logStream?.write(chunk);
|
|
3329
|
+
if (options.teeOutput) {
|
|
3330
|
+
process.stdout.write(chunk);
|
|
3331
|
+
}
|
|
3332
|
+
});
|
|
3333
|
+
child.stderr.on("data", (chunk) => {
|
|
3334
|
+
stderrChunks.push(chunk);
|
|
3335
|
+
logStream?.write(chunk);
|
|
3336
|
+
if (options.teeOutput) {
|
|
3337
|
+
process.stderr.write(chunk);
|
|
3338
|
+
}
|
|
3339
|
+
});
|
|
3340
|
+
child.on("error", (error) => {
|
|
3341
|
+
const chunk = Buffer.from(`${error.message}
|
|
3342
|
+
`, "utf8");
|
|
3343
|
+
stderrChunks.push(chunk);
|
|
3344
|
+
logStream?.write(chunk);
|
|
3345
|
+
if (options.teeOutput) {
|
|
3346
|
+
process.stderr.write(chunk);
|
|
3347
|
+
}
|
|
3348
|
+
});
|
|
3231
3349
|
child.on("close", (code) => {
|
|
3232
|
-
|
|
3350
|
+
const result = {
|
|
3233
3351
|
exitCode: code ?? 1,
|
|
3234
3352
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
3235
3353
|
stderr: Buffer.concat(stderrChunks).toString("utf8")
|
|
3236
|
-
}
|
|
3354
|
+
};
|
|
3355
|
+
if (logStream) {
|
|
3356
|
+
logStream.end(() => resolve(result));
|
|
3357
|
+
return;
|
|
3358
|
+
}
|
|
3359
|
+
resolve(result);
|
|
3237
3360
|
});
|
|
3238
3361
|
});
|
|
3239
3362
|
}
|
|
@@ -3258,11 +3381,11 @@ async function runGit(args, cwd) {
|
|
|
3258
3381
|
// src/bootstrap.ts
|
|
3259
3382
|
async function runBootstrap(client, accessToken, options) {
|
|
3260
3383
|
const workspace = await validateWorkspace(options.workspacePath);
|
|
3261
|
-
const project = await client.loadProject(options.projectId, accessToken);
|
|
3384
|
+
const project = await client.loadProject(options.projectId, accessToken, options.delegationId);
|
|
3262
3385
|
if (project.boardState !== "UNLOCKED") {
|
|
3263
3386
|
throw new Error(`Project ${project.id} is ${project.boardState}; unlock the board before bootstrapping a workspace.`);
|
|
3264
3387
|
}
|
|
3265
|
-
const board = await client.loadBoard(project.id, accessToken);
|
|
3388
|
+
const board = await client.loadBoard(project.id, accessToken, options.delegationId);
|
|
3266
3389
|
const seedFilesWritten = await seedWorkspace(project, workspace.workspacePath);
|
|
3267
3390
|
const selectedTickets = selectBootstrapTickets(board, options);
|
|
3268
3391
|
const packetFilesCreated = [];
|
|
@@ -3312,14 +3435,17 @@ async function runBootstrap(client, accessToken, options) {
|
|
|
3312
3435
|
await writeWorkspaceFile(workspace.workspacePath, relativePacketPath, packetContents);
|
|
3313
3436
|
packetFilesCreated.push(relativePacketPath);
|
|
3314
3437
|
}
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3438
|
+
const packetLinkMatches = ticket.packetLink && ticket.packetLink.workspaceFolder === workspace.workspaceFolderName && ticket.packetLink.packetFilePath === relativePacketPath;
|
|
3439
|
+
if (!packetLinkMatches) {
|
|
3440
|
+
await client.upsertTicketPacketLink(project.id, ticket.id, {
|
|
3441
|
+
workspaceFolder: workspace.workspaceFolderName,
|
|
3442
|
+
packetFilePath: relativePacketPath,
|
|
3443
|
+
expectedRevision: ticket.revision,
|
|
3444
|
+
lastSyncedTicketRevision: ticket.revision,
|
|
3445
|
+
lastSyncedAt: syncedAt
|
|
3446
|
+
}, accessToken, options.delegationId);
|
|
3447
|
+
linkedPackets.push(relativePacketPath);
|
|
3448
|
+
}
|
|
3323
3449
|
}
|
|
3324
3450
|
return {
|
|
3325
3451
|
projectTitle: project.title || project.projectTitle || project.id,
|
|
@@ -3331,7 +3457,7 @@ async function runBootstrap(client, accessToken, options) {
|
|
|
3331
3457
|
linkedPackets,
|
|
3332
3458
|
skippedTickets,
|
|
3333
3459
|
nextCommands: [
|
|
3334
|
-
`swarmcraft one-shot --project ${project.id} --workspace ${workspace.workspacePath} --agent-provider codex`,
|
|
3460
|
+
`swarmcraft one-shot --project ${project.id} --workspace ${workspace.workspacePath} --agent-provider codex${options.delegationId ? ` --delegation ${options.delegationId}` : ""}`,
|
|
3335
3461
|
"Review generated files under .swarmcraft/, .github/, .agents/, and .codex/ before running agents."
|
|
3336
3462
|
]
|
|
3337
3463
|
};
|
|
@@ -3367,21 +3493,28 @@ async function seedWorkspace(project, workspacePath) {
|
|
|
3367
3493
|
var import_promises2 = require("node:fs/promises");
|
|
3368
3494
|
var import_node_path3 = __toESM(require("node:path"), 1);
|
|
3369
3495
|
var import_packet_workflow2 = __toESM(require_dist(), 1);
|
|
3370
|
-
async function runOneShot(client,
|
|
3496
|
+
async function runOneShot(client, accessTokenProvider, options) {
|
|
3371
3497
|
const workspace = await validateWorkspace(options.workspacePath);
|
|
3498
|
+
const getAccessToken = () => resolveAccessToken(accessTokenProvider);
|
|
3372
3499
|
if (workspace.dirtyFileCount > 0 && !options.allowDirty) {
|
|
3373
3500
|
throw new Error(`Workspace has ${workspace.dirtyFileCount} changed file(s). Commit, stash, or rerun with --allow-dirty before one-shot.`);
|
|
3374
3501
|
}
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3502
|
+
if (options.resumeRunId) {
|
|
3503
|
+
printProgress("Resume detected; using existing packet files without re-running bootstrap.");
|
|
3504
|
+
} else {
|
|
3505
|
+
await runBootstrap(client, await getAccessToken(), {
|
|
3506
|
+
projectId: options.projectId,
|
|
3507
|
+
workspacePath: workspace.workspacePath,
|
|
3508
|
+
ticketIds: options.ticketIds,
|
|
3509
|
+
ticketLimit: options.ticketLimit,
|
|
3510
|
+
delegationId: options.delegation?.id ?? null
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
const project = await client.loadProject(options.projectId, await getAccessToken(), options.delegation?.id ?? null);
|
|
3514
|
+
let board = await client.loadBoard(project.id, await getAccessToken(), options.delegation?.id ?? null);
|
|
3383
3515
|
const runId = options.resumeRunId ?? options.runId ?? createRunId();
|
|
3384
3516
|
const manifestPath = resolveManifestPath(workspace.workspacePath, runId);
|
|
3517
|
+
const selectedTickets = selectTickets(board, options);
|
|
3385
3518
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3386
3519
|
const manifest = await loadOrCreateManifest(manifestPath, {
|
|
3387
3520
|
generator: "swarmcraft-cli",
|
|
@@ -3404,6 +3537,9 @@ async function runOneShot(client, accessToken, options) {
|
|
|
3404
3537
|
}
|
|
3405
3538
|
});
|
|
3406
3539
|
await persistManifest(manifestPath, manifest);
|
|
3540
|
+
printProgress(`Run ${runId} started for ${project.title || project.projectTitle || project.id}.`);
|
|
3541
|
+
printProgress(`Manifest: ${manifestPath}`);
|
|
3542
|
+
printProgress(`Selected tickets: ${selectedTickets.length}.`);
|
|
3407
3543
|
let agentRequests = manifest.tickets.flatMap((ticket) => ticket.stages).length;
|
|
3408
3544
|
const completedTicketIds = new Set(
|
|
3409
3545
|
manifest.tickets.filter((ticket) => ["done", "manual-review-required"].includes(ticket.outcome)).map((ticket) => ticket.ticketId)
|
|
@@ -3412,10 +3548,13 @@ async function runOneShot(client, accessToken, options) {
|
|
|
3412
3548
|
manifest.tickets = manifest.tickets.filter((ticket) => completedTicketIds.has(ticket.ticketId));
|
|
3413
3549
|
await persistManifest(manifestPath, manifest);
|
|
3414
3550
|
}
|
|
3415
|
-
|
|
3551
|
+
let finalManifestPersisted = false;
|
|
3552
|
+
for (const [ticketIndex, selectedTicket] of selectedTickets.entries()) {
|
|
3416
3553
|
if (completedTicketIds.has(selectedTicket.id)) {
|
|
3554
|
+
printProgress(`Skipping completed ticket ${ticketIndex + 1}/${selectedTickets.length}: ${selectedTicket.title}.`);
|
|
3417
3555
|
continue;
|
|
3418
3556
|
}
|
|
3557
|
+
printProgress(`Starting ticket ${ticketIndex + 1}/${selectedTickets.length}: ${selectedTicket.title}.`);
|
|
3419
3558
|
const ticketOutcome = {
|
|
3420
3559
|
ticketId: selectedTicket.id,
|
|
3421
3560
|
title: selectedTicket.title,
|
|
@@ -3430,7 +3569,7 @@ async function runOneShot(client, accessToken, options) {
|
|
|
3430
3569
|
await persistManifest(manifestPath, manifest);
|
|
3431
3570
|
const result = await processTicket({
|
|
3432
3571
|
client,
|
|
3433
|
-
|
|
3572
|
+
getAccessToken,
|
|
3434
3573
|
project,
|
|
3435
3574
|
board,
|
|
3436
3575
|
ticket: selectedTicket,
|
|
@@ -3448,30 +3587,59 @@ async function runOneShot(client, accessToken, options) {
|
|
|
3448
3587
|
ticketOutcome.outcome = result.outcome;
|
|
3449
3588
|
ticketOutcome.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3450
3589
|
ticketOutcome.elapsedMs = (0, import_packet_workflow2.computeElapsedMs)(ticketOutcome.startedAt, ticketOutcome.finishedAt);
|
|
3590
|
+
printProgress(`Ticket ${ticketIndex + 1}/${selectedTickets.length} finished as ${result.outcome} in ${formatDuration(ticketOutcome.elapsedMs)}.`);
|
|
3591
|
+
if (result.outcome === "done" || result.outcome === "manual-review-required") {
|
|
3592
|
+
completedTicketIds.add(selectedTicket.id);
|
|
3593
|
+
}
|
|
3594
|
+
const willStop = result.outcome.startsWith("blocked");
|
|
3595
|
+
const isFinalSelectedTicket = !hasRemainingUncompletedTickets(selectedTickets, ticketIndex + 1, completedTicketIds);
|
|
3451
3596
|
if (result.outcome === "done" && options.commitPolicy === "done") {
|
|
3452
|
-
ticketOutcome.commitCreated =
|
|
3597
|
+
ticketOutcome.commitCreated = true;
|
|
3598
|
+
}
|
|
3599
|
+
if (willStop || isFinalSelectedTicket) {
|
|
3600
|
+
finalizeManifest(manifest);
|
|
3601
|
+
finalManifestPersisted = true;
|
|
3453
3602
|
}
|
|
3454
3603
|
await persistManifest(manifestPath, manifest);
|
|
3455
|
-
if (result.outcome.
|
|
3604
|
+
if (result.outcome === "done" && options.commitPolicy === "done") {
|
|
3605
|
+
ticketOutcome.commitCreated = await gitAddAndCommit(workspace.workspacePath, `Complete ${selectedTicket.id}: ${selectedTicket.title}`);
|
|
3606
|
+
if (!ticketOutcome.commitCreated) {
|
|
3607
|
+
await persistManifest(manifestPath, manifest);
|
|
3608
|
+
}
|
|
3609
|
+
printProgress(ticketOutcome.commitCreated ? "Committed completed ticket changes." : "No completed ticket changes to commit.");
|
|
3610
|
+
}
|
|
3611
|
+
if (willStop) {
|
|
3612
|
+
printProgress(`Stopping because ticket is ${result.outcome}.`);
|
|
3456
3613
|
break;
|
|
3457
3614
|
}
|
|
3458
3615
|
}
|
|
3616
|
+
if (!finalManifestPersisted) {
|
|
3617
|
+
finalizeManifest(manifest);
|
|
3618
|
+
await persistManifest(manifestPath, manifest);
|
|
3619
|
+
}
|
|
3620
|
+
printProgress(`Run ${runId} finished with status ${manifest.status} in ${formatDuration(manifest.durations.elapsedMs)}.`);
|
|
3621
|
+
return manifest;
|
|
3622
|
+
}
|
|
3623
|
+
function hasRemainingUncompletedTickets(selectedTickets, startIndex, completedTicketIds) {
|
|
3624
|
+
return selectedTickets.slice(startIndex).some((ticket) => !completedTicketIds.has(ticket.id));
|
|
3625
|
+
}
|
|
3626
|
+
function finalizeManifest(manifest) {
|
|
3459
3627
|
manifest.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3460
3628
|
manifest.status = resolveManifestStatus(manifest);
|
|
3461
3629
|
manifest.durations = (0, import_packet_workflow2.summarizeWorkflowRunDurations)(manifest);
|
|
3462
|
-
await persistManifest(manifestPath, manifest);
|
|
3463
|
-
return manifest;
|
|
3464
3630
|
}
|
|
3465
3631
|
async function processTicket(context) {
|
|
3466
3632
|
let currentBoard = context.board;
|
|
3467
3633
|
let currentTicket = requireTicket(currentBoard, context.ticket.id);
|
|
3468
3634
|
if (currentTicket.workflowState === "todo") {
|
|
3469
|
-
|
|
3635
|
+
printProgress("Moving ticket to doing.");
|
|
3636
|
+
currentBoard = await moveTicket(context.client, await context.getAccessToken(), context.project.id, currentTicket, "doing", context.options.delegation?.id ?? null);
|
|
3470
3637
|
currentTicket = requireTicket(currentBoard, context.ticket.id);
|
|
3471
3638
|
}
|
|
3472
3639
|
const packetPath = (0, import_packet_workflow2.resolvePacketFilePath)(context.project, currentTicket, currentTicket.packetLink);
|
|
3473
3640
|
context.ticketOutcome.packetPath = packetPath;
|
|
3474
3641
|
await refreshPacketMetadata(context.workspacePath, packetPath, currentTicket);
|
|
3642
|
+
printProgress(`Packet: ${packetPath}`);
|
|
3475
3643
|
if (currentTicket.workflowState !== "checking" && currentTicket.workflowState !== "reviewing") {
|
|
3476
3644
|
const doResult = await runStageAndSync({ ...context, board: currentBoard, ticket: currentTicket, packetPath, stage: "do" });
|
|
3477
3645
|
currentBoard = doResult.board;
|
|
@@ -3481,7 +3649,8 @@ async function processTicket(context) {
|
|
|
3481
3649
|
return moveAfterAcceptedChecking(context, currentBoard, currentTicket, packetPath);
|
|
3482
3650
|
}
|
|
3483
3651
|
if (currentTicket.workflowState === "doing") {
|
|
3484
|
-
|
|
3652
|
+
printProgress("Moving ticket to checking.");
|
|
3653
|
+
currentBoard = await moveTicket(context.client, await context.getAccessToken(), context.project.id, currentTicket, "checking", context.options.delegation?.id ?? null);
|
|
3485
3654
|
currentTicket = requireTicket(currentBoard, currentTicket.id);
|
|
3486
3655
|
await refreshPacketMetadata(context.workspacePath, packetPath, currentTicket);
|
|
3487
3656
|
}
|
|
@@ -3491,9 +3660,11 @@ async function processTicket(context) {
|
|
|
3491
3660
|
currentTicket = checkResult.ticket;
|
|
3492
3661
|
if (currentTicket.workflowState === "doing") {
|
|
3493
3662
|
if (attempt >= context.options.maxCheckingAttempts) {
|
|
3663
|
+
printProgress("Checking sent the ticket back to doing and retry budget is exhausted.");
|
|
3494
3664
|
return { board: currentBoard, ticket: currentTicket, outcome: "blocked-checking-retries-exhausted" };
|
|
3495
3665
|
}
|
|
3496
|
-
|
|
3666
|
+
printProgress(`Checking requested more work; retrying checking attempt ${attempt + 1}/${context.options.maxCheckingAttempts}.`);
|
|
3667
|
+
currentBoard = await moveTicket(context.client, await context.getAccessToken(), context.project.id, currentTicket, "checking", context.options.delegation?.id ?? null);
|
|
3497
3668
|
currentTicket = requireTicket(currentBoard, currentTicket.id);
|
|
3498
3669
|
await refreshPacketMetadata(context.workspacePath, packetPath, currentTicket);
|
|
3499
3670
|
continue;
|
|
@@ -3523,9 +3694,12 @@ async function runStageAndSync(context) {
|
|
|
3523
3694
|
ticket: context.packetPath,
|
|
3524
3695
|
workspace: context.workspacePath
|
|
3525
3696
|
});
|
|
3697
|
+
const agentRequestNumber = context.getAgentRequests() + 1;
|
|
3698
|
+
const logPath = import_node_path3.default.join(import_node_path3.default.dirname(context.manifestPath), "logs", `${String(agentRequestNumber).padStart(4, "0")}-${context.stage}.log`);
|
|
3526
3699
|
const stageOutcome = {
|
|
3527
3700
|
stage: context.stage,
|
|
3528
3701
|
command,
|
|
3702
|
+
logPath: import_node_path3.default.relative(context.workspacePath, logPath),
|
|
3529
3703
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3530
3704
|
finishedAt: null,
|
|
3531
3705
|
elapsedMs: null,
|
|
@@ -3539,7 +3713,9 @@ async function runStageAndSync(context) {
|
|
|
3539
3713
|
context.ticketOutcome.stages.push(stageOutcome);
|
|
3540
3714
|
await persistManifest(context.manifestPath, context.manifest);
|
|
3541
3715
|
context.incrementAgentRequests();
|
|
3542
|
-
|
|
3716
|
+
printProgress(`Running ${context.stage} stage for ${context.ticket.title}.`);
|
|
3717
|
+
printProgress(`Agent log: ${stageOutcome.logPath}`);
|
|
3718
|
+
const costOutputPath = import_node_path3.default.join(import_node_path3.default.dirname(context.manifestPath), "agent-cost", `${String(agentRequestNumber).padStart(4, "0")}-${context.stage}.json`);
|
|
3543
3719
|
await (0, import_promises2.mkdir)(import_node_path3.default.dirname(costOutputPath), { recursive: true });
|
|
3544
3720
|
const execution = await runCommand(command, context.workspacePath, await buildAgentEnvironment(context.options, {
|
|
3545
3721
|
SWARMCRAFT_STAGE: context.stage,
|
|
@@ -3547,23 +3723,26 @@ async function runStageAndSync(context) {
|
|
|
3547
3723
|
SWARMCRAFT_WORKSPACE: context.workspacePath,
|
|
3548
3724
|
SWARMCRAFT_AGENT_PROVIDER: context.options.agentProvider,
|
|
3549
3725
|
SWARMCRAFT_AGENT_COST_OUTPUT: costOutputPath
|
|
3550
|
-
}));
|
|
3726
|
+
}), { logPath, teeOutput: true });
|
|
3551
3727
|
stageOutcome.exitCode = execution.exitCode;
|
|
3552
3728
|
if (execution.exitCode !== 0) {
|
|
3553
3729
|
stageOutcome.result = "failed";
|
|
3554
3730
|
stageOutcome.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3555
3731
|
stageOutcome.elapsedMs = (0, import_packet_workflow2.computeElapsedMs)(stageOutcome.startedAt, stageOutcome.finishedAt);
|
|
3556
3732
|
await persistManifest(context.manifestPath, context.manifest);
|
|
3733
|
+
printProgress(`${context.stage} stage failed in ${formatDuration(stageOutcome.elapsedMs)}.`);
|
|
3557
3734
|
throw new Error(`Agent command failed during ${context.stage}: ${execution.stderr || execution.stdout || command}`);
|
|
3558
3735
|
}
|
|
3559
3736
|
const syncResult = await syncPacketFile({
|
|
3560
3737
|
client: context.client,
|
|
3561
|
-
|
|
3738
|
+
getAccessToken: context.getAccessToken,
|
|
3562
3739
|
project: context.project,
|
|
3563
3740
|
board: context.board,
|
|
3564
3741
|
ticket: context.ticket,
|
|
3565
3742
|
workspacePath: context.workspacePath,
|
|
3566
|
-
relativePacketPath: context.packetPath
|
|
3743
|
+
relativePacketPath: context.packetPath,
|
|
3744
|
+
allowLaneSync: context.options.checking || context.stage !== "do",
|
|
3745
|
+
delegationId: context.options.delegation?.id ?? null
|
|
3567
3746
|
});
|
|
3568
3747
|
stageOutcome.result = "synced";
|
|
3569
3748
|
stageOutcome.costUsd = await readStageCostUsd(costOutputPath);
|
|
@@ -3579,6 +3758,7 @@ async function runStageAndSync(context) {
|
|
|
3579
3758
|
stageOutcome.laneAfter = syncResult.ticket.workflowState;
|
|
3580
3759
|
stageOutcome.syncedChanges = syncResult.syncedChanges;
|
|
3581
3760
|
await persistManifest(context.manifestPath, context.manifest);
|
|
3761
|
+
printProgress(`${context.stage} stage completed in ${formatDuration(stageOutcome.elapsedMs)}; synced ${syncResult.syncedChanges} change(s); lane is ${syncResult.ticket.workflowState}.`);
|
|
3582
3762
|
return { board: syncResult.board, ticket: syncResult.ticket };
|
|
3583
3763
|
}
|
|
3584
3764
|
async function syncPacketFile(input2) {
|
|
@@ -3609,7 +3789,7 @@ async function syncPacketFile(input2) {
|
|
|
3609
3789
|
currentBoard = await input2.client.updateChecklistItem(input2.project.id, currentTicket.id, currentItem.id, {
|
|
3610
3790
|
completed: parsedItem.completed,
|
|
3611
3791
|
expectedRevision: currentItem.revision
|
|
3612
|
-
}, input2.
|
|
3792
|
+
}, await input2.getAccessToken(), input2.delegationId);
|
|
3613
3793
|
currentTicket = requireTicket(currentBoard, currentTicket.id);
|
|
3614
3794
|
syncedChanges += 1;
|
|
3615
3795
|
}
|
|
@@ -3619,12 +3799,12 @@ async function syncPacketFile(input2) {
|
|
|
3619
3799
|
currentBoard = await input2.client.updateTicketNotes(input2.project.id, currentTicket.id, {
|
|
3620
3800
|
notes: desiredNotes,
|
|
3621
3801
|
expectedRevision: currentTicket.revision
|
|
3622
|
-
}, input2.
|
|
3802
|
+
}, await input2.getAccessToken(), input2.delegationId);
|
|
3623
3803
|
currentTicket = requireTicket(currentBoard, currentTicket.id);
|
|
3624
3804
|
syncedChanges += 1;
|
|
3625
3805
|
}
|
|
3626
|
-
if (parsedPacket.frontmatter.lane !== currentTicket.workflowState) {
|
|
3627
|
-
currentBoard = await moveTicket(input2.client, input2.
|
|
3806
|
+
if (input2.allowLaneSync && parsedPacket.frontmatter.lane !== currentTicket.workflowState) {
|
|
3807
|
+
currentBoard = await moveTicket(input2.client, await input2.getAccessToken(), input2.project.id, currentTicket, parsedPacket.frontmatter.lane, input2.delegationId);
|
|
3628
3808
|
currentTicket = requireTicket(currentBoard, currentTicket.id);
|
|
3629
3809
|
syncedChanges += 1;
|
|
3630
3810
|
}
|
|
@@ -3639,6 +3819,10 @@ async function buildAgentEnvironment(options, stageEnv) {
|
|
|
3639
3819
|
TERM: process.env.TERM,
|
|
3640
3820
|
...stageEnv
|
|
3641
3821
|
};
|
|
3822
|
+
if (options.commitPolicy === "done") {
|
|
3823
|
+
env.SWARMCRAFT_DRIVER_AUTO_COMMIT = "true";
|
|
3824
|
+
env.SWARMCRAFT_CLI_AUTO_COMMIT = "true";
|
|
3825
|
+
}
|
|
3642
3826
|
if (!options.agentApiKeyEnvName) {
|
|
3643
3827
|
return env;
|
|
3644
3828
|
}
|
|
@@ -3675,23 +3859,28 @@ function sumStageCostUsd(manifest) {
|
|
|
3675
3859
|
return manifest.tickets.flatMap((ticket) => ticket.stages).reduce((total, stage) => total + Number(stage.costUsd ?? 0), 0);
|
|
3676
3860
|
}
|
|
3677
3861
|
async function moveAfterAcceptedChecking(context, board, ticket, packetPath) {
|
|
3678
|
-
if (context.options.
|
|
3679
|
-
|
|
3862
|
+
if (!context.options.reviewing) {
|
|
3863
|
+
printProgress("Moving accepted ticket to done.");
|
|
3864
|
+
const doneBoard = ticket.workflowState === "done" ? board : await moveTicket(context.client, await context.getAccessToken(), context.project.id, ticket, "done", context.options.delegation?.id ?? null);
|
|
3680
3865
|
const doneTicket = requireTicket(doneBoard, ticket.id);
|
|
3681
3866
|
await refreshPacketMetadata(context.workspacePath, packetPath, doneTicket);
|
|
3682
3867
|
return { board: doneBoard, ticket: doneTicket, outcome: "done" };
|
|
3683
3868
|
}
|
|
3684
|
-
|
|
3869
|
+
let reviewBoard = board;
|
|
3870
|
+
if (ticket.workflowState !== "reviewing") {
|
|
3871
|
+
printProgress("Moving accepted ticket to reviewing.");
|
|
3872
|
+
reviewBoard = await moveTicket(context.client, await context.getAccessToken(), context.project.id, ticket, "reviewing", context.options.delegation?.id ?? null);
|
|
3873
|
+
}
|
|
3685
3874
|
const reviewTicket = requireTicket(reviewBoard, ticket.id);
|
|
3686
3875
|
await refreshPacketMetadata(context.workspacePath, packetPath, reviewTicket);
|
|
3687
3876
|
return { board: reviewBoard, ticket: reviewTicket, outcome: "manual-review-required" };
|
|
3688
3877
|
}
|
|
3689
|
-
async function moveTicket(client, accessToken, projectId, ticket, workflowState) {
|
|
3878
|
+
async function moveTicket(client, accessToken, projectId, ticket, workflowState, delegationId) {
|
|
3690
3879
|
return client.moveTicket(projectId, ticket.id, {
|
|
3691
3880
|
workflowState,
|
|
3692
3881
|
targetIndex: null,
|
|
3693
3882
|
expectedRevision: ticket.revision
|
|
3694
|
-
}, accessToken);
|
|
3883
|
+
}, accessToken, delegationId);
|
|
3695
3884
|
}
|
|
3696
3885
|
async function refreshPacketMetadata(workspacePath, relativePacketPath, ticket, syncedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3697
3886
|
const contents = await readWorkspaceFile(workspacePath, relativePacketPath);
|
|
@@ -3782,6 +3971,29 @@ function describeAgentPolicy(options) {
|
|
|
3782
3971
|
})
|
|
3783
3972
|
};
|
|
3784
3973
|
}
|
|
3974
|
+
function printProgress(message) {
|
|
3975
|
+
console.log(`[one-shot] ${message}`);
|
|
3976
|
+
}
|
|
3977
|
+
async function resolveAccessToken(accessTokenProvider) {
|
|
3978
|
+
return typeof accessTokenProvider === "string" ? accessTokenProvider : accessTokenProvider();
|
|
3979
|
+
}
|
|
3980
|
+
function formatDuration(elapsedMs) {
|
|
3981
|
+
if (elapsedMs === null || elapsedMs === void 0) {
|
|
3982
|
+
return "unknown";
|
|
3983
|
+
}
|
|
3984
|
+
const seconds = Math.round(elapsedMs / 1e3);
|
|
3985
|
+
if (seconds < 60) {
|
|
3986
|
+
return `${seconds}s`;
|
|
3987
|
+
}
|
|
3988
|
+
const minutes = Math.floor(seconds / 60);
|
|
3989
|
+
const remainingSeconds = seconds % 60;
|
|
3990
|
+
if (minutes < 60) {
|
|
3991
|
+
return remainingSeconds === 0 ? `${minutes}m` : `${minutes}m ${remainingSeconds}s`;
|
|
3992
|
+
}
|
|
3993
|
+
const hours = Math.floor(minutes / 60);
|
|
3994
|
+
const remainingMinutes = minutes % 60;
|
|
3995
|
+
return remainingMinutes === 0 ? `${hours}h` : `${hours}h ${remainingMinutes}m`;
|
|
3996
|
+
}
|
|
3785
3997
|
|
|
3786
3998
|
// src/sessionStore.ts
|
|
3787
3999
|
var import_promises3 = require("node:fs/promises");
|
|
@@ -3822,10 +4034,52 @@ function sessionExpiresSoon(session) {
|
|
|
3822
4034
|
return expiresAt - Date.now() < 6e4;
|
|
3823
4035
|
}
|
|
3824
4036
|
|
|
4037
|
+
// src/supportBundle.ts
|
|
4038
|
+
function summarizeDelegationForBundle(value) {
|
|
4039
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || !("options" in value)) {
|
|
4040
|
+
return null;
|
|
4041
|
+
}
|
|
4042
|
+
const options = value.options;
|
|
4043
|
+
if (!options || typeof options !== "object" || Array.isArray(options) || !("delegation" in options)) {
|
|
4044
|
+
return null;
|
|
4045
|
+
}
|
|
4046
|
+
const delegation = options.delegation;
|
|
4047
|
+
if (!delegation || typeof delegation !== "object" || Array.isArray(delegation)) {
|
|
4048
|
+
return null;
|
|
4049
|
+
}
|
|
4050
|
+
const typedDelegation = delegation;
|
|
4051
|
+
return {
|
|
4052
|
+
id: typedDelegation.id ?? null,
|
|
4053
|
+
status: typedDelegation.status ?? null,
|
|
4054
|
+
expiresAt: typedDelegation.expiresAt ?? null,
|
|
4055
|
+
supportTicketId: typedDelegation.supportTicketId ?? null,
|
|
4056
|
+
hasActorEmail: Boolean(typedDelegation.actorEmail),
|
|
4057
|
+
hasTargetEmail: Boolean(typedDelegation.targetEmail),
|
|
4058
|
+
hasTargetUserId: Boolean(typedDelegation.targetUserId),
|
|
4059
|
+
hasReason: Boolean(typedDelegation.reason)
|
|
4060
|
+
};
|
|
4061
|
+
}
|
|
4062
|
+
function redactSupportBundleValue(value) {
|
|
4063
|
+
if (Array.isArray(value)) {
|
|
4064
|
+
return value.map(redactSupportBundleValue);
|
|
4065
|
+
}
|
|
4066
|
+
if (!value || typeof value !== "object") {
|
|
4067
|
+
return value;
|
|
4068
|
+
}
|
|
4069
|
+
return Object.fromEntries(
|
|
4070
|
+
Object.entries(value).map(([key, entry]) => [
|
|
4071
|
+
key,
|
|
4072
|
+
/token|secret|password|api[-_]?key|credential|actorEmail|targetEmail|targetUserId|actorUserId|reason/i.test(key) ? "<redacted>" : redactSupportBundleValue(entry)
|
|
4073
|
+
])
|
|
4074
|
+
);
|
|
4075
|
+
}
|
|
4076
|
+
|
|
3825
4077
|
// src/main.ts
|
|
3826
|
-
var VERSION =
|
|
4078
|
+
var VERSION = package_default.version;
|
|
3827
4079
|
var DEFAULT_API_BASE_URL = "https://swarmcraft.ai/api";
|
|
3828
4080
|
var AGENT_PROVIDER_USAGE = import_packet_workflow3.EXTERNAL_AGENT_PROVIDERS.join("|");
|
|
4081
|
+
var MIN_DELEGATION_EXPIRY_MINUTES = 5;
|
|
4082
|
+
var MAX_DELEGATION_EXPIRY_MINUTES = 600;
|
|
3829
4083
|
var COMMANDS = [
|
|
3830
4084
|
{
|
|
3831
4085
|
name: "auth login",
|
|
@@ -3833,45 +4087,57 @@ var COMMANDS = [
|
|
|
3833
4087
|
summary: "Sign in to SwarmCraft and store a local CLI session.",
|
|
3834
4088
|
implemented: true
|
|
3835
4089
|
},
|
|
4090
|
+
{
|
|
4091
|
+
name: "delegation start",
|
|
4092
|
+
usage: "swarmcraft delegation start --user <email-or-id> --reason <text> [--support-ticket <id>] [--expires-in-minutes <n>]",
|
|
4093
|
+
summary: "Start short-lived operator access to a customer project workspace.",
|
|
4094
|
+
implemented: true
|
|
4095
|
+
},
|
|
4096
|
+
{
|
|
4097
|
+
name: "delegation end",
|
|
4098
|
+
usage: "swarmcraft delegation end --delegation <id>",
|
|
4099
|
+
summary: "End an active operator delegation.",
|
|
4100
|
+
implemented: true
|
|
4101
|
+
},
|
|
3836
4102
|
{
|
|
3837
4103
|
name: "projects list",
|
|
3838
|
-
usage: "swarmcraft projects list",
|
|
4104
|
+
usage: "swarmcraft projects list [--delegation <id>] [--on-behalf-of <email-or-id> --delegation-reason <text>]",
|
|
3839
4105
|
summary: "List SwarmCraft projects available to the signed-in user.",
|
|
3840
4106
|
implemented: true
|
|
3841
4107
|
},
|
|
3842
4108
|
{
|
|
3843
4109
|
name: "init",
|
|
3844
|
-
usage: "swarmcraft init --project <project-id> --workspace <path> [--ticket <ticket-id>] [--ticket-limit <n|all>]",
|
|
4110
|
+
usage: "swarmcraft init --project <project-id> --workspace <path> [--ticket <ticket-id>] [--ticket-limit <n|all>] [--delegation <id>]",
|
|
3845
4111
|
summary: "Prepare a repository with SwarmCraft packet and agent guidance files.",
|
|
3846
4112
|
implemented: true
|
|
3847
4113
|
},
|
|
3848
4114
|
{
|
|
3849
4115
|
name: "one-shot",
|
|
3850
|
-
usage: `swarmcraft one-shot --project <project-id> --workspace <path> --agent-provider ${AGENT_PROVIDER_USAGE} [--run-id <id>] [--done-policy stop|auto] [--commit-policy none|done] [--allow-dirty]`,
|
|
4116
|
+
usage: `swarmcraft one-shot --project <project-id> --workspace <path> --agent-provider ${AGENT_PROVIDER_USAGE} [--delegation <id>] [--run-id <id>] [--checking on|off] [--reviewing on|off] [--done-policy stop|auto] [--commit-policy none|done] [--allow-dirty]`,
|
|
3851
4117
|
summary: "Run the project board packet loop with the selected agent provider.",
|
|
3852
4118
|
implemented: true
|
|
3853
4119
|
},
|
|
3854
4120
|
{
|
|
3855
4121
|
name: "resume",
|
|
3856
|
-
usage: "swarmcraft resume --run <run-id> --workspace <path>",
|
|
4122
|
+
usage: "swarmcraft resume --run <run-id> --workspace <path> [--allow-dirty] [--delegation <id>]",
|
|
3857
4123
|
summary: "Resume a prior CLI run from its recorded manifest.",
|
|
3858
4124
|
implemented: true
|
|
3859
4125
|
},
|
|
3860
4126
|
{
|
|
3861
4127
|
name: "status",
|
|
3862
|
-
usage: "swarmcraft status --run <run-id> --workspace <path>",
|
|
4128
|
+
usage: "swarmcraft status --run <run-id> --workspace <path> [--delegation <id>]",
|
|
3863
4129
|
summary: "Show the status of a recorded CLI run.",
|
|
3864
4130
|
implemented: true
|
|
3865
4131
|
},
|
|
3866
4132
|
{
|
|
3867
4133
|
name: "doctor",
|
|
3868
|
-
usage: "swarmcraft doctor [--workspace <path>] [--agent-provider codex|copilot|custom]",
|
|
4134
|
+
usage: "swarmcraft doctor [--workspace <path>] [--agent-provider codex|copilot|custom] [--delegation <id>]",
|
|
3869
4135
|
summary: "Check local auth, API, git, workspace, runtime, and agent prerequisites.",
|
|
3870
4136
|
implemented: true
|
|
3871
4137
|
},
|
|
3872
4138
|
{
|
|
3873
4139
|
name: "support-bundle",
|
|
3874
|
-
usage: "swarmcraft support-bundle --run <run-id> --workspace <path>",
|
|
4140
|
+
usage: "swarmcraft support-bundle --run <run-id> --workspace <path> [--delegation <id>]",
|
|
3875
4141
|
summary: "Collect a redacted support bundle for a CLI run.",
|
|
3876
4142
|
implemented: true
|
|
3877
4143
|
}
|
|
@@ -3908,6 +4174,12 @@ Run "swarmcraft --help" for available commands.`);
|
|
|
3908
4174
|
case "auth login":
|
|
3909
4175
|
await runAuthLogin(parsed);
|
|
3910
4176
|
return 0;
|
|
4177
|
+
case "delegation start":
|
|
4178
|
+
await runDelegationStart(parsed);
|
|
4179
|
+
return 0;
|
|
4180
|
+
case "delegation end":
|
|
4181
|
+
await runDelegationEnd(parsed);
|
|
4182
|
+
return 0;
|
|
3911
4183
|
case "projects list":
|
|
3912
4184
|
await runProjectsList(parsed);
|
|
3913
4185
|
return 0;
|
|
@@ -3958,9 +4230,23 @@ async function runAuthLogin(parsed) {
|
|
|
3958
4230
|
await writeStoredSession(toStoredSession(apiBaseUrl, tokenResponse));
|
|
3959
4231
|
console.log(`Signed in as ${tokenResponse.user.email}.`);
|
|
3960
4232
|
}
|
|
3961
|
-
async function
|
|
4233
|
+
async function runDelegationStart(parsed) {
|
|
3962
4234
|
const { client, session } = await resolveAuthenticatedClient(parsed);
|
|
3963
|
-
const
|
|
4235
|
+
const delegation = await client.startDelegation(session.accessToken, buildStartDelegationRequest(parsed, {
|
|
4236
|
+
userOption: "user",
|
|
4237
|
+
reasonOption: "reason"
|
|
4238
|
+
}));
|
|
4239
|
+
printDelegationLifecycle("Delegation started", toDelegationContext(delegation));
|
|
4240
|
+
}
|
|
4241
|
+
async function runDelegationEnd(parsed) {
|
|
4242
|
+
const { client, session } = await resolveAuthenticatedClient(parsed);
|
|
4243
|
+
const delegation = await client.endDelegation(session.accessToken, requireOption(parsed, "delegation"));
|
|
4244
|
+
printDelegationLifecycle("Delegation ended", toDelegationContext(delegation));
|
|
4245
|
+
}
|
|
4246
|
+
async function runProjectsList(parsed) {
|
|
4247
|
+
const { client, session, delegation } = await resolveProjectCommandContext(parsed);
|
|
4248
|
+
printDelegationContext(delegation);
|
|
4249
|
+
const projects = await client.listProjects(session.accessToken, delegation?.id ?? null);
|
|
3964
4250
|
if (projects.length === 0) {
|
|
3965
4251
|
console.log("No projects available.");
|
|
3966
4252
|
return;
|
|
@@ -3975,19 +4261,25 @@ async function runInit(parsed) {
|
|
|
3975
4261
|
const projectId = requireOption(parsed, "project");
|
|
3976
4262
|
const workspacePath = requireOption(parsed, "workspace");
|
|
3977
4263
|
const ticketLimit = parseTicketLimit(getOption(parsed, "ticket-limit") ?? "all");
|
|
3978
|
-
const { client, session } = await
|
|
4264
|
+
const { client, session, delegation } = await resolveProjectCommandContext(parsed);
|
|
4265
|
+
printDelegationContext(delegation);
|
|
3979
4266
|
const summary = await runBootstrap(client, session.accessToken, {
|
|
3980
4267
|
projectId,
|
|
3981
4268
|
workspacePath,
|
|
3982
4269
|
ticketIds: getOptions(parsed, "ticket"),
|
|
3983
|
-
ticketLimit
|
|
4270
|
+
ticketLimit,
|
|
4271
|
+
delegationId: delegation?.id ?? null
|
|
3984
4272
|
});
|
|
3985
4273
|
printBootstrapSummary(summary);
|
|
3986
4274
|
}
|
|
3987
4275
|
async function runOneShotCommand(parsed) {
|
|
3988
|
-
const { client, session } = await
|
|
3989
|
-
const options =
|
|
4276
|
+
const { client, session, delegation } = await resolveProjectCommandContext(parsed);
|
|
4277
|
+
const options = {
|
|
4278
|
+
...parseOneShotOptions(parsed),
|
|
4279
|
+
delegation
|
|
4280
|
+
};
|
|
3990
4281
|
const policy = describeAgentPolicy(options);
|
|
4282
|
+
printDelegationContext(delegation);
|
|
3991
4283
|
console.log(`Agent provider: ${options.agentProvider}`);
|
|
3992
4284
|
console.log(`Agent sandbox: ${policy.sandboxMode ?? "custom"}`);
|
|
3993
4285
|
console.log(`Agent approval posture: ${policy.approvalPosture ?? "custom"}`);
|
|
@@ -3997,7 +4289,7 @@ async function runOneShotCommand(parsed) {
|
|
|
3997
4289
|
};
|
|
3998
4290
|
process.once("SIGINT", onSigint);
|
|
3999
4291
|
try {
|
|
4000
|
-
const manifest = await runOneShot(client, session
|
|
4292
|
+
const manifest = await runOneShot(client, createAccessTokenProvider(client, session), options);
|
|
4001
4293
|
printRunSummary(manifest);
|
|
4002
4294
|
} finally {
|
|
4003
4295
|
process.off("SIGINT", onSigint);
|
|
@@ -4007,10 +4299,13 @@ async function runResume(parsed) {
|
|
|
4007
4299
|
const runId = requireOption(parsed, "run");
|
|
4008
4300
|
const workspacePath = requireOption(parsed, "workspace");
|
|
4009
4301
|
const manifest = await readRunManifest(workspacePath, runId);
|
|
4010
|
-
const { client, session } = await
|
|
4011
|
-
|
|
4302
|
+
const { client, session, delegation } = await resolveProjectCommandContext(parsed, manifest.options.delegation ?? null);
|
|
4303
|
+
printDelegationContext(delegation);
|
|
4304
|
+
const resumed = await runOneShot(client, createAccessTokenProvider(client, session), {
|
|
4012
4305
|
...manifest.options,
|
|
4306
|
+
delegation,
|
|
4013
4307
|
workspacePath,
|
|
4308
|
+
allowDirty: hasFlag(parsed, "allow-dirty") ? true : manifest.options.allowDirty,
|
|
4014
4309
|
resumeRunId: runId
|
|
4015
4310
|
});
|
|
4016
4311
|
printRunSummary(resumed);
|
|
@@ -4018,7 +4313,8 @@ async function runResume(parsed) {
|
|
|
4018
4313
|
async function runStatus(parsed) {
|
|
4019
4314
|
const runId = requireOption(parsed, "run");
|
|
4020
4315
|
const workspacePath = requireOption(parsed, "workspace");
|
|
4021
|
-
|
|
4316
|
+
const manifest = await readRunManifest(workspacePath, runId);
|
|
4317
|
+
printRunSummary(manifest);
|
|
4022
4318
|
}
|
|
4023
4319
|
async function runDoctor(parsed) {
|
|
4024
4320
|
console.log(`Node: ${process.version}`);
|
|
@@ -4026,8 +4322,9 @@ async function runDoctor(parsed) {
|
|
|
4026
4322
|
console.log(`Session: ${session ? `signed in as ${session.user.email}` : "missing"}`);
|
|
4027
4323
|
if (session) {
|
|
4028
4324
|
try {
|
|
4029
|
-
const client =
|
|
4030
|
-
await client.listProjects(
|
|
4325
|
+
const { client, session: resolvedSession, delegation } = await resolveProjectCommandContext(parsed);
|
|
4326
|
+
await client.listProjects(resolvedSession.accessToken, delegation?.id ?? null);
|
|
4327
|
+
printDelegationContext(delegation);
|
|
4031
4328
|
console.log("API: reachable");
|
|
4032
4329
|
} catch (error) {
|
|
4033
4330
|
console.log(`API: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -4058,13 +4355,14 @@ async function runSupportBundle(parsed) {
|
|
|
4058
4355
|
const manifest = JSON.parse(await (0, import_promises4.readFile)(manifestPath, "utf8"));
|
|
4059
4356
|
const bundleDir = import_node_path5.default.join(workspacePath, ".swarmcraft", "runs", runId, "support-bundle");
|
|
4060
4357
|
await (0, import_promises4.mkdir)(bundleDir, { recursive: true });
|
|
4061
|
-
await (0, import_promises4.writeFile)(import_node_path5.default.join(bundleDir, "manifest.redacted.json"), `${JSON.stringify(
|
|
4358
|
+
await (0, import_promises4.writeFile)(import_node_path5.default.join(bundleDir, "manifest.redacted.json"), `${JSON.stringify(redactSupportBundleValue(manifest), null, 2)}
|
|
4062
4359
|
`, "utf8");
|
|
4063
4360
|
await (0, import_promises4.writeFile)(import_node_path5.default.join(bundleDir, "summary.json"), `${JSON.stringify({
|
|
4064
4361
|
runId,
|
|
4065
4362
|
manifestPath,
|
|
4066
4363
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4067
|
-
node: process.version
|
|
4364
|
+
node: process.version,
|
|
4365
|
+
delegation: summarizeDelegationForBundle(manifest)
|
|
4068
4366
|
}, null, 2)}
|
|
4069
4367
|
`, "utf8");
|
|
4070
4368
|
console.log(`Support bundle written: ${bundleDir}`);
|
|
@@ -4076,6 +4374,10 @@ async function resolveAuthenticatedClient(parsed) {
|
|
|
4076
4374
|
}
|
|
4077
4375
|
const apiBaseUrl = getOption(parsed, "api-base-url") ?? process.env.SWARMCRAFT_API_BASE_URL ?? session.apiBaseUrl;
|
|
4078
4376
|
const client = new SwarmCraftClient(apiBaseUrl);
|
|
4377
|
+
session = {
|
|
4378
|
+
...session,
|
|
4379
|
+
apiBaseUrl
|
|
4380
|
+
};
|
|
4079
4381
|
if (sessionExpiresSoon(session)) {
|
|
4080
4382
|
if (!session.refreshToken) {
|
|
4081
4383
|
throw new Error("Stored SwarmCraft session has expired. Run `swarmcraft auth login` again.");
|
|
@@ -4089,6 +4391,79 @@ async function resolveAuthenticatedClient(parsed) {
|
|
|
4089
4391
|
}
|
|
4090
4392
|
return { client, session };
|
|
4091
4393
|
}
|
|
4394
|
+
function createAccessTokenProvider(client, initialSession) {
|
|
4395
|
+
let session = initialSession;
|
|
4396
|
+
let refreshPromise = null;
|
|
4397
|
+
return async () => {
|
|
4398
|
+
if (!sessionExpiresSoon(session)) {
|
|
4399
|
+
return session.accessToken;
|
|
4400
|
+
}
|
|
4401
|
+
if (!session.refreshToken) {
|
|
4402
|
+
throw new Error("Stored SwarmCraft session has expired. Run `swarmcraft auth login` again.");
|
|
4403
|
+
}
|
|
4404
|
+
refreshPromise ??= (async () => {
|
|
4405
|
+
const refreshed = await client.refresh(session.refreshToken);
|
|
4406
|
+
session = toStoredSession(session.apiBaseUrl, {
|
|
4407
|
+
...refreshed,
|
|
4408
|
+
refreshToken: refreshed.refreshToken ?? session.refreshToken
|
|
4409
|
+
});
|
|
4410
|
+
await writeStoredSession(session);
|
|
4411
|
+
})().finally(() => {
|
|
4412
|
+
refreshPromise = null;
|
|
4413
|
+
});
|
|
4414
|
+
await refreshPromise;
|
|
4415
|
+
return session.accessToken;
|
|
4416
|
+
};
|
|
4417
|
+
}
|
|
4418
|
+
async function resolveProjectCommandContext(parsed, fallbackDelegation = null) {
|
|
4419
|
+
const { client, session } = await resolveAuthenticatedClient(parsed);
|
|
4420
|
+
const delegation = await resolveDelegationContext(client, session, parsed, fallbackDelegation);
|
|
4421
|
+
return { client, session, delegation };
|
|
4422
|
+
}
|
|
4423
|
+
async function resolveDelegationContext(client, session, parsed, fallbackDelegation) {
|
|
4424
|
+
const delegationId = getOption(parsed, "delegation");
|
|
4425
|
+
const onBehalfOf = getOption(parsed, "on-behalf-of");
|
|
4426
|
+
if (delegationId && onBehalfOf) {
|
|
4427
|
+
throw new Error("Use either --delegation or --on-behalf-of, not both.");
|
|
4428
|
+
}
|
|
4429
|
+
if (delegationId) {
|
|
4430
|
+
return toDelegationContext(await client.loadDelegation(session.accessToken, delegationId));
|
|
4431
|
+
}
|
|
4432
|
+
if (onBehalfOf) {
|
|
4433
|
+
const delegation = await client.startDelegation(session.accessToken, buildStartDelegationRequest(parsed, {
|
|
4434
|
+
userOption: "on-behalf-of",
|
|
4435
|
+
reasonOption: "delegation-reason"
|
|
4436
|
+
}));
|
|
4437
|
+
const context = toDelegationContext(delegation);
|
|
4438
|
+
printDelegationLifecycle("Delegation started", context);
|
|
4439
|
+
return context;
|
|
4440
|
+
}
|
|
4441
|
+
if (hasFlag(parsed, "delegation-reason")) {
|
|
4442
|
+
throw new Error("--delegation-reason requires --on-behalf-of.");
|
|
4443
|
+
}
|
|
4444
|
+
return fallbackDelegation;
|
|
4445
|
+
}
|
|
4446
|
+
function buildStartDelegationRequest(parsed, options) {
|
|
4447
|
+
const expiresInMinutes = getOption(parsed, "expires-in-minutes") ? parseDelegationExpiry(getOption(parsed, "expires-in-minutes")) : null;
|
|
4448
|
+
return {
|
|
4449
|
+
user: requireOption(parsed, options.userOption),
|
|
4450
|
+
reason: requireOption(parsed, options.reasonOption),
|
|
4451
|
+
supportTicketId: getOption(parsed, "support-ticket") ?? null,
|
|
4452
|
+
expiresInMinutes
|
|
4453
|
+
};
|
|
4454
|
+
}
|
|
4455
|
+
function toDelegationContext(delegation) {
|
|
4456
|
+
return {
|
|
4457
|
+
id: delegation.id,
|
|
4458
|
+
actorEmail: delegation.actorEmail,
|
|
4459
|
+
targetUserId: delegation.targetUserId,
|
|
4460
|
+
targetEmail: delegation.targetEmail,
|
|
4461
|
+
reason: delegation.reason,
|
|
4462
|
+
supportTicketId: delegation.supportTicketId,
|
|
4463
|
+
status: delegation.status,
|
|
4464
|
+
expiresAt: delegation.expiresAt
|
|
4465
|
+
};
|
|
4466
|
+
}
|
|
4092
4467
|
function printBootstrapSummary(summary) {
|
|
4093
4468
|
console.log(`Bootstrapped ${summary.projectTitle}`);
|
|
4094
4469
|
console.log(`Workspace: ${summary.workspacePath}`);
|
|
@@ -4110,10 +4485,26 @@ function printBootstrapSummary(summary) {
|
|
|
4110
4485
|
console.log(` ${nextCommand}`);
|
|
4111
4486
|
}
|
|
4112
4487
|
}
|
|
4488
|
+
function printDelegationLifecycle(prefix, delegation) {
|
|
4489
|
+
console.log(`${prefix}: ${delegation.id}`);
|
|
4490
|
+
printDelegationContext(delegation);
|
|
4491
|
+
if (delegation.expiresAt) {
|
|
4492
|
+
console.log(`Delegation expires: ${delegation.expiresAt}`);
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
function printDelegationContext(delegation) {
|
|
4496
|
+
if (!delegation) {
|
|
4497
|
+
return;
|
|
4498
|
+
}
|
|
4499
|
+
const actor = delegation.actorEmail ?? "operator";
|
|
4500
|
+
const target = delegation.targetEmail ?? delegation.targetUserId ?? "target user";
|
|
4501
|
+
console.log(`Delegated access: ${actor} -> ${target} (${delegation.id})`);
|
|
4502
|
+
}
|
|
4113
4503
|
function printRunSummary(manifest) {
|
|
4114
4504
|
console.log(`Run: ${manifest.runId}`);
|
|
4115
4505
|
console.log(`Status: ${manifest.status}`);
|
|
4116
4506
|
console.log(`Workspace: ${manifest.workspacePath}`);
|
|
4507
|
+
printDelegationContext(manifest.options?.delegation ?? null);
|
|
4117
4508
|
console.log(`Tickets: ${manifest.tickets.length}`);
|
|
4118
4509
|
for (const ticket of manifest.tickets) {
|
|
4119
4510
|
console.log(` - ${ticket.outcome}: ${ticket.title} (${ticket.stages.length} stage(s))`);
|
|
@@ -4124,14 +4515,16 @@ function printRunSummary(manifest) {
|
|
|
4124
4515
|
}
|
|
4125
4516
|
function parseOneShotOptions(parsed) {
|
|
4126
4517
|
const agentProvider = (0, import_packet_workflow3.parseExternalAgentProvider)(requireOption(parsed, "agent-provider"));
|
|
4127
|
-
const donePolicy = parseChoice(getOption(parsed, "done-policy") ?? "
|
|
4518
|
+
const donePolicy = parseChoice(getOption(parsed, "done-policy") ?? "auto", ["stop", "auto"], "--done-policy");
|
|
4128
4519
|
const commitPolicy = parseChoice(getOption(parsed, "commit-policy") ?? "none", ["none", "done"], "--commit-policy");
|
|
4520
|
+
const explicitReviewing = getOption(parsed, "reviewing");
|
|
4129
4521
|
return {
|
|
4130
4522
|
projectId: requireOption(parsed, "project"),
|
|
4131
4523
|
workspacePath: requireOption(parsed, "workspace"),
|
|
4132
4524
|
agentProvider,
|
|
4133
4525
|
agentCommand: getOption(parsed, "agent-command") ?? process.env.SWARMCRAFT_AGENT_COMMAND ?? null,
|
|
4134
|
-
checking: parseOnOff(getOption(parsed, "checking") ?? "
|
|
4526
|
+
checking: parseOnOff(getOption(parsed, "checking") ?? "off", "--checking"),
|
|
4527
|
+
reviewing: explicitReviewing ? parseOnOff(explicitReviewing, "--reviewing") : donePolicy === "stop",
|
|
4135
4528
|
maxCheckingAttempts: parsePositiveInteger(getOption(parsed, "max-checking-attempts") ?? "2", "--max-checking-attempts"),
|
|
4136
4529
|
donePolicy,
|
|
4137
4530
|
commitPolicy,
|
|
@@ -4143,6 +4536,7 @@ function parseOneShotOptions(parsed) {
|
|
|
4143
4536
|
agentApiKeyEnvName: getOption(parsed, "agent-api-key-env-name") ?? null,
|
|
4144
4537
|
ticketIds: getOptions(parsed, "ticket"),
|
|
4145
4538
|
ticketLimit: parseTicketLimit(getOption(parsed, "ticket-limit") ?? "all"),
|
|
4539
|
+
delegation: null,
|
|
4146
4540
|
runId: parseRunId(getOption(parsed, "run-id"))
|
|
4147
4541
|
};
|
|
4148
4542
|
}
|
|
@@ -4178,7 +4572,7 @@ function findCommand(positionals) {
|
|
|
4178
4572
|
if (positionals.length === 0) {
|
|
4179
4573
|
return void 0;
|
|
4180
4574
|
}
|
|
4181
|
-
const commandKey = positionals[0] === "auth" || positionals[0] === "projects" ? positionals.slice(0, 2).join(" ") : positionals[0];
|
|
4575
|
+
const commandKey = positionals[0] === "auth" || positionals[0] === "projects" || positionals[0] === "delegation" ? positionals.slice(0, 2).join(" ") : positionals[0];
|
|
4182
4576
|
return COMMANDS.find((command) => command.name === commandKey);
|
|
4183
4577
|
}
|
|
4184
4578
|
function printGeneralHelp() {
|
|
@@ -4243,6 +4637,13 @@ function parsePositiveInteger(rawValue, flagName) {
|
|
|
4243
4637
|
}
|
|
4244
4638
|
return parsed;
|
|
4245
4639
|
}
|
|
4640
|
+
function parseDelegationExpiry(rawValue) {
|
|
4641
|
+
const parsed = parsePositiveInteger(rawValue, "--expires-in-minutes");
|
|
4642
|
+
if (parsed < MIN_DELEGATION_EXPIRY_MINUTES || parsed > MAX_DELEGATION_EXPIRY_MINUTES) {
|
|
4643
|
+
throw new Error(`--expires-in-minutes must be between ${MIN_DELEGATION_EXPIRY_MINUTES} and ${MAX_DELEGATION_EXPIRY_MINUTES}.`);
|
|
4644
|
+
}
|
|
4645
|
+
return parsed;
|
|
4646
|
+
}
|
|
4246
4647
|
function parseNonNegativeNumber(rawValue, flagName) {
|
|
4247
4648
|
const parsed = Number.parseFloat(rawValue);
|
|
4248
4649
|
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
@@ -4274,20 +4675,6 @@ function parseRunId(rawValue) {
|
|
|
4274
4675
|
}
|
|
4275
4676
|
return rawValue;
|
|
4276
4677
|
}
|
|
4277
|
-
function redact(value) {
|
|
4278
|
-
if (Array.isArray(value)) {
|
|
4279
|
-
return value.map(redact);
|
|
4280
|
-
}
|
|
4281
|
-
if (!value || typeof value !== "object") {
|
|
4282
|
-
return value;
|
|
4283
|
-
}
|
|
4284
|
-
return Object.fromEntries(
|
|
4285
|
-
Object.entries(value).map(([key, entry]) => [
|
|
4286
|
-
key,
|
|
4287
|
-
/token|secret|password|api[-_]?key|credential/i.test(key) ? "<redacted>" : redact(entry)
|
|
4288
|
-
])
|
|
4289
|
-
);
|
|
4290
|
-
}
|
|
4291
4678
|
async function promptLine(prompt) {
|
|
4292
4679
|
const readline = (0, import_promises5.createInterface)({ input: import_node_process.stdin, output: import_node_process.stdout });
|
|
4293
4680
|
try {
|
|
@@ -4299,14 +4686,26 @@ async function promptLine(prompt) {
|
|
|
4299
4686
|
async function promptSecret(prompt) {
|
|
4300
4687
|
return promptLine(prompt);
|
|
4301
4688
|
}
|
|
4302
|
-
|
|
4303
|
-
process.exitCode
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4689
|
+
if (isCliEntrypoint()) {
|
|
4690
|
+
main(process.argv.slice(2)).then((exitCode) => {
|
|
4691
|
+
process.exitCode = exitCode;
|
|
4692
|
+
}).catch((error) => {
|
|
4693
|
+
if (error instanceof SwarmCraftApiError) {
|
|
4694
|
+
const request = error.requestId ? ` requestId=${error.requestId}` : "";
|
|
4695
|
+
console.error(`${error.code}: ${error.message}${request}`);
|
|
4696
|
+
} else {
|
|
4697
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
4698
|
+
}
|
|
4699
|
+
process.exitCode = 1;
|
|
4700
|
+
});
|
|
4701
|
+
}
|
|
4702
|
+
function isCliEntrypoint() {
|
|
4703
|
+
const invokedScript = process.argv[1] ? import_node_path5.default.basename(process.argv[1]) : "";
|
|
4704
|
+
return invokedScript === "main.cjs" || invokedScript === "main.js" || invokedScript === "main.ts";
|
|
4705
|
+
}
|
|
4706
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4707
|
+
0 && (module.exports = {
|
|
4708
|
+
findCommand,
|
|
4709
|
+
main,
|
|
4710
|
+
parseArgs
|
|
4312
4711
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmcraftai/cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Prepare repositories and run packet-based SwarmCraft agent workflows from the terminal.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://swarmcraft.ai",
|