@ondrej-svec/hog 1.1.2 → 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 +41 -124
- 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);
|
|
@@ -4049,14 +3974,22 @@ function listAllRepos() {
|
|
|
4049
3974
|
}
|
|
4050
3975
|
function listOrgProjects(owner) {
|
|
4051
3976
|
try {
|
|
4052
|
-
|
|
3977
|
+
const result = ghJson([
|
|
3978
|
+
"project",
|
|
3979
|
+
"list",
|
|
3980
|
+
"--owner",
|
|
3981
|
+
owner,
|
|
3982
|
+
"--format",
|
|
3983
|
+
"json"
|
|
3984
|
+
]);
|
|
3985
|
+
return result.projects ?? [];
|
|
4053
3986
|
} catch {
|
|
4054
3987
|
return [];
|
|
4055
3988
|
}
|
|
4056
3989
|
}
|
|
4057
3990
|
function listProjectFields(owner, projectNumber) {
|
|
4058
3991
|
try {
|
|
4059
|
-
|
|
3992
|
+
const result = ghJson([
|
|
4060
3993
|
"project",
|
|
4061
3994
|
"field-list",
|
|
4062
3995
|
String(projectNumber),
|
|
@@ -4065,16 +3998,18 @@ function listProjectFields(owner, projectNumber) {
|
|
|
4065
3998
|
"--format",
|
|
4066
3999
|
"json"
|
|
4067
4000
|
]);
|
|
4001
|
+
return result.fields ?? [];
|
|
4068
4002
|
} catch {
|
|
4069
4003
|
return [];
|
|
4070
4004
|
}
|
|
4071
4005
|
}
|
|
4072
|
-
function
|
|
4006
|
+
function detectStatusField(owner, projectNumber) {
|
|
4073
4007
|
const fields = listProjectFields(owner, projectNumber);
|
|
4074
4008
|
const statusField = fields.find(
|
|
4075
4009
|
(f) => f.name === "Status" && f.type === "ProjectV2SingleSelectField"
|
|
4076
4010
|
);
|
|
4077
|
-
|
|
4011
|
+
if (!statusField) return null;
|
|
4012
|
+
return { fieldId: statusField.id, options: statusField.options ?? [] };
|
|
4078
4013
|
}
|
|
4079
4014
|
async function runInit(opts = {}) {
|
|
4080
4015
|
try {
|
|
@@ -4148,8 +4083,10 @@ Configuring ${repoName}...`);
|
|
|
4148
4083
|
});
|
|
4149
4084
|
}
|
|
4150
4085
|
console.log(" Detecting status field...");
|
|
4151
|
-
|
|
4152
|
-
|
|
4086
|
+
const statusInfo = detectStatusField(owner, projectNumber);
|
|
4087
|
+
let statusFieldId;
|
|
4088
|
+
if (statusInfo) {
|
|
4089
|
+
statusFieldId = statusInfo.fieldId;
|
|
4153
4090
|
console.log(` Found status field: ${statusFieldId}`);
|
|
4154
4091
|
} else {
|
|
4155
4092
|
console.log(" Could not auto-detect status field.");
|
|
@@ -4158,7 +4095,7 @@ Configuring ${repoName}...`);
|
|
|
4158
4095
|
});
|
|
4159
4096
|
}
|
|
4160
4097
|
const completionType = await select({
|
|
4161
|
-
message: ` When a task is completed
|
|
4098
|
+
message: ` When a task is completed, what should happen on GitHub?`,
|
|
4162
4099
|
choices: [
|
|
4163
4100
|
{ name: "Close the issue", value: "closeIssue" },
|
|
4164
4101
|
{ name: "Add a label (e.g. review:pending)", value: "addLabel" },
|
|
@@ -4173,9 +4110,21 @@ Configuring ${repoName}...`);
|
|
|
4173
4110
|
});
|
|
4174
4111
|
completionAction = { type: "addLabel", label };
|
|
4175
4112
|
} else if (completionType === "updateProjectStatus") {
|
|
4176
|
-
const
|
|
4177
|
-
|
|
4178
|
-
|
|
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
|
+
}
|
|
4179
4128
|
completionAction = { type: "updateProjectStatus", optionId };
|
|
4180
4129
|
} else {
|
|
4181
4130
|
completionAction = { type: "closeIssue" };
|
|
@@ -4192,43 +4141,11 @@ Configuring ${repoName}...`);
|
|
|
4192
4141
|
completionAction
|
|
4193
4142
|
});
|
|
4194
4143
|
}
|
|
4195
|
-
const
|
|
4196
|
-
message: "Enable TickTick integration?",
|
|
4197
|
-
default: false
|
|
4198
|
-
});
|
|
4144
|
+
const ticktickAlreadyEnabled = existsSync2(`${CONFIG_DIR}/auth.json`);
|
|
4199
4145
|
let ticktickAuth = false;
|
|
4200
|
-
if (
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
console.log(" TickTick auth already configured.");
|
|
4204
|
-
ticktickAuth = true;
|
|
4205
|
-
} else {
|
|
4206
|
-
const setupNow = await confirm({
|
|
4207
|
-
message: " Set up TickTick OAuth now?",
|
|
4208
|
-
default: true
|
|
4209
|
-
});
|
|
4210
|
-
if (setupNow) {
|
|
4211
|
-
const clientId = await input({ message: " TickTick OAuth client ID:" });
|
|
4212
|
-
const clientSecret = await input({ message: " TickTick OAuth client secret:" });
|
|
4213
|
-
const url = getAuthorizationUrl(clientId);
|
|
4214
|
-
console.log(`
|
|
4215
|
-
Open this URL to authorize:
|
|
4216
|
-
|
|
4217
|
-
${url}
|
|
4218
|
-
`);
|
|
4219
|
-
try {
|
|
4220
|
-
const { exec } = await import("child_process");
|
|
4221
|
-
exec(`open "${url}"`);
|
|
4222
|
-
} catch {
|
|
4223
|
-
}
|
|
4224
|
-
console.log(" Waiting for authorization...");
|
|
4225
|
-
const code = await waitForAuthCode();
|
|
4226
|
-
const accessToken = await exchangeCodeForToken(clientId, clientSecret, code);
|
|
4227
|
-
saveAuth({ accessToken, clientId, clientSecret });
|
|
4228
|
-
console.log(" TickTick authenticated successfully.");
|
|
4229
|
-
ticktickAuth = true;
|
|
4230
|
-
}
|
|
4231
|
-
}
|
|
4146
|
+
if (ticktickAlreadyEnabled) {
|
|
4147
|
+
ticktickAuth = true;
|
|
4148
|
+
console.log("TickTick auth found \u2014 integration enabled.");
|
|
4232
4149
|
}
|
|
4233
4150
|
console.log("\nBoard settings:");
|
|
4234
4151
|
const refreshInterval = await input({
|
|
@@ -4255,7 +4172,7 @@ Configuring ${repoName}...`);
|
|
|
4255
4172
|
assignee: login,
|
|
4256
4173
|
focusDuration: Number.parseInt(focusDuration, 10) || 1500
|
|
4257
4174
|
},
|
|
4258
|
-
ticktick: { enabled:
|
|
4175
|
+
ticktick: { enabled: ticktickAuth },
|
|
4259
4176
|
profiles: existingConfig?.profiles ?? {}
|
|
4260
4177
|
};
|
|
4261
4178
|
saveFullConfig(config2);
|
|
@@ -4665,7 +4582,7 @@ function resolveProjectId(projectId) {
|
|
|
4665
4582
|
process.exit(1);
|
|
4666
4583
|
}
|
|
4667
4584
|
var program = new Command();
|
|
4668
|
-
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) => {
|
|
4669
4586
|
const opts = thisCommand.opts();
|
|
4670
4587
|
if (opts.json) setFormat("json");
|
|
4671
4588
|
if (opts.human) setFormat("human");
|