@ondrej-svec/hog 1.3.0 → 1.4.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 +265 -164
- package/dist/cli.js.map +1 -1
- package/dist/fetch-worker.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,191 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/config.ts
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
14
|
+
import { homedir } from "os";
|
|
15
|
+
import { join } from "path";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
function migrateConfig(raw) {
|
|
18
|
+
const version = typeof raw["version"] === "number" ? raw["version"] : 1;
|
|
19
|
+
if (version < 2) {
|
|
20
|
+
raw = {
|
|
21
|
+
...raw,
|
|
22
|
+
version: 2,
|
|
23
|
+
repos: LEGACY_REPOS,
|
|
24
|
+
board: {
|
|
25
|
+
refreshInterval: 60,
|
|
26
|
+
backlogLimit: 20,
|
|
27
|
+
assignee: "unknown"
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const currentVersion = typeof raw["version"] === "number" ? raw["version"] : 2;
|
|
32
|
+
if (currentVersion < 3) {
|
|
33
|
+
raw = {
|
|
34
|
+
...raw,
|
|
35
|
+
version: 3,
|
|
36
|
+
ticktick: { enabled: existsSync(AUTH_FILE) }
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return HOG_CONFIG_SCHEMA.parse(raw);
|
|
40
|
+
}
|
|
41
|
+
function loadFullConfig() {
|
|
42
|
+
const raw = loadRawConfig();
|
|
43
|
+
if (Object.keys(raw).length === 0) {
|
|
44
|
+
const config2 = migrateConfig({});
|
|
45
|
+
saveFullConfig(config2);
|
|
46
|
+
return config2;
|
|
47
|
+
}
|
|
48
|
+
const version = typeof raw["version"] === "number" ? raw["version"] : 1;
|
|
49
|
+
if (version < 3) {
|
|
50
|
+
const migrated = migrateConfig(raw);
|
|
51
|
+
saveFullConfig(migrated);
|
|
52
|
+
return migrated;
|
|
53
|
+
}
|
|
54
|
+
return HOG_CONFIG_SCHEMA.parse(raw);
|
|
55
|
+
}
|
|
56
|
+
function saveFullConfig(config2) {
|
|
57
|
+
ensureDir();
|
|
58
|
+
writeFileSync(CONFIG_FILE, `${JSON.stringify(config2, null, 2)}
|
|
59
|
+
`, { mode: 384 });
|
|
60
|
+
}
|
|
61
|
+
function loadRawConfig() {
|
|
62
|
+
if (!existsSync(CONFIG_FILE)) return {};
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
65
|
+
} catch {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function resolveProfile(config2, profileName) {
|
|
70
|
+
const name = profileName ?? config2.defaultProfile;
|
|
71
|
+
if (!name) {
|
|
72
|
+
return { resolved: config2, activeProfile: null };
|
|
73
|
+
}
|
|
74
|
+
const profile = config2.profiles[name];
|
|
75
|
+
if (!profile) {
|
|
76
|
+
console.error(
|
|
77
|
+
`Profile "${name}" not found. Available: ${Object.keys(config2.profiles).join(", ") || "(none)"}`
|
|
78
|
+
);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
resolved: { ...config2, repos: profile.repos, board: profile.board, ticktick: profile.ticktick },
|
|
83
|
+
activeProfile: name
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function findRepo(config2, shortNameOrFull) {
|
|
87
|
+
return config2.repos.find((r) => r.shortName === shortNameOrFull || r.name === shortNameOrFull);
|
|
88
|
+
}
|
|
89
|
+
function validateRepoName(name) {
|
|
90
|
+
return REPO_NAME_PATTERN.test(name);
|
|
91
|
+
}
|
|
92
|
+
function ensureDir() {
|
|
93
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
function getAuth() {
|
|
96
|
+
if (!existsSync(AUTH_FILE)) return null;
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function saveAuth(data) {
|
|
104
|
+
ensureDir();
|
|
105
|
+
writeFileSync(AUTH_FILE, `${JSON.stringify(data, null, 2)}
|
|
106
|
+
`, {
|
|
107
|
+
mode: 384
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function getLlmAuth() {
|
|
111
|
+
const auth = getAuth();
|
|
112
|
+
if (auth?.openrouterApiKey) return { provider: "openrouter", apiKey: auth.openrouterApiKey };
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
function saveLlmAuth(openrouterApiKey) {
|
|
116
|
+
const existing = getAuth();
|
|
117
|
+
const updated = existing ? { ...existing, openrouterApiKey } : { accessToken: "", clientId: "", clientSecret: "", openrouterApiKey };
|
|
118
|
+
saveAuth(updated);
|
|
119
|
+
}
|
|
120
|
+
function clearLlmAuth() {
|
|
121
|
+
const existing = getAuth();
|
|
122
|
+
if (!existing) return;
|
|
123
|
+
const { openrouterApiKey: _, ...rest } = existing;
|
|
124
|
+
saveAuth(rest);
|
|
125
|
+
}
|
|
126
|
+
function getConfig() {
|
|
127
|
+
if (!existsSync(CONFIG_FILE)) return {};
|
|
128
|
+
try {
|
|
129
|
+
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
130
|
+
} catch {
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function saveConfig(data) {
|
|
135
|
+
ensureDir();
|
|
136
|
+
const existing = getConfig();
|
|
137
|
+
writeFileSync(CONFIG_FILE, `${JSON.stringify({ ...existing, ...data }, null, 2)}
|
|
138
|
+
`);
|
|
139
|
+
}
|
|
140
|
+
function requireAuth() {
|
|
141
|
+
const auth = getAuth();
|
|
142
|
+
if (!auth) {
|
|
143
|
+
console.error("Not authenticated. Run `hog init` first.");
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
return auth;
|
|
147
|
+
}
|
|
148
|
+
var CONFIG_DIR, AUTH_FILE, CONFIG_FILE, COMPLETION_ACTION_SCHEMA, REPO_NAME_PATTERN, REPO_CONFIG_SCHEMA, BOARD_CONFIG_SCHEMA, TICKTICK_CONFIG_SCHEMA, PROFILE_SCHEMA, HOG_CONFIG_SCHEMA, LEGACY_REPOS;
|
|
149
|
+
var init_config = __esm({
|
|
150
|
+
"src/config.ts"() {
|
|
151
|
+
"use strict";
|
|
152
|
+
CONFIG_DIR = join(homedir(), ".config", "hog");
|
|
153
|
+
AUTH_FILE = join(CONFIG_DIR, "auth.json");
|
|
154
|
+
CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
155
|
+
COMPLETION_ACTION_SCHEMA = z.discriminatedUnion("type", [
|
|
156
|
+
z.object({ type: z.literal("updateProjectStatus"), optionId: z.string() }),
|
|
157
|
+
z.object({ type: z.literal("closeIssue") }),
|
|
158
|
+
z.object({ type: z.literal("addLabel"), label: z.string() })
|
|
159
|
+
]);
|
|
160
|
+
REPO_NAME_PATTERN = /^[\w.-]+\/[\w.-]+$/;
|
|
161
|
+
REPO_CONFIG_SCHEMA = z.object({
|
|
162
|
+
name: z.string().regex(REPO_NAME_PATTERN, "Must be owner/repo format"),
|
|
163
|
+
shortName: z.string().min(1),
|
|
164
|
+
projectNumber: z.number().int().positive(),
|
|
165
|
+
statusFieldId: z.string().min(1),
|
|
166
|
+
completionAction: COMPLETION_ACTION_SCHEMA,
|
|
167
|
+
statusGroups: z.array(z.string()).optional()
|
|
168
|
+
});
|
|
169
|
+
BOARD_CONFIG_SCHEMA = z.object({
|
|
170
|
+
refreshInterval: z.number().int().min(10).default(60),
|
|
171
|
+
backlogLimit: z.number().int().min(1).default(20),
|
|
172
|
+
assignee: z.string().min(1),
|
|
173
|
+
focusDuration: z.number().int().min(60).default(1500)
|
|
174
|
+
});
|
|
175
|
+
TICKTICK_CONFIG_SCHEMA = z.object({
|
|
176
|
+
enabled: z.boolean().default(true)
|
|
177
|
+
});
|
|
178
|
+
PROFILE_SCHEMA = z.object({
|
|
179
|
+
repos: z.array(REPO_CONFIG_SCHEMA).default([]),
|
|
180
|
+
board: BOARD_CONFIG_SCHEMA,
|
|
181
|
+
ticktick: TICKTICK_CONFIG_SCHEMA.default({ enabled: true })
|
|
182
|
+
});
|
|
183
|
+
HOG_CONFIG_SCHEMA = z.object({
|
|
184
|
+
version: z.number().int().default(3),
|
|
185
|
+
defaultProjectId: z.string().optional(),
|
|
186
|
+
defaultProjectName: z.string().optional(),
|
|
187
|
+
repos: z.array(REPO_CONFIG_SCHEMA).default([]),
|
|
188
|
+
board: BOARD_CONFIG_SCHEMA,
|
|
189
|
+
ticktick: TICKTICK_CONFIG_SCHEMA.default({ enabled: true }),
|
|
190
|
+
profiles: z.record(z.string(), PROFILE_SCHEMA).default({}),
|
|
191
|
+
defaultProfile: z.string().optional()
|
|
192
|
+
});
|
|
193
|
+
LEGACY_REPOS = [];
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
12
197
|
// src/ai.ts
|
|
13
198
|
async function parseHeuristic(input2, today = /* @__PURE__ */ new Date()) {
|
|
14
199
|
let remaining = input2;
|
|
@@ -46,7 +231,7 @@ function detectProvider() {
|
|
|
46
231
|
if (orKey) return { provider: "openrouter", apiKey: orKey };
|
|
47
232
|
const antKey = process.env["ANTHROPIC_API_KEY"];
|
|
48
233
|
if (antKey) return { provider: "anthropic", apiKey: antKey };
|
|
49
|
-
return
|
|
234
|
+
return getLlmAuth();
|
|
50
235
|
}
|
|
51
236
|
async function callLLM(userText, validLabels, today, providerConfig) {
|
|
52
237
|
const { provider, apiKey } = providerConfig;
|
|
@@ -166,6 +351,7 @@ function hasLlmApiKey() {
|
|
|
166
351
|
var init_ai = __esm({
|
|
167
352
|
"src/ai.ts"() {
|
|
168
353
|
"use strict";
|
|
354
|
+
init_config();
|
|
169
355
|
}
|
|
170
356
|
});
|
|
171
357
|
|
|
@@ -233,168 +419,6 @@ var init_api = __esm({
|
|
|
233
419
|
}
|
|
234
420
|
});
|
|
235
421
|
|
|
236
|
-
// src/config.ts
|
|
237
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
238
|
-
import { homedir } from "os";
|
|
239
|
-
import { join } from "path";
|
|
240
|
-
import { z } from "zod";
|
|
241
|
-
function migrateConfig(raw) {
|
|
242
|
-
const version = typeof raw["version"] === "number" ? raw["version"] : 1;
|
|
243
|
-
if (version < 2) {
|
|
244
|
-
raw = {
|
|
245
|
-
...raw,
|
|
246
|
-
version: 2,
|
|
247
|
-
repos: LEGACY_REPOS,
|
|
248
|
-
board: {
|
|
249
|
-
refreshInterval: 60,
|
|
250
|
-
backlogLimit: 20,
|
|
251
|
-
assignee: "unknown"
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
const currentVersion = typeof raw["version"] === "number" ? raw["version"] : 2;
|
|
256
|
-
if (currentVersion < 3) {
|
|
257
|
-
raw = {
|
|
258
|
-
...raw,
|
|
259
|
-
version: 3,
|
|
260
|
-
ticktick: { enabled: existsSync(AUTH_FILE) }
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
return HOG_CONFIG_SCHEMA.parse(raw);
|
|
264
|
-
}
|
|
265
|
-
function loadFullConfig() {
|
|
266
|
-
const raw = loadRawConfig();
|
|
267
|
-
if (Object.keys(raw).length === 0) {
|
|
268
|
-
const config2 = migrateConfig({});
|
|
269
|
-
saveFullConfig(config2);
|
|
270
|
-
return config2;
|
|
271
|
-
}
|
|
272
|
-
const version = typeof raw["version"] === "number" ? raw["version"] : 1;
|
|
273
|
-
if (version < 3) {
|
|
274
|
-
const migrated = migrateConfig(raw);
|
|
275
|
-
saveFullConfig(migrated);
|
|
276
|
-
return migrated;
|
|
277
|
-
}
|
|
278
|
-
return HOG_CONFIG_SCHEMA.parse(raw);
|
|
279
|
-
}
|
|
280
|
-
function saveFullConfig(config2) {
|
|
281
|
-
ensureDir();
|
|
282
|
-
writeFileSync(CONFIG_FILE, `${JSON.stringify(config2, null, 2)}
|
|
283
|
-
`, { mode: 384 });
|
|
284
|
-
}
|
|
285
|
-
function loadRawConfig() {
|
|
286
|
-
if (!existsSync(CONFIG_FILE)) return {};
|
|
287
|
-
try {
|
|
288
|
-
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
289
|
-
} catch {
|
|
290
|
-
return {};
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
function resolveProfile(config2, profileName) {
|
|
294
|
-
const name = profileName ?? config2.defaultProfile;
|
|
295
|
-
if (!name) {
|
|
296
|
-
return { resolved: config2, activeProfile: null };
|
|
297
|
-
}
|
|
298
|
-
const profile = config2.profiles[name];
|
|
299
|
-
if (!profile) {
|
|
300
|
-
console.error(
|
|
301
|
-
`Profile "${name}" not found. Available: ${Object.keys(config2.profiles).join(", ") || "(none)"}`
|
|
302
|
-
);
|
|
303
|
-
process.exit(1);
|
|
304
|
-
}
|
|
305
|
-
return {
|
|
306
|
-
resolved: { ...config2, repos: profile.repos, board: profile.board, ticktick: profile.ticktick },
|
|
307
|
-
activeProfile: name
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
function findRepo(config2, shortNameOrFull) {
|
|
311
|
-
return config2.repos.find((r) => r.shortName === shortNameOrFull || r.name === shortNameOrFull);
|
|
312
|
-
}
|
|
313
|
-
function validateRepoName(name) {
|
|
314
|
-
return REPO_NAME_PATTERN.test(name);
|
|
315
|
-
}
|
|
316
|
-
function ensureDir() {
|
|
317
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
318
|
-
}
|
|
319
|
-
function getAuth() {
|
|
320
|
-
if (!existsSync(AUTH_FILE)) return null;
|
|
321
|
-
try {
|
|
322
|
-
return JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
|
|
323
|
-
} catch {
|
|
324
|
-
return null;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
function getConfig() {
|
|
328
|
-
if (!existsSync(CONFIG_FILE)) return {};
|
|
329
|
-
try {
|
|
330
|
-
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
331
|
-
} catch {
|
|
332
|
-
return {};
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
function saveConfig(data) {
|
|
336
|
-
ensureDir();
|
|
337
|
-
const existing = getConfig();
|
|
338
|
-
writeFileSync(CONFIG_FILE, `${JSON.stringify({ ...existing, ...data }, null, 2)}
|
|
339
|
-
`);
|
|
340
|
-
}
|
|
341
|
-
function requireAuth() {
|
|
342
|
-
const auth = getAuth();
|
|
343
|
-
if (!auth) {
|
|
344
|
-
console.error("Not authenticated. Run `hog init` first.");
|
|
345
|
-
process.exit(1);
|
|
346
|
-
}
|
|
347
|
-
return auth;
|
|
348
|
-
}
|
|
349
|
-
var CONFIG_DIR, AUTH_FILE, CONFIG_FILE, COMPLETION_ACTION_SCHEMA, REPO_NAME_PATTERN, REPO_CONFIG_SCHEMA, BOARD_CONFIG_SCHEMA, TICKTICK_CONFIG_SCHEMA, PROFILE_SCHEMA, HOG_CONFIG_SCHEMA, LEGACY_REPOS;
|
|
350
|
-
var init_config = __esm({
|
|
351
|
-
"src/config.ts"() {
|
|
352
|
-
"use strict";
|
|
353
|
-
CONFIG_DIR = join(homedir(), ".config", "hog");
|
|
354
|
-
AUTH_FILE = join(CONFIG_DIR, "auth.json");
|
|
355
|
-
CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
356
|
-
COMPLETION_ACTION_SCHEMA = z.discriminatedUnion("type", [
|
|
357
|
-
z.object({ type: z.literal("updateProjectStatus"), optionId: z.string() }),
|
|
358
|
-
z.object({ type: z.literal("closeIssue") }),
|
|
359
|
-
z.object({ type: z.literal("addLabel"), label: z.string() })
|
|
360
|
-
]);
|
|
361
|
-
REPO_NAME_PATTERN = /^[\w.-]+\/[\w.-]+$/;
|
|
362
|
-
REPO_CONFIG_SCHEMA = z.object({
|
|
363
|
-
name: z.string().regex(REPO_NAME_PATTERN, "Must be owner/repo format"),
|
|
364
|
-
shortName: z.string().min(1),
|
|
365
|
-
projectNumber: z.number().int().positive(),
|
|
366
|
-
statusFieldId: z.string().min(1),
|
|
367
|
-
completionAction: COMPLETION_ACTION_SCHEMA,
|
|
368
|
-
statusGroups: z.array(z.string()).optional()
|
|
369
|
-
});
|
|
370
|
-
BOARD_CONFIG_SCHEMA = z.object({
|
|
371
|
-
refreshInterval: z.number().int().min(10).default(60),
|
|
372
|
-
backlogLimit: z.number().int().min(1).default(20),
|
|
373
|
-
assignee: z.string().min(1),
|
|
374
|
-
focusDuration: z.number().int().min(60).default(1500)
|
|
375
|
-
});
|
|
376
|
-
TICKTICK_CONFIG_SCHEMA = z.object({
|
|
377
|
-
enabled: z.boolean().default(true)
|
|
378
|
-
});
|
|
379
|
-
PROFILE_SCHEMA = z.object({
|
|
380
|
-
repos: z.array(REPO_CONFIG_SCHEMA).default([]),
|
|
381
|
-
board: BOARD_CONFIG_SCHEMA,
|
|
382
|
-
ticktick: TICKTICK_CONFIG_SCHEMA.default({ enabled: true })
|
|
383
|
-
});
|
|
384
|
-
HOG_CONFIG_SCHEMA = z.object({
|
|
385
|
-
version: z.number().int().default(3),
|
|
386
|
-
defaultProjectId: z.string().optional(),
|
|
387
|
-
defaultProjectName: z.string().optional(),
|
|
388
|
-
repos: z.array(REPO_CONFIG_SCHEMA).default([]),
|
|
389
|
-
board: BOARD_CONFIG_SCHEMA,
|
|
390
|
-
ticktick: TICKTICK_CONFIG_SCHEMA.default({ enabled: true }),
|
|
391
|
-
profiles: z.record(z.string(), PROFILE_SCHEMA).default({}),
|
|
392
|
-
defaultProfile: z.string().optional()
|
|
393
|
-
});
|
|
394
|
-
LEGACY_REPOS = [];
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
|
|
398
422
|
// src/types.ts
|
|
399
423
|
var init_types = __esm({
|
|
400
424
|
"src/types.ts"() {
|
|
@@ -5088,6 +5112,28 @@ Configuring ${repoName}...`);
|
|
|
5088
5112
|
message: " Focus timer duration (seconds):",
|
|
5089
5113
|
default: "1500"
|
|
5090
5114
|
});
|
|
5115
|
+
console.log("\nAI-enhanced issue creation (optional):");
|
|
5116
|
+
console.log(
|
|
5117
|
+
' Press I on the board to create issues with natural language (e.g. "fix login bug #backend @alice due friday").'
|
|
5118
|
+
);
|
|
5119
|
+
console.log(" Without a key the heuristic parser still works \u2014 labels, assignee, and due dates");
|
|
5120
|
+
console.log(" are extracted from #, @, and due tokens. An OpenRouter key enables richer title");
|
|
5121
|
+
console.log(" cleanup and inference for ambiguous input.");
|
|
5122
|
+
const setupLlm = await confirm({
|
|
5123
|
+
message: " Set up an OpenRouter API key now?",
|
|
5124
|
+
default: false
|
|
5125
|
+
});
|
|
5126
|
+
if (setupLlm) {
|
|
5127
|
+
console.log(" Get a free key at https://openrouter.ai/keys");
|
|
5128
|
+
const llmKey = await input({
|
|
5129
|
+
message: " OpenRouter API key:",
|
|
5130
|
+
validate: (v) => v.trim().startsWith("sk-or-") ? true : 'Key must start with "sk-or-"'
|
|
5131
|
+
});
|
|
5132
|
+
saveLlmAuth(llmKey.trim());
|
|
5133
|
+
console.log(" OpenRouter key saved to ~/.config/hog/auth.json");
|
|
5134
|
+
} else {
|
|
5135
|
+
console.log(" Skipped. You can add it later: hog config ai:set-key");
|
|
5136
|
+
}
|
|
5091
5137
|
const existingConfig = configExists ? loadFullConfig() : void 0;
|
|
5092
5138
|
const config2 = {
|
|
5093
5139
|
version: 3,
|
|
@@ -5510,7 +5556,7 @@ function resolveProjectId(projectId) {
|
|
|
5510
5556
|
process.exit(1);
|
|
5511
5557
|
}
|
|
5512
5558
|
var program = new Command();
|
|
5513
|
-
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.
|
|
5559
|
+
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.4.0").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
|
|
5514
5560
|
const opts = thisCommand.opts();
|
|
5515
5561
|
if (opts.json) setFormat("json");
|
|
5516
5562
|
if (opts.human) setFormat("human");
|
|
@@ -5789,6 +5835,61 @@ config.command("ticktick:disable").description("Disable TickTick integration in
|
|
|
5789
5835
|
printSuccess("TickTick integration disabled. Board will no longer show TickTick tasks.");
|
|
5790
5836
|
}
|
|
5791
5837
|
});
|
|
5838
|
+
config.command("ai:set-key <key>").description("Store an OpenRouter API key for AI-enhanced issue creation (I key on board)").action((key) => {
|
|
5839
|
+
if (!key.startsWith("sk-or-")) {
|
|
5840
|
+
console.error('Error: key must start with "sk-or-". Get one at https://openrouter.ai/keys');
|
|
5841
|
+
process.exit(1);
|
|
5842
|
+
}
|
|
5843
|
+
saveLlmAuth(key);
|
|
5844
|
+
if (useJson()) {
|
|
5845
|
+
jsonOut({ ok: true, message: "OpenRouter key saved" });
|
|
5846
|
+
} else {
|
|
5847
|
+
printSuccess("OpenRouter key saved to ~/.config/hog/auth.json");
|
|
5848
|
+
console.log(" Press I on the board to create issues with natural language.");
|
|
5849
|
+
}
|
|
5850
|
+
});
|
|
5851
|
+
config.command("ai:clear-key").description("Remove the stored OpenRouter API key").action(() => {
|
|
5852
|
+
const existing = getLlmAuth();
|
|
5853
|
+
if (!existing) {
|
|
5854
|
+
if (useJson()) {
|
|
5855
|
+
jsonOut({ ok: true, message: "No key was stored" });
|
|
5856
|
+
} else {
|
|
5857
|
+
console.log("No OpenRouter key stored.");
|
|
5858
|
+
}
|
|
5859
|
+
return;
|
|
5860
|
+
}
|
|
5861
|
+
clearLlmAuth();
|
|
5862
|
+
if (useJson()) {
|
|
5863
|
+
jsonOut({ ok: true, message: "OpenRouter key removed" });
|
|
5864
|
+
} else {
|
|
5865
|
+
printSuccess("OpenRouter key removed from ~/.config/hog/auth.json");
|
|
5866
|
+
}
|
|
5867
|
+
});
|
|
5868
|
+
config.command("ai:status").description("Show whether AI-enhanced issue creation is available and which source provides it").action(() => {
|
|
5869
|
+
const envOr = process.env["OPENROUTER_API_KEY"];
|
|
5870
|
+
const envAnt = process.env["ANTHROPIC_API_KEY"];
|
|
5871
|
+
const stored = getLlmAuth();
|
|
5872
|
+
if (useJson()) {
|
|
5873
|
+
jsonOut({
|
|
5874
|
+
ok: true,
|
|
5875
|
+
data: {
|
|
5876
|
+
active: !!(envOr ?? envAnt ?? stored),
|
|
5877
|
+
source: envOr ? "env:OPENROUTER_API_KEY" : envAnt ? "env:ANTHROPIC_API_KEY" : stored ? "config:auth.json" : null,
|
|
5878
|
+
provider: envOr ? "openrouter" : envAnt ? "anthropic" : stored ? "openrouter" : null
|
|
5879
|
+
}
|
|
5880
|
+
});
|
|
5881
|
+
} else if (envOr) {
|
|
5882
|
+
console.log("AI: active (source: OPENROUTER_API_KEY env var, provider: openrouter)");
|
|
5883
|
+
} else if (envAnt) {
|
|
5884
|
+
console.log("AI: active (source: ANTHROPIC_API_KEY env var, provider: anthropic)");
|
|
5885
|
+
} else if (stored) {
|
|
5886
|
+
console.log("AI: active (source: ~/.config/hog/auth.json, provider: openrouter)");
|
|
5887
|
+
} else {
|
|
5888
|
+
console.log("AI: off \u2014 heuristic-only mode");
|
|
5889
|
+
console.log(" Enable with: hog config ai:set-key <sk-or-...>");
|
|
5890
|
+
console.log(" Or set env: export OPENROUTER_API_KEY=sk-or-...");
|
|
5891
|
+
}
|
|
5892
|
+
});
|
|
5792
5893
|
config.command("profile:create <name>").description("Create a board profile (copies current top-level config)").action((name) => {
|
|
5793
5894
|
const cfg = loadFullConfig();
|
|
5794
5895
|
if (cfg.profiles[name]) {
|