@ondrej-svec/hog 1.1.3 → 1.2.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/dist/cli.js +30 -122
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -164,13 +164,6 @@ function getAuth() {
|
|
|
164
164
|
return null;
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
|
-
function saveAuth(data) {
|
|
168
|
-
ensureDir();
|
|
169
|
-
writeFileSync(AUTH_FILE, `${JSON.stringify(data, null, 2)}
|
|
170
|
-
`, {
|
|
171
|
-
mode: 384
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
167
|
function getConfig() {
|
|
175
168
|
if (!existsSync(CONFIG_FILE)) return {};
|
|
176
169
|
try {
|
|
@@ -3923,78 +3916,10 @@ init_config();
|
|
|
3923
3916
|
import { Command } from "commander";
|
|
3924
3917
|
|
|
3925
3918
|
// src/init.ts
|
|
3919
|
+
init_config();
|
|
3926
3920
|
import { execFileSync } from "child_process";
|
|
3927
3921
|
import { existsSync as existsSync2 } from "fs";
|
|
3928
3922
|
import { checkbox, confirm, input, select } from "@inquirer/prompts";
|
|
3929
|
-
|
|
3930
|
-
// src/auth.ts
|
|
3931
|
-
import { createServer } from "http";
|
|
3932
|
-
var AUTH_URL = "https://ticktick.com/oauth/authorize";
|
|
3933
|
-
var TOKEN_URL = "https://ticktick.com/oauth/token";
|
|
3934
|
-
var REDIRECT_URI = "http://localhost:8080";
|
|
3935
|
-
var SCOPE = "tasks:write tasks:read";
|
|
3936
|
-
function getAuthorizationUrl(clientId) {
|
|
3937
|
-
const params = new URLSearchParams({
|
|
3938
|
-
scope: SCOPE,
|
|
3939
|
-
client_id: clientId,
|
|
3940
|
-
state: "hog",
|
|
3941
|
-
redirect_uri: REDIRECT_URI,
|
|
3942
|
-
response_type: "code"
|
|
3943
|
-
});
|
|
3944
|
-
return `${AUTH_URL}?${params}`;
|
|
3945
|
-
}
|
|
3946
|
-
async function waitForAuthCode() {
|
|
3947
|
-
return new Promise((resolve, reject) => {
|
|
3948
|
-
const server = createServer((req, res) => {
|
|
3949
|
-
const url = new URL(req.url ?? "", REDIRECT_URI);
|
|
3950
|
-
const code = url.searchParams.get("code");
|
|
3951
|
-
if (code) {
|
|
3952
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
3953
|
-
res.end(
|
|
3954
|
-
"<html><body><h1>Heart of Gold authenticated!</h1><p>You can close this window.</p></body></html>"
|
|
3955
|
-
);
|
|
3956
|
-
server.close();
|
|
3957
|
-
resolve(code);
|
|
3958
|
-
} else {
|
|
3959
|
-
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
3960
|
-
res.end("Missing authorization code");
|
|
3961
|
-
server.close();
|
|
3962
|
-
reject(new Error("No authorization code received"));
|
|
3963
|
-
}
|
|
3964
|
-
});
|
|
3965
|
-
server.listen(8080, () => {
|
|
3966
|
-
});
|
|
3967
|
-
server.on("error", reject);
|
|
3968
|
-
setTimeout(() => {
|
|
3969
|
-
server.close();
|
|
3970
|
-
reject(new Error("Authorization timed out (2 min)"));
|
|
3971
|
-
}, 12e4);
|
|
3972
|
-
});
|
|
3973
|
-
}
|
|
3974
|
-
async function exchangeCodeForToken(clientId, clientSecret, code) {
|
|
3975
|
-
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
|
3976
|
-
const res = await fetch(TOKEN_URL, {
|
|
3977
|
-
method: "POST",
|
|
3978
|
-
headers: {
|
|
3979
|
-
Authorization: `Basic ${credentials}`,
|
|
3980
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
3981
|
-
},
|
|
3982
|
-
body: new URLSearchParams({
|
|
3983
|
-
grant_type: "authorization_code",
|
|
3984
|
-
code,
|
|
3985
|
-
redirect_uri: REDIRECT_URI
|
|
3986
|
-
})
|
|
3987
|
-
});
|
|
3988
|
-
if (!res.ok) {
|
|
3989
|
-
const text = await res.text();
|
|
3990
|
-
throw new Error(`Token exchange failed: ${text}`);
|
|
3991
|
-
}
|
|
3992
|
-
const data = await res.json();
|
|
3993
|
-
return data.access_token;
|
|
3994
|
-
}
|
|
3995
|
-
|
|
3996
|
-
// src/init.ts
|
|
3997
|
-
init_config();
|
|
3998
3923
|
function ghJson(args) {
|
|
3999
3924
|
const output = execFileSync("gh", args, { encoding: "utf-8", timeout: 3e4 }).trim();
|
|
4000
3925
|
return JSON.parse(output);
|
|
@@ -4078,12 +4003,13 @@ function listProjectFields(owner, projectNumber) {
|
|
|
4078
4003
|
return [];
|
|
4079
4004
|
}
|
|
4080
4005
|
}
|
|
4081
|
-
function
|
|
4006
|
+
function detectStatusField(owner, projectNumber) {
|
|
4082
4007
|
const fields = listProjectFields(owner, projectNumber);
|
|
4083
4008
|
const statusField = fields.find(
|
|
4084
4009
|
(f) => f.name === "Status" && f.type === "ProjectV2SingleSelectField"
|
|
4085
4010
|
);
|
|
4086
|
-
|
|
4011
|
+
if (!statusField) return null;
|
|
4012
|
+
return { fieldId: statusField.id, options: statusField.options ?? [] };
|
|
4087
4013
|
}
|
|
4088
4014
|
async function runInit(opts = {}) {
|
|
4089
4015
|
try {
|
|
@@ -4157,8 +4083,10 @@ Configuring ${repoName}...`);
|
|
|
4157
4083
|
});
|
|
4158
4084
|
}
|
|
4159
4085
|
console.log(" Detecting status field...");
|
|
4160
|
-
|
|
4161
|
-
|
|
4086
|
+
const statusInfo = detectStatusField(owner, projectNumber);
|
|
4087
|
+
let statusFieldId;
|
|
4088
|
+
if (statusInfo) {
|
|
4089
|
+
statusFieldId = statusInfo.fieldId;
|
|
4162
4090
|
console.log(` Found status field: ${statusFieldId}`);
|
|
4163
4091
|
} else {
|
|
4164
4092
|
console.log(" Could not auto-detect status field.");
|
|
@@ -4167,7 +4095,7 @@ Configuring ${repoName}...`);
|
|
|
4167
4095
|
});
|
|
4168
4096
|
}
|
|
4169
4097
|
const completionType = await select({
|
|
4170
|
-
message: ` When a task is completed
|
|
4098
|
+
message: ` When a task is completed, what should happen on GitHub?`,
|
|
4171
4099
|
choices: [
|
|
4172
4100
|
{ name: "Close the issue", value: "closeIssue" },
|
|
4173
4101
|
{ name: "Add a label (e.g. review:pending)", value: "addLabel" },
|
|
@@ -4182,9 +4110,21 @@ Configuring ${repoName}...`);
|
|
|
4182
4110
|
});
|
|
4183
4111
|
completionAction = { type: "addLabel", label };
|
|
4184
4112
|
} else if (completionType === "updateProjectStatus") {
|
|
4185
|
-
const
|
|
4186
|
-
|
|
4187
|
-
|
|
4113
|
+
const statusOptions = statusInfo?.options ?? [];
|
|
4114
|
+
let optionId;
|
|
4115
|
+
if (statusOptions.length > 0) {
|
|
4116
|
+
optionId = await select({
|
|
4117
|
+
message: " Status to set when completed:",
|
|
4118
|
+
choices: statusOptions.map((o) => ({
|
|
4119
|
+
name: o.name,
|
|
4120
|
+
value: o.id
|
|
4121
|
+
}))
|
|
4122
|
+
});
|
|
4123
|
+
} else {
|
|
4124
|
+
optionId = await input({
|
|
4125
|
+
message: " Status option ID to set:"
|
|
4126
|
+
});
|
|
4127
|
+
}
|
|
4188
4128
|
completionAction = { type: "updateProjectStatus", optionId };
|
|
4189
4129
|
} else {
|
|
4190
4130
|
completionAction = { type: "closeIssue" };
|
|
@@ -4201,43 +4141,11 @@ Configuring ${repoName}...`);
|
|
|
4201
4141
|
completionAction
|
|
4202
4142
|
});
|
|
4203
4143
|
}
|
|
4204
|
-
const
|
|
4205
|
-
message: "Enable TickTick integration?",
|
|
4206
|
-
default: false
|
|
4207
|
-
});
|
|
4144
|
+
const ticktickAlreadyEnabled = existsSync2(`${CONFIG_DIR}/auth.json`);
|
|
4208
4145
|
let ticktickAuth = false;
|
|
4209
|
-
if (
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
console.log(" TickTick auth already configured.");
|
|
4213
|
-
ticktickAuth = true;
|
|
4214
|
-
} else {
|
|
4215
|
-
const setupNow = await confirm({
|
|
4216
|
-
message: " Set up TickTick OAuth now?",
|
|
4217
|
-
default: true
|
|
4218
|
-
});
|
|
4219
|
-
if (setupNow) {
|
|
4220
|
-
const clientId = await input({ message: " TickTick OAuth client ID:" });
|
|
4221
|
-
const clientSecret = await input({ message: " TickTick OAuth client secret:" });
|
|
4222
|
-
const url = getAuthorizationUrl(clientId);
|
|
4223
|
-
console.log(`
|
|
4224
|
-
Open this URL to authorize:
|
|
4225
|
-
|
|
4226
|
-
${url}
|
|
4227
|
-
`);
|
|
4228
|
-
try {
|
|
4229
|
-
const { exec } = await import("child_process");
|
|
4230
|
-
exec(`open "${url}"`);
|
|
4231
|
-
} catch {
|
|
4232
|
-
}
|
|
4233
|
-
console.log(" Waiting for authorization...");
|
|
4234
|
-
const code = await waitForAuthCode();
|
|
4235
|
-
const accessToken = await exchangeCodeForToken(clientId, clientSecret, code);
|
|
4236
|
-
saveAuth({ accessToken, clientId, clientSecret });
|
|
4237
|
-
console.log(" TickTick authenticated successfully.");
|
|
4238
|
-
ticktickAuth = true;
|
|
4239
|
-
}
|
|
4240
|
-
}
|
|
4146
|
+
if (ticktickAlreadyEnabled) {
|
|
4147
|
+
ticktickAuth = true;
|
|
4148
|
+
console.log("TickTick auth found \u2014 integration enabled.");
|
|
4241
4149
|
}
|
|
4242
4150
|
console.log("\nBoard settings:");
|
|
4243
4151
|
const refreshInterval = await input({
|
|
@@ -4264,7 +4172,7 @@ Configuring ${repoName}...`);
|
|
|
4264
4172
|
assignee: login,
|
|
4265
4173
|
focusDuration: Number.parseInt(focusDuration, 10) || 1500
|
|
4266
4174
|
},
|
|
4267
|
-
ticktick: { enabled:
|
|
4175
|
+
ticktick: { enabled: ticktickAuth },
|
|
4268
4176
|
profiles: existingConfig?.profiles ?? {}
|
|
4269
4177
|
};
|
|
4270
4178
|
saveFullConfig(config2);
|
|
@@ -4674,7 +4582,7 @@ function resolveProjectId(projectId) {
|
|
|
4674
4582
|
process.exit(1);
|
|
4675
4583
|
}
|
|
4676
4584
|
var program = new Command();
|
|
4677
|
-
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.
|
|
4585
|
+
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.2.0").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
|
|
4678
4586
|
const opts = thisCommand.opts();
|
|
4679
4587
|
if (opts.json) setFormat("json");
|
|
4680
4588
|
if (opts.human) setFormat("human");
|