flightdesk 0.4.3 → 0.6.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 +48 -12
- package/main.js +414 -85
- package/main.js.map +4 -4
- package/package.json +1 -1
package/main.js
CHANGED
|
@@ -958,8 +958,8 @@ var require_command = __commonJS({
|
|
|
958
958
|
"node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/command.js"(exports2) {
|
|
959
959
|
var EventEmitter = require("node:events").EventEmitter;
|
|
960
960
|
var childProcess = require("node:child_process");
|
|
961
|
-
var
|
|
962
|
-
var
|
|
961
|
+
var path4 = require("node:path");
|
|
962
|
+
var fs4 = require("node:fs");
|
|
963
963
|
var process2 = require("node:process");
|
|
964
964
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
965
965
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1891,11 +1891,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1891
1891
|
let launchWithNode = false;
|
|
1892
1892
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1893
1893
|
function findFile(baseDir, baseName) {
|
|
1894
|
-
const localBin =
|
|
1895
|
-
if (
|
|
1896
|
-
if (sourceExt.includes(
|
|
1894
|
+
const localBin = path4.resolve(baseDir, baseName);
|
|
1895
|
+
if (fs4.existsSync(localBin)) return localBin;
|
|
1896
|
+
if (sourceExt.includes(path4.extname(baseName))) return void 0;
|
|
1897
1897
|
const foundExt = sourceExt.find(
|
|
1898
|
-
(ext) =>
|
|
1898
|
+
(ext) => fs4.existsSync(`${localBin}${ext}`)
|
|
1899
1899
|
);
|
|
1900
1900
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1901
1901
|
return void 0;
|
|
@@ -1907,21 +1907,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1907
1907
|
if (this._scriptPath) {
|
|
1908
1908
|
let resolvedScriptPath;
|
|
1909
1909
|
try {
|
|
1910
|
-
resolvedScriptPath =
|
|
1910
|
+
resolvedScriptPath = fs4.realpathSync(this._scriptPath);
|
|
1911
1911
|
} catch (err) {
|
|
1912
1912
|
resolvedScriptPath = this._scriptPath;
|
|
1913
1913
|
}
|
|
1914
|
-
executableDir =
|
|
1915
|
-
|
|
1914
|
+
executableDir = path4.resolve(
|
|
1915
|
+
path4.dirname(resolvedScriptPath),
|
|
1916
1916
|
executableDir
|
|
1917
1917
|
);
|
|
1918
1918
|
}
|
|
1919
1919
|
if (executableDir) {
|
|
1920
1920
|
let localFile = findFile(executableDir, executableFile);
|
|
1921
1921
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1922
|
-
const legacyName =
|
|
1922
|
+
const legacyName = path4.basename(
|
|
1923
1923
|
this._scriptPath,
|
|
1924
|
-
|
|
1924
|
+
path4.extname(this._scriptPath)
|
|
1925
1925
|
);
|
|
1926
1926
|
if (legacyName !== this._name) {
|
|
1927
1927
|
localFile = findFile(
|
|
@@ -1932,7 +1932,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1932
1932
|
}
|
|
1933
1933
|
executableFile = localFile || executableFile;
|
|
1934
1934
|
}
|
|
1935
|
-
launchWithNode = sourceExt.includes(
|
|
1935
|
+
launchWithNode = sourceExt.includes(path4.extname(executableFile));
|
|
1936
1936
|
let proc;
|
|
1937
1937
|
if (process2.platform !== "win32") {
|
|
1938
1938
|
if (launchWithNode) {
|
|
@@ -2772,7 +2772,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2772
2772
|
* @return {Command}
|
|
2773
2773
|
*/
|
|
2774
2774
|
nameFromFilename(filename) {
|
|
2775
|
-
this._name =
|
|
2775
|
+
this._name = path4.basename(filename, path4.extname(filename));
|
|
2776
2776
|
return this;
|
|
2777
2777
|
}
|
|
2778
2778
|
/**
|
|
@@ -2786,9 +2786,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2786
2786
|
* @param {string} [path]
|
|
2787
2787
|
* @return {(string|null|Command)}
|
|
2788
2788
|
*/
|
|
2789
|
-
executableDir(
|
|
2790
|
-
if (
|
|
2791
|
-
this._executableDir =
|
|
2789
|
+
executableDir(path5) {
|
|
2790
|
+
if (path5 === void 0) return this._executableDir;
|
|
2791
|
+
this._executableDir = path5;
|
|
2792
2792
|
return this;
|
|
2793
2793
|
}
|
|
2794
2794
|
/**
|
|
@@ -3018,28 +3018,45 @@ var require_commander = __commonJS({
|
|
|
3018
3018
|
}
|
|
3019
3019
|
});
|
|
3020
3020
|
|
|
3021
|
-
// node_modules/.pnpm/commander@12.1.0/node_modules/commander/esm.mjs
|
|
3022
|
-
var import_index = __toESM(require_commander(), 1);
|
|
3023
|
-
var {
|
|
3024
|
-
program,
|
|
3025
|
-
createCommand,
|
|
3026
|
-
createArgument,
|
|
3027
|
-
createOption,
|
|
3028
|
-
CommanderError,
|
|
3029
|
-
InvalidArgumentError,
|
|
3030
|
-
InvalidOptionArgumentError,
|
|
3031
|
-
// deprecated old name
|
|
3032
|
-
Command,
|
|
3033
|
-
Argument,
|
|
3034
|
-
Option,
|
|
3035
|
-
Help
|
|
3036
|
-
} = import_index.default;
|
|
3037
|
-
|
|
3038
3021
|
// apps/cli/src/lib/config.ts
|
|
3039
3022
|
var fs = __toESM(require("fs"));
|
|
3040
3023
|
var path = __toESM(require("path"));
|
|
3041
3024
|
var os = __toESM(require("os"));
|
|
3042
3025
|
var CONFIG_FILE = path.join(os.homedir(), ".flightdeskrc");
|
|
3026
|
+
var FOLDER_RC = ".flightdeskrc";
|
|
3027
|
+
function readFolderCredential() {
|
|
3028
|
+
const envKey = process.env["FLIGHTDESK_API_KEY"];
|
|
3029
|
+
if (envKey) {
|
|
3030
|
+
return {
|
|
3031
|
+
apiKey: envKey,
|
|
3032
|
+
apiUrl: process.env["FLIGHTDESK_API_URL"],
|
|
3033
|
+
organizationId: process.env["FLIGHTDESK_ORGANIZATION_ID"],
|
|
3034
|
+
source: "env"
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
3037
|
+
let dir = process.cwd();
|
|
3038
|
+
const home = os.homedir();
|
|
3039
|
+
for (; ; ) {
|
|
3040
|
+
const candidate = path.join(dir, FOLDER_RC);
|
|
3041
|
+
if (candidate !== CONFIG_FILE && fs.existsSync(candidate)) {
|
|
3042
|
+
try {
|
|
3043
|
+
const parsed = JSON.parse(fs.readFileSync(candidate, "utf-8"));
|
|
3044
|
+
if (typeof parsed.apiKey === "string" && parsed.apiKey && !("organizations" in parsed))
|
|
3045
|
+
return { ...parsed, apiKey: parsed.apiKey, source: candidate };
|
|
3046
|
+
} catch {
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
const parent = path.dirname(dir);
|
|
3050
|
+
if (parent === dir || dir === home) break;
|
|
3051
|
+
dir = parent;
|
|
3052
|
+
}
|
|
3053
|
+
return null;
|
|
3054
|
+
}
|
|
3055
|
+
var folderCredential;
|
|
3056
|
+
function folder() {
|
|
3057
|
+
if (folderCredential === void 0) folderCredential = readFolderCredential();
|
|
3058
|
+
return folderCredential;
|
|
3059
|
+
}
|
|
3043
3060
|
var DEFAULT_API_URL = "https://api.flightdesk.dev";
|
|
3044
3061
|
var DEV_API_URL = "http://localhost:3000";
|
|
3045
3062
|
var apiUrlOverride = null;
|
|
@@ -3052,9 +3069,20 @@ function setApiUrl(url) {
|
|
|
3052
3069
|
apiUrlOverride = url;
|
|
3053
3070
|
}
|
|
3054
3071
|
function getApiUrl() {
|
|
3055
|
-
return apiUrlOverride ?? DEFAULT_API_URL;
|
|
3072
|
+
return apiUrlOverride ?? process.env["FLIGHTDESK_API_URL"] ?? folder()?.apiUrl ?? DEFAULT_API_URL;
|
|
3056
3073
|
}
|
|
3074
|
+
var TOKEN_SCOPED_ORG = "token-scoped";
|
|
3057
3075
|
function loadConfig() {
|
|
3076
|
+
const scoped = folder();
|
|
3077
|
+
if (scoped) {
|
|
3078
|
+
const id = scoped.organizationId ?? TOKEN_SCOPED_ORG;
|
|
3079
|
+
return {
|
|
3080
|
+
apiKey: scoped.apiKey,
|
|
3081
|
+
activeOrganization: id,
|
|
3082
|
+
organizations: [{ id, name: `agent (${scoped.source})` }],
|
|
3083
|
+
repoMapping: {}
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3058
3086
|
try {
|
|
3059
3087
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
3060
3088
|
const content = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
@@ -3074,6 +3102,8 @@ function loadConfig() {
|
|
|
3074
3102
|
};
|
|
3075
3103
|
}
|
|
3076
3104
|
function saveConfig(config) {
|
|
3105
|
+
if (folder())
|
|
3106
|
+
throw new Error("Refusing to write the home config while a folder credential is active");
|
|
3077
3107
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
3078
3108
|
}
|
|
3079
3109
|
function getActiveOrganization() {
|
|
@@ -3088,7 +3118,9 @@ function getActiveOrganization() {
|
|
|
3088
3118
|
}
|
|
3089
3119
|
function setActiveOrganization(orgId) {
|
|
3090
3120
|
const config = loadConfig();
|
|
3091
|
-
const org2 = config.organizations.find(
|
|
3121
|
+
const org2 = config.organizations.find(
|
|
3122
|
+
(o) => o.id === orgId || o.name.toLowerCase() === orgId.toLowerCase()
|
|
3123
|
+
);
|
|
3092
3124
|
if (!org2) {
|
|
3093
3125
|
throw new Error(`Organization not found: ${orgId}`);
|
|
3094
3126
|
}
|
|
@@ -3137,10 +3169,10 @@ function requireActiveOrg() {
|
|
|
3137
3169
|
return { config, org: org2 };
|
|
3138
3170
|
}
|
|
3139
3171
|
|
|
3140
|
-
// apps/cli/src/commands/init.ts
|
|
3141
|
-
var readline = __toESM(require("readline"));
|
|
3142
|
-
|
|
3143
3172
|
// apps/cli/src/lib/api.ts
|
|
3173
|
+
var fs2 = __toESM(require("node:fs"));
|
|
3174
|
+
var path2 = __toESM(require("node:path"));
|
|
3175
|
+
var import_node_crypto = require("node:crypto");
|
|
3144
3176
|
var FlightDeskAPI = class _FlightDeskAPI {
|
|
3145
3177
|
constructor(config, org2) {
|
|
3146
3178
|
if (!config.apiKey) {
|
|
@@ -3156,6 +3188,32 @@ var FlightDeskAPI = class _FlightDeskAPI {
|
|
|
3156
3188
|
static fromConfig(config, org2) {
|
|
3157
3189
|
return new _FlightDeskAPI(config, org2);
|
|
3158
3190
|
}
|
|
3191
|
+
/**
|
|
3192
|
+
* F23: transport failures (network, 5xx) are retried with backoff. GraphQL-level errors are
|
|
3193
|
+
* not — they are the API's answer. When `outbox` is set and every attempt fails, the call is
|
|
3194
|
+
* written to `<FLIGHTDESK_OUTBOX_DIR ?? cwd>/.flightdesk-outbox/<id>.json` as
|
|
3195
|
+
* `{ query, variables, attemptedAt }` for the runtime to flush on its next tick, and the
|
|
3196
|
+
* function resolves to `{ queued: true }` so an agent turn can still end.
|
|
3197
|
+
*/
|
|
3198
|
+
async graphqlWithRetry(query, variables, options = {}) {
|
|
3199
|
+
const attempts = options.attempts ?? 3;
|
|
3200
|
+
let lastError;
|
|
3201
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
3202
|
+
try {
|
|
3203
|
+
return await this.graphql(query, variables);
|
|
3204
|
+
} catch (error) {
|
|
3205
|
+
lastError = error;
|
|
3206
|
+
if (!isTransportError(error)) throw error;
|
|
3207
|
+
if (attempt < attempts) await sleep(1e3 * 3 ** (attempt - 1));
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
if (!options.outbox) throw lastError;
|
|
3211
|
+
const file = writeOutbox(query, variables);
|
|
3212
|
+
console.error(
|
|
3213
|
+
`\u26A0\uFE0F FlightDesk unreachable after ${attempts} attempts; queued to ${file} for the runtime to flush.`
|
|
3214
|
+
);
|
|
3215
|
+
return { queued: true, file };
|
|
3216
|
+
}
|
|
3159
3217
|
async graphql(query, variables) {
|
|
3160
3218
|
const verbose = process.env.FLIGHTDESK_DEBUG === "1";
|
|
3161
3219
|
if (verbose) {
|
|
@@ -3167,7 +3225,7 @@ var FlightDeskAPI = class _FlightDeskAPI {
|
|
|
3167
3225
|
method: "POST",
|
|
3168
3226
|
headers: {
|
|
3169
3227
|
"Content-Type": "application/json",
|
|
3170
|
-
|
|
3228
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
3171
3229
|
},
|
|
3172
3230
|
body: JSON.stringify({ query, variables })
|
|
3173
3231
|
});
|
|
@@ -3178,7 +3236,11 @@ var FlightDeskAPI = class _FlightDeskAPI {
|
|
|
3178
3236
|
console.log("Status:", response.status, response.statusText);
|
|
3179
3237
|
console.log("Body:", body);
|
|
3180
3238
|
}
|
|
3181
|
-
|
|
3239
|
+
const error = new Error(
|
|
3240
|
+
`API request failed: ${response.status} ${response.statusText}${verbose ? "" : " (set FLIGHTDESK_DEBUG=1 for details)"}`
|
|
3241
|
+
);
|
|
3242
|
+
error.status = response.status;
|
|
3243
|
+
throw error;
|
|
3182
3244
|
}
|
|
3183
3245
|
const result = await response.json();
|
|
3184
3246
|
if (verbose) {
|
|
@@ -3416,6 +3478,12 @@ var FlightDeskAPI = class _FlightDeskAPI {
|
|
|
3416
3478
|
url
|
|
3417
3479
|
primary
|
|
3418
3480
|
}
|
|
3481
|
+
freshness
|
|
3482
|
+
isStale
|
|
3483
|
+
builtCommitSha
|
|
3484
|
+
builtCommitAt
|
|
3485
|
+
headCommitSha
|
|
3486
|
+
headCommitAt
|
|
3419
3487
|
}
|
|
3420
3488
|
}
|
|
3421
3489
|
`;
|
|
@@ -3430,7 +3498,10 @@ var FlightDeskAPI = class _FlightDeskAPI {
|
|
|
3430
3498
|
}
|
|
3431
3499
|
}
|
|
3432
3500
|
`;
|
|
3433
|
-
const result = await this.graphql(query, {
|
|
3501
|
+
const result = await this.graphql(query, {
|
|
3502
|
+
taskId,
|
|
3503
|
+
lines
|
|
3504
|
+
});
|
|
3434
3505
|
return result.userInstanceLogs;
|
|
3435
3506
|
}
|
|
3436
3507
|
async restartInstance(taskId) {
|
|
@@ -3466,7 +3537,7 @@ async function fetchUserInfo(apiKey, apiUrl = "https://api.flightdesk.dev") {
|
|
|
3466
3537
|
method: "POST",
|
|
3467
3538
|
headers: {
|
|
3468
3539
|
"Content-Type": "application/json",
|
|
3469
|
-
|
|
3540
|
+
Authorization: `Bearer ${apiKey}`
|
|
3470
3541
|
},
|
|
3471
3542
|
body: JSON.stringify({
|
|
3472
3543
|
query: `{
|
|
@@ -3498,8 +3569,197 @@ async function fetchUserInfo(apiKey, apiUrl = "https://api.flightdesk.dev") {
|
|
|
3498
3569
|
}
|
|
3499
3570
|
return result.data.me;
|
|
3500
3571
|
}
|
|
3572
|
+
function isTransportError(error) {
|
|
3573
|
+
const status = error?.status;
|
|
3574
|
+
if (typeof status === "number") return status >= 500 || status === 429;
|
|
3575
|
+
return !(error instanceof Error && error.message.startsWith("GraphQL error"));
|
|
3576
|
+
}
|
|
3577
|
+
function sleep(ms) {
|
|
3578
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3579
|
+
}
|
|
3580
|
+
var lastOutboxStamp = 0;
|
|
3581
|
+
function outboxId() {
|
|
3582
|
+
lastOutboxStamp = Math.max(Date.now(), lastOutboxStamp + 1);
|
|
3583
|
+
const stamp = lastOutboxStamp.toString(36).padStart(10, "0");
|
|
3584
|
+
return `${stamp}-${(0, import_node_crypto.randomBytes)(6).toString("hex")}`;
|
|
3585
|
+
}
|
|
3586
|
+
function writeOutbox(query, variables) {
|
|
3587
|
+
const dir = path2.join(process.env["FLIGHTDESK_OUTBOX_DIR"] ?? process.cwd(), ".flightdesk-outbox");
|
|
3588
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
3589
|
+
const file = path2.join(dir, `${outboxId()}.json`);
|
|
3590
|
+
fs2.writeFileSync(
|
|
3591
|
+
file,
|
|
3592
|
+
JSON.stringify({ query, variables, attemptedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
3593
|
+
);
|
|
3594
|
+
return file;
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
// apps/cli/src/commands/questions.ts
|
|
3598
|
+
async function execute(query, variables) {
|
|
3599
|
+
const { config, org: org2 } = requireActiveOrg();
|
|
3600
|
+
const api = FlightDeskAPI.fromConfig(config, org2);
|
|
3601
|
+
const outbox = query.trimStart().startsWith("mutation");
|
|
3602
|
+
console.log(JSON.stringify(await api.graphqlWithRetry(query, variables, { outbox }), null, 2));
|
|
3603
|
+
}
|
|
3604
|
+
function registerQuestionCommands(program3) {
|
|
3605
|
+
const questions = program3.command("questions").description("Ask roles, poll questions, and consume answers");
|
|
3606
|
+
questions.command("ask <taskId>").option("--to-role <role>", "requester, product_owner, tech_owner, client_contact, triager").option("--to-user <id>", "Address a specific human member directly").requiredOption("--key <key>", "Stable retry key").requiredOption("--body <text>", "Question").requiredOption("--context <text>", "Short answer context").option("--default <text>", "Proposed default; required for agents").option("--options <values...>", "Suggested answer choices").option("--due-at <iso>", "Optional due timestamp").option("--nonblocking", "Allow work to continue").action(
|
|
3607
|
+
(taskId, options) => execute("mutation($input:JSON!){userAskQuestion(input:$input)}", {
|
|
3608
|
+
input: {
|
|
3609
|
+
taskId,
|
|
3610
|
+
toRole: options.toRole,
|
|
3611
|
+
toUserId: options.toUser,
|
|
3612
|
+
requestKey: options.key,
|
|
3613
|
+
body: options.body,
|
|
3614
|
+
context: options.context,
|
|
3615
|
+
proposedDefault: options.default,
|
|
3616
|
+
blocking: !options.nonblocking,
|
|
3617
|
+
options: options.options,
|
|
3618
|
+
dueAt: options.dueAt
|
|
3619
|
+
}
|
|
3620
|
+
})
|
|
3621
|
+
);
|
|
3622
|
+
questions.command("list").option("--task <id>", "Task id").option("--assignee <id>", "Assignee user id").option("--status <status>", "OPEN, ANSWERED, DEFERRED, WITHDRAWN").action(
|
|
3623
|
+
(options) => execute("query($input:JSON!){userQuestions(input:$input)}", {
|
|
3624
|
+
input: { taskId: options.task, assignee: options.assignee, status: options.status }
|
|
3625
|
+
})
|
|
3626
|
+
);
|
|
3627
|
+
questions.command("answers <taskId>").description("Consume answers as the assigned agent; freezes answered and deferred records").action(
|
|
3628
|
+
(taskId) => execute("mutation($taskId:String!){userConsumeAnswers(taskId:$taskId)}", { taskId })
|
|
3629
|
+
);
|
|
3630
|
+
questions.command("withdraw <id>").action((id) => execute("mutation($id:String!){userWithdrawQuestion(id:$id)}", { id }));
|
|
3631
|
+
}
|
|
3632
|
+
|
|
3633
|
+
// apps/cli/src/commands/plan.ts
|
|
3634
|
+
var import_node_fs = require("node:fs");
|
|
3635
|
+
async function execute2(query, variables, attempts = 3) {
|
|
3636
|
+
const { config, org: org2 } = requireActiveOrg();
|
|
3637
|
+
const api = FlightDeskAPI.fromConfig(config, org2);
|
|
3638
|
+
console.log(
|
|
3639
|
+
JSON.stringify(
|
|
3640
|
+
await api.graphqlWithRetry(query, variables, { outbox: false, attempts }),
|
|
3641
|
+
null,
|
|
3642
|
+
2
|
|
3643
|
+
)
|
|
3644
|
+
);
|
|
3645
|
+
}
|
|
3646
|
+
function registerPlanCommands(program3) {
|
|
3647
|
+
const plan = program3.command("plan").description("Task plans held in FlightDesk");
|
|
3648
|
+
plan.command("submit <taskId>").description("Submit a plan revision from a markdown file (or --body)").option("-f, --file <path>", "Markdown file holding the plan").option("--body <markdown>", "Plan body inline (prefer --file)").option("--summary <text>", "One-paragraph summary").action(async (taskId, options) => {
|
|
3649
|
+
const body = options.file ? (0, import_node_fs.readFileSync)(options.file, "utf8") : options.body;
|
|
3650
|
+
if (!body?.trim()) {
|
|
3651
|
+
console.error("Provide --file <path> or --body <markdown>");
|
|
3652
|
+
process.exit(1);
|
|
3653
|
+
}
|
|
3654
|
+
await execute2(
|
|
3655
|
+
"mutation($input:JSON!){userSubmitPlan(input:$input)}",
|
|
3656
|
+
{ input: { taskId, body, summary: options.summary } },
|
|
3657
|
+
1
|
|
3658
|
+
);
|
|
3659
|
+
});
|
|
3660
|
+
plan.command("show <taskId>").description("Print the plan revisions and approval state").action(
|
|
3661
|
+
(taskId) => execute2("query($taskId:String!){userTaskPlan(taskId:$taskId)}", { taskId })
|
|
3662
|
+
);
|
|
3663
|
+
plan.command("approve <taskId>").description("Approve a plan revision (latest by default); humans only").option("--revision <n>", "Revision number").action(
|
|
3664
|
+
(taskId, options) => execute2("mutation($input:JSON!){userApprovePlan(input:$input)}", {
|
|
3665
|
+
input: {
|
|
3666
|
+
taskId,
|
|
3667
|
+
revision: options.revision ? Number.parseInt(options.revision, 10) : void 0
|
|
3668
|
+
}
|
|
3669
|
+
})
|
|
3670
|
+
);
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
// apps/cli/src/commands/dispatch.ts
|
|
3674
|
+
var os2 = __toESM(require("node:os"));
|
|
3675
|
+
async function execute3(query, variables) {
|
|
3676
|
+
const { config, org: org2 } = requireActiveOrg();
|
|
3677
|
+
const api = FlightDeskAPI.fromConfig(config, org2);
|
|
3678
|
+
const mutation = query.trimStart().startsWith("mutation");
|
|
3679
|
+
const idempotent = mutation && !query.includes("userAddTaskComment");
|
|
3680
|
+
const options = idempotent ? { outbox: true } : { outbox: false, attempts: mutation ? 1 : 3 };
|
|
3681
|
+
console.log(JSON.stringify(await api.graphqlWithRetry(query, variables, options), null, 2));
|
|
3682
|
+
}
|
|
3683
|
+
function registerDispatchCommands(program3) {
|
|
3684
|
+
const dispatch = program3.command("dispatch").description("Qalatra dispatch polling and reporting");
|
|
3685
|
+
dispatch.command("list").option("--since <iso>", "Only requests updated after this time").action(
|
|
3686
|
+
(options) => execute3("query($since: String) { userDispatchRequests(since: $since) }", {
|
|
3687
|
+
since: options.since
|
|
3688
|
+
})
|
|
3689
|
+
);
|
|
3690
|
+
dispatch.command("poll").description("Envelope form: { requests, serverTime, pollAfterSeconds }").option("--since <iso>", "Only requests updated after this time").action(
|
|
3691
|
+
(options) => execute3("query($since: String) { userDispatchPoll(since: $since) }", {
|
|
3692
|
+
since: options.since
|
|
3693
|
+
})
|
|
3694
|
+
);
|
|
3695
|
+
dispatch.command("update <id>").requiredOption("--status <status>", "ACKNOWLEDGED, RUNNING, DONE, or FAILED").option("--job <id>", "Runtime job id").option("--result-tail <text>", "Last \u22648 KB of the job output (DONE/FAILED)").option("--result-length <n>", "Full output length in characters").option("--diagnostics-kind <kind>", "error|timed_out|orphaned|launch_failed|dependency_down").option("--diagnostics-text <text>", "Sanitised diagnostics (\u22644 KB)").option("--resumable", "A session id was captured; the turn can be resumed").action(
|
|
3696
|
+
(id, options) => execute3("mutation($input: JSON!) { userUpdateDispatch(input: $input) }", {
|
|
3697
|
+
input: {
|
|
3698
|
+
id,
|
|
3699
|
+
status: options.status,
|
|
3700
|
+
agentJobId: options.job,
|
|
3701
|
+
resultTail: options.resultTail,
|
|
3702
|
+
resultLength: options.resultLength ? Number(options.resultLength) : void 0,
|
|
3703
|
+
diagnostics: options.diagnosticsKind ? { kind: options.diagnosticsKind, text: options.diagnosticsText ?? "" } : void 0,
|
|
3704
|
+
resumable: options.resumable
|
|
3705
|
+
}
|
|
3706
|
+
})
|
|
3707
|
+
);
|
|
3708
|
+
dispatch.command("request <taskId>").requiredOption("--agent <id>", "Agent user id").option("--kind <kind>", "PLAN, EXECUTE, ANSWER, CUSTOM", "EXECUTE").option("--prompt <text>", "Request context").action(
|
|
3709
|
+
(taskId, options) => execute3("mutation($input: JSON!) { userRequestDispatch(input: $input) }", {
|
|
3710
|
+
input: { taskId, agentUserId: options.agent, kind: options.kind, prompt: options.prompt }
|
|
3711
|
+
})
|
|
3712
|
+
);
|
|
3713
|
+
program3.command("comment <taskId>").requiredOption("--body <text>", "Internal comment").option("--ask-agent", "Ask the assigned agent; requires human owner/admin").action(
|
|
3714
|
+
(taskId, options) => execute3("mutation($input: JSON!) { userAddTaskComment(input: $input) }", {
|
|
3715
|
+
input: { taskId, body: options.body, askAgent: options.askAgent ?? false }
|
|
3716
|
+
})
|
|
3717
|
+
);
|
|
3718
|
+
program3.command("agent-bind").requiredOption("--path <path>", "Agent folder on the runtime box").option("--node <url>", "Runtime node URL (unused under pull-only; recorded for reference)").option("--box <id>", "Box identifier (defaults to the runtime hostname)").action((options) => {
|
|
3719
|
+
const { org: org2 } = requireActiveOrg();
|
|
3720
|
+
return execute3("mutation($input: JSON!) { userBindAgent(input: $input) }", {
|
|
3721
|
+
input: {
|
|
3722
|
+
// A folder credential without organizationId relies on the token's organization.
|
|
3723
|
+
organizationId: org2.id === TOKEN_SCOPED_ORG ? void 0 : org2.id,
|
|
3724
|
+
agentPath: options.path,
|
|
3725
|
+
agentNodeUrl: options.node,
|
|
3726
|
+
boxId: options.box ?? os2.hostname()
|
|
3727
|
+
}
|
|
3728
|
+
});
|
|
3729
|
+
});
|
|
3730
|
+
const turn = program3.command("turn").description("Report the outcome of an agent turn");
|
|
3731
|
+
turn.command("end").requiredOption("--dispatch <id>", "Dispatch id ($FLIGHTDESK_DISPATCH_ID)").requiredOption("--outcome <outcome>", "done | waiting | failed | blocked").option("--waiting-on <what>", "session | ci | review | qa | merge | deploy | dependency").option("--ref <url>", "PR URL or other reference").option("--summary <text>", "One-paragraph summary").action(
|
|
3732
|
+
(options) => execute3("mutation($input: JSON!) { userEndTurn(input: $input) }", {
|
|
3733
|
+
input: {
|
|
3734
|
+
dispatchId: options.dispatch,
|
|
3735
|
+
outcome: options.outcome,
|
|
3736
|
+
waitingOn: options.waitingOn,
|
|
3737
|
+
ref: options.ref,
|
|
3738
|
+
summary: options.summary
|
|
3739
|
+
}
|
|
3740
|
+
})
|
|
3741
|
+
);
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
// node_modules/.pnpm/commander@12.1.0/node_modules/commander/esm.mjs
|
|
3745
|
+
var import_index = __toESM(require_commander(), 1);
|
|
3746
|
+
var {
|
|
3747
|
+
program,
|
|
3748
|
+
createCommand,
|
|
3749
|
+
createArgument,
|
|
3750
|
+
createOption,
|
|
3751
|
+
CommanderError,
|
|
3752
|
+
InvalidArgumentError,
|
|
3753
|
+
InvalidOptionArgumentError,
|
|
3754
|
+
// deprecated old name
|
|
3755
|
+
Command,
|
|
3756
|
+
Argument,
|
|
3757
|
+
Option,
|
|
3758
|
+
Help
|
|
3759
|
+
} = import_index.default;
|
|
3501
3760
|
|
|
3502
3761
|
// apps/cli/src/commands/init.ts
|
|
3762
|
+
var readline = __toESM(require("readline"));
|
|
3503
3763
|
function question(rl, prompt) {
|
|
3504
3764
|
return new Promise((resolve) => {
|
|
3505
3765
|
rl.question(prompt, (answer) => {
|
|
@@ -3563,8 +3823,11 @@ async function initCommand() {
|
|
|
3563
3823
|
organizations.forEach((org2, i) => {
|
|
3564
3824
|
console.log(` ${i + 1}. ${org2.name}`);
|
|
3565
3825
|
});
|
|
3566
|
-
const choice = await question(
|
|
3567
|
-
|
|
3826
|
+
const choice = await question(
|
|
3827
|
+
rl,
|
|
3828
|
+
`
|
|
3829
|
+
Select active organization (1-${organizations.length}): `
|
|
3830
|
+
);
|
|
3568
3831
|
const choiceIndex = Number.parseInt(choice, 10) - 1;
|
|
3569
3832
|
if (choiceIndex < 0 || choiceIndex >= organizations.length) {
|
|
3570
3833
|
console.error("Invalid selection, using first organization");
|
|
@@ -3575,7 +3838,6 @@ Select active organization (1-${organizations.length}): `);
|
|
|
3575
3838
|
}
|
|
3576
3839
|
const config = loadConfig();
|
|
3577
3840
|
config.apiKey = apiKey;
|
|
3578
|
-
config.apiUrl = apiUrl;
|
|
3579
3841
|
config.organizations = organizations;
|
|
3580
3842
|
config.activeOrganization = activeOrganization;
|
|
3581
3843
|
saveConfig(config);
|
|
@@ -3640,7 +3902,7 @@ function parseGitRemoteUrl(remoteUrl) {
|
|
|
3640
3902
|
|
|
3641
3903
|
// apps/cli/src/commands/register.ts
|
|
3642
3904
|
var import_node_child_process2 = require("node:child_process");
|
|
3643
|
-
var
|
|
3905
|
+
var import_node_fs2 = require("node:fs");
|
|
3644
3906
|
var import_node_os = require("node:os");
|
|
3645
3907
|
var import_node_path = require("node:path");
|
|
3646
3908
|
async function registerCommand(taskId, options) {
|
|
@@ -3676,19 +3938,29 @@ async function registerCommand(taskId, options) {
|
|
|
3676
3938
|
const task2 = await api.createTask({
|
|
3677
3939
|
projectId,
|
|
3678
3940
|
title: options.title,
|
|
3679
|
-
|
|
3680
|
-
|
|
3941
|
+
// Launch-first orchestrators do not know the FlightDesk UUID when the
|
|
3942
|
+
// cloud prompt is sent. Persisting that same prompt on the task lets
|
|
3943
|
+
// the API derive its durable Linear/Notion source identity instead.
|
|
3944
|
+
description: options.description ?? options.prompt,
|
|
3945
|
+
subprojectId: options.subproject,
|
|
3946
|
+
sourceSystem: options.sourceSystem,
|
|
3947
|
+
sourceRef: options.sourceRef,
|
|
3948
|
+
sourceUrl: options.sourceUrl
|
|
3681
3949
|
});
|
|
3682
3950
|
actualTaskId = task2.id;
|
|
3683
3951
|
console.log(`\u2705 Created task: ${task2.id}`);
|
|
3684
3952
|
}
|
|
3685
3953
|
if (viewUrl || teleportId) {
|
|
3686
3954
|
console.log("Registering session with task...");
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3955
|
+
if (viewUrl && teleportId) {
|
|
3956
|
+
await api.registerSession(actualTaskId, { viewUrl, teleportId });
|
|
3957
|
+
} else {
|
|
3958
|
+
await api.updateTask(actualTaskId, {
|
|
3959
|
+
sessionViewUrl: viewUrl,
|
|
3960
|
+
sessionTeleportId: teleportId,
|
|
3961
|
+
status: "DISPATCHED"
|
|
3962
|
+
});
|
|
3963
|
+
}
|
|
3692
3964
|
console.log("\u2705 Session registered");
|
|
3693
3965
|
}
|
|
3694
3966
|
console.log(`
|
|
@@ -3705,7 +3977,7 @@ Task ID: ${actualTaskId}`);
|
|
|
3705
3977
|
}
|
|
3706
3978
|
}
|
|
3707
3979
|
function spawnClaudeRemote(prompt) {
|
|
3708
|
-
const tmpDir = (0,
|
|
3980
|
+
const tmpDir = (0, import_node_fs2.mkdtempSync)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "fd-claude-"));
|
|
3709
3981
|
const tmpFile = (0, import_node_path.join)(tmpDir, "output.txt");
|
|
3710
3982
|
const isMac = process.platform === "darwin";
|
|
3711
3983
|
const escaped = prompt.replaceAll("'", `'"'"'`);
|
|
@@ -3719,14 +3991,18 @@ function spawnClaudeRemote(prompt) {
|
|
|
3719
3991
|
try {
|
|
3720
3992
|
let raw = "";
|
|
3721
3993
|
try {
|
|
3722
|
-
raw = (0,
|
|
3994
|
+
raw = (0, import_node_fs2.readFileSync)(tmpFile, "utf8");
|
|
3723
3995
|
} catch {
|
|
3724
3996
|
}
|
|
3725
3997
|
try {
|
|
3726
|
-
(0,
|
|
3998
|
+
(0, import_node_fs2.rmSync)(tmpDir, { recursive: true });
|
|
3727
3999
|
} catch {
|
|
3728
4000
|
}
|
|
3729
|
-
const
|
|
4001
|
+
const ansiEscape = new RegExp(
|
|
4002
|
+
String.raw`${String.fromCodePoint(27)}\[[0-9;]*[mGKHFJABCDr]`,
|
|
4003
|
+
"g"
|
|
4004
|
+
);
|
|
4005
|
+
const output = raw.replace(ansiEscape, "").replace(/\r/g, "");
|
|
3730
4006
|
if (code !== 0) {
|
|
3731
4007
|
reject(new Error(`claude exited with code ${code}
|
|
3732
4008
|
Output: ${output.slice(0, 500)}`));
|
|
@@ -3794,14 +4070,20 @@ async function autoDetectProject(api, activeOrg) {
|
|
|
3794
4070
|
if (!mappedOrg) {
|
|
3795
4071
|
console.error(`\u274C Repository "${repoInfo.remote}" is not mapped to any organization.`);
|
|
3796
4072
|
console.error(" Run: flightdesk sync");
|
|
3797
|
-
console.error(
|
|
4073
|
+
console.error(
|
|
4074
|
+
" Or provide a project ID: flightdesk register --project <project-id> [task-id]"
|
|
4075
|
+
);
|
|
3798
4076
|
process.exit(1);
|
|
3799
4077
|
}
|
|
3800
4078
|
if (mappedOrg.id !== activeOrg.id) {
|
|
3801
|
-
console.error(
|
|
4079
|
+
console.error(
|
|
4080
|
+
`\u274C Repository "${repoInfo.remote}" is mapped to organization "${mappedOrg.name}",`
|
|
4081
|
+
);
|
|
3802
4082
|
console.error(` but your active organization is "${activeOrg.name}".`);
|
|
3803
4083
|
console.error(" Switch with: flightdesk org switch " + mappedOrg.name);
|
|
3804
|
-
console.error(
|
|
4084
|
+
console.error(
|
|
4085
|
+
" Or provide a project ID: flightdesk register --project <project-id> [task-id]"
|
|
4086
|
+
);
|
|
3805
4087
|
process.exit(1);
|
|
3806
4088
|
}
|
|
3807
4089
|
const projects = await api.listProjects();
|
|
@@ -3949,7 +4231,10 @@ async function handleCreate(api, options) {
|
|
|
3949
4231
|
projectId: options.project,
|
|
3950
4232
|
title: options.title,
|
|
3951
4233
|
description: options.description,
|
|
3952
|
-
subprojectId: options.subproject
|
|
4234
|
+
subprojectId: options.subproject,
|
|
4235
|
+
sourceSystem: options.sourceSystem,
|
|
4236
|
+
sourceRef: options.sourceRef,
|
|
4237
|
+
sourceUrl: options.sourceUrl
|
|
3953
4238
|
});
|
|
3954
4239
|
console.log(`
|
|
3955
4240
|
\u2705 Task created`);
|
|
@@ -3989,7 +4274,9 @@ async function handleStatus(api, options) {
|
|
|
3989
4274
|
console.log(`ID: ${task2.id}`);
|
|
3990
4275
|
console.log(`Status: ${getStatusEmoji2(task2.status)} ${task2.status}`);
|
|
3991
4276
|
if (task2.project) {
|
|
3992
|
-
console.log(
|
|
4277
|
+
console.log(
|
|
4278
|
+
`Project: ${task2.project.name}${task2.project.githubRepo ? ` (${task2.project.githubRepo})` : ""}`
|
|
4279
|
+
);
|
|
3993
4280
|
}
|
|
3994
4281
|
if (task2.description) {
|
|
3995
4282
|
console.log(`
|
|
@@ -4023,18 +4310,13 @@ async function handleUpdate(api, options) {
|
|
|
4023
4310
|
input.prUrl = options.prUrl;
|
|
4024
4311
|
const prNumberMatch = options.prUrl.match(/\/pull\/(\d+)/);
|
|
4025
4312
|
if (prNumberMatch) {
|
|
4026
|
-
input.prNumber = parseInt(prNumberMatch[1], 10);
|
|
4313
|
+
input.prNumber = Number.parseInt(prNumberMatch[1], 10);
|
|
4027
4314
|
}
|
|
4028
4315
|
}
|
|
4029
4316
|
if (options.prNumber) {
|
|
4030
|
-
input.prNumber = parseInt(options.prNumber, 10);
|
|
4317
|
+
input.prNumber = Number.parseInt(options.prNumber, 10);
|
|
4031
4318
|
}
|
|
4032
|
-
if (options.session) {
|
|
4033
|
-
const sessionId = parseSessionId(options.session);
|
|
4034
|
-
input.sessionViewUrl = `https://claude.ai/code/${sessionId}`;
|
|
4035
|
-
input.sessionTeleportId = sessionId;
|
|
4036
|
-
}
|
|
4037
|
-
if (Object.keys(input).length === 0) {
|
|
4319
|
+
if (Object.keys(input).length === 0 && !options.session) {
|
|
4038
4320
|
console.error("No updates specified. Use --status, --branch, --pr-url, or --session");
|
|
4039
4321
|
process.exit(1);
|
|
4040
4322
|
}
|
|
@@ -4046,7 +4328,18 @@ async function handleUpdate(api, options) {
|
|
|
4046
4328
|
}
|
|
4047
4329
|
}
|
|
4048
4330
|
console.log(`Updating task ${options.taskId}...`);
|
|
4049
|
-
|
|
4331
|
+
let task2;
|
|
4332
|
+
if (options.session) {
|
|
4333
|
+
const sessionId = parseSessionId(options.session);
|
|
4334
|
+
task2 = await api.registerSession(options.taskId, {
|
|
4335
|
+
viewUrl: `https://claude.ai/code/${sessionId}`,
|
|
4336
|
+
teleportId: sessionId
|
|
4337
|
+
});
|
|
4338
|
+
}
|
|
4339
|
+
if (Object.keys(input).length > 0) {
|
|
4340
|
+
task2 = await api.updateTask(options.taskId, input);
|
|
4341
|
+
}
|
|
4342
|
+
if (!task2) return;
|
|
4050
4343
|
console.log(`
|
|
4051
4344
|
\u2705 Task updated`);
|
|
4052
4345
|
console.log(` Status: ${task2.status}`);
|
|
@@ -4407,11 +4700,39 @@ async function listProjects(api) {
|
|
|
4407
4700
|
${projects.length} project(s)`);
|
|
4408
4701
|
}
|
|
4409
4702
|
|
|
4703
|
+
// apps/cli/src/lib/preview-freshness.ts
|
|
4704
|
+
var SHORT_SHA_LENGTH = 7;
|
|
4705
|
+
var UNKNOWN_SHA_LABEL = "unknown";
|
|
4706
|
+
function short(value) {
|
|
4707
|
+
if (typeof value !== "string") return UNKNOWN_SHA_LABEL;
|
|
4708
|
+
const trimmed = value.trim();
|
|
4709
|
+
return trimmed ? trimmed.slice(0, SHORT_SHA_LENGTH) : UNKNOWN_SHA_LABEL;
|
|
4710
|
+
}
|
|
4711
|
+
var UNKNOWN_LINES = [
|
|
4712
|
+
" \u2754 Freshness unknown \u2014 this preview cannot be confirmed to contain the latest commits"
|
|
4713
|
+
];
|
|
4714
|
+
function formatFreshnessLines(view) {
|
|
4715
|
+
switch (view.freshness) {
|
|
4716
|
+
case "FRESH":
|
|
4717
|
+
return [` \u2705 Up to date with ${short(view.builtCommitSha)}`];
|
|
4718
|
+
case "STALE":
|
|
4719
|
+
return [
|
|
4720
|
+
" \u26A0\uFE0F STALE \u2014 this preview does NOT contain the latest commits",
|
|
4721
|
+
` Built from: ${short(view.builtCommitSha)}`,
|
|
4722
|
+
` Branch head: ${short(view.headCommitSha)}`
|
|
4723
|
+
];
|
|
4724
|
+
// UNKNOWN and anything unrecognised. A future server-side enum value
|
|
4725
|
+
// degrades to "unknown" rather than to silence.
|
|
4726
|
+
default:
|
|
4727
|
+
return UNKNOWN_LINES;
|
|
4728
|
+
}
|
|
4729
|
+
}
|
|
4730
|
+
|
|
4410
4731
|
// apps/cli/src/commands/preview.ts
|
|
4411
4732
|
var import_node_child_process3 = require("node:child_process");
|
|
4412
|
-
var
|
|
4413
|
-
var
|
|
4414
|
-
var
|
|
4733
|
+
var path3 = __toESM(require("node:path"));
|
|
4734
|
+
var os3 = __toESM(require("node:os"));
|
|
4735
|
+
var fs3 = __toESM(require("node:fs"));
|
|
4415
4736
|
function isValidContainerId(id) {
|
|
4416
4737
|
return /^[a-fA-F0-9]+$/.test(id) && id.length >= 12 && id.length <= 64;
|
|
4417
4738
|
}
|
|
@@ -4494,6 +4815,9 @@ async function handleStatus2(api, options) {
|
|
|
4494
4815
|
} else {
|
|
4495
4816
|
console.log(` Preview URL: ${instance.previewUrl}`);
|
|
4496
4817
|
}
|
|
4818
|
+
for (const line of formatFreshnessLines(instance)) {
|
|
4819
|
+
console.log(line);
|
|
4820
|
+
}
|
|
4497
4821
|
console.log(` SSH: ${instance.sshConnectionString}`);
|
|
4498
4822
|
console.log(` Container: ${instance.containerId.substring(0, 12)}`);
|
|
4499
4823
|
if (instance.lastActivityAt) {
|
|
@@ -4577,10 +4901,10 @@ async function handleMount(api, options) {
|
|
|
4577
4901
|
process.exit(1);
|
|
4578
4902
|
}
|
|
4579
4903
|
const rawTaskIdPrefix = options.taskId.substring(0, 8);
|
|
4580
|
-
const safeTaskId =
|
|
4581
|
-
const mountDir = options.directory ||
|
|
4582
|
-
if (!
|
|
4583
|
-
|
|
4904
|
+
const safeTaskId = path3.basename(rawTaskIdPrefix).replaceAll(/[^a-zA-Z0-9_-]/g, "") || "task";
|
|
4905
|
+
const mountDir = options.directory || path3.join(os3.homedir(), "flightdesk-mounts", safeTaskId);
|
|
4906
|
+
if (!fs3.existsSync(mountDir)) {
|
|
4907
|
+
fs3.mkdirSync(mountDir, { recursive: true });
|
|
4584
4908
|
}
|
|
4585
4909
|
try {
|
|
4586
4910
|
const mounted = (0, import_node_child_process3.execSync)("mount", { encoding: "utf8" });
|
|
@@ -4633,9 +4957,9 @@ async function handleMount(api, options) {
|
|
|
4633
4957
|
}
|
|
4634
4958
|
async function handleUnmount(_api, options) {
|
|
4635
4959
|
const rawTaskIdPrefix = options.taskId.substring(0, 8);
|
|
4636
|
-
const safeTaskId =
|
|
4637
|
-
const mountDir =
|
|
4638
|
-
if (!
|
|
4960
|
+
const safeTaskId = path3.basename(rawTaskIdPrefix).replaceAll(/[^a-zA-Z0-9_-]/g, "") || "task";
|
|
4961
|
+
const mountDir = path3.join(os3.homedir(), "flightdesk-mounts", safeTaskId);
|
|
4962
|
+
if (!fs3.existsSync(mountDir)) {
|
|
4639
4963
|
console.log("Mount directory does not exist");
|
|
4640
4964
|
return;
|
|
4641
4965
|
}
|
|
@@ -4652,7 +4976,7 @@ async function handleUnmount(_api, options) {
|
|
|
4652
4976
|
}
|
|
4653
4977
|
console.log("\u2705 Unmounted successfully");
|
|
4654
4978
|
try {
|
|
4655
|
-
|
|
4979
|
+
fs3.rmSync(mountDir, { recursive: true, force: true });
|
|
4656
4980
|
} catch {
|
|
4657
4981
|
}
|
|
4658
4982
|
} catch (error) {
|
|
@@ -4685,7 +5009,10 @@ async function handleTeardown(api, options) {
|
|
|
4685
5009
|
|
|
4686
5010
|
// apps/cli/src/main.ts
|
|
4687
5011
|
var program2 = new Command();
|
|
4688
|
-
program2
|
|
5012
|
+
registerDispatchCommands(program2);
|
|
5013
|
+
registerQuestionCommands(program2);
|
|
5014
|
+
registerPlanCommands(program2);
|
|
5015
|
+
program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.6.0").option("--dev", "Use local development API (localhost:3000)").option("--api <url>", "Use custom API URL");
|
|
4689
5016
|
program2.hook("preAction", () => {
|
|
4690
5017
|
const opts = program2.opts();
|
|
4691
5018
|
if (opts.api) {
|
|
@@ -4698,11 +5025,13 @@ program2.hook("preAction", () => {
|
|
|
4698
5025
|
}
|
|
4699
5026
|
});
|
|
4700
5027
|
program2.command("init").description("Configure FlightDesk CLI with your API credentials").action(initCommand);
|
|
4701
|
-
program2.command("register [task-id]").description(
|
|
5028
|
+
program2.command("register [task-id]").description(
|
|
5029
|
+
"Register a Claude Code session with a FlightDesk task (auto-detects project from git repo)"
|
|
5030
|
+
).option("-p, --project <id>", "Project ID (auto-detected from git repo if not provided)").option("--subproject <id>", "Subproject ID (required if project has subprojects configured)").option("--view-url <url>", "Claude Code session view URL").option("--teleport-id <id>", "Claude Code teleport ID").option("--title <title>", "Task title (creates new task if task-id not provided)").option("--description <description>", "Task description").option("--prompt <prompt>", "Prompt to pass to claude --remote (avoids pipe detection issues)").option("--source-system <system>", "External source system (for example notion or linear)").option("--source-ref <ref>", "External source reference or page ID").option("--source-url <url>", "External source URL used for deterministic branch binding").action(registerCommand);
|
|
4702
5031
|
var project = program2.command("project").description("Project management commands");
|
|
4703
5032
|
project.command("list").description("List projects in the active organization").action(() => projectCommand("list", {}));
|
|
4704
5033
|
var task = program2.command("task").description("Task management commands");
|
|
4705
|
-
task.command("create").description("Create a new task").requiredOption("-p, --project <id>", "Project ID").requiredOption("-t, --title <title>", "Task title").option("-d, --description <description>", "Task description").option("--subproject <id>", "Subproject ID (required if project has subprojects configured)").action((options) => taskCommand("create", options));
|
|
5034
|
+
task.command("create").description("Create a new task").requiredOption("-p, --project <id>", "Project ID").requiredOption("-t, --title <title>", "Task title").option("-d, --description <description>", "Task description").option("--subproject <id>", "Subproject ID (required if project has subprojects configured)").option("--source-system <system>", "External source system (for example notion or linear)").option("--source-ref <ref>", "External source reference or page ID").option("--source-url <url>", "External source URL used for deterministic branch binding").action((options) => taskCommand("create", options));
|
|
4706
5035
|
task.command("list").description("List tasks").option("-p, --project <id>", "Filter by project ID").option("--status <status>", "Filter by status").action((options) => taskCommand("list", options));
|
|
4707
5036
|
task.command("status <task-id>").description("Get task status").action((taskId) => taskCommand("status", { taskId }));
|
|
4708
5037
|
task.command("update <task-id>").description("Update task").option("-s, --status <status>", "New status").option("--branch <branch>", "Branch name").option("--pr-url <url>", "Pull request URL").option("--pr-number <number>", "Pull request number (use if --pr-url is not enough)").option("--session <session>", "Claude Code session (URL or session ID)").action((taskId, options) => taskCommand("update", { taskId, ...options }));
|