@gajae-code/coding-agent 0.6.4 → 0.7.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/CHANGELOG.md +51 -0
- package/dist/types/async/job-manager.d.ts +3 -1
- package/dist/types/cli/daemon-cli.d.ts +25 -0
- package/dist/types/cli/migrate-cli.d.ts +20 -0
- package/dist/types/cli/notify-cli.d.ts +23 -0
- package/dist/types/cli/setup-cli.d.ts +20 -1
- package/dist/types/commands/daemon.d.ts +41 -0
- package/dist/types/commands/migrate.d.ts +33 -0
- package/dist/types/commands/notify.d.ts +41 -0
- package/dist/types/config/keybindings.d.ts +4 -0
- package/dist/types/config/model-profile-activation.d.ts +12 -0
- package/dist/types/config/model-profiles.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +38 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/daemon/builtin.d.ts +20 -0
- package/dist/types/daemon/control-types.d.ts +57 -0
- package/dist/types/daemon/runtime.d.ts +25 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -2
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
- package/dist/types/gjc-runtime/session-layout.d.ts +59 -0
- package/dist/types/gjc-runtime/session-resolution.d.ts +47 -0
- package/dist/types/gjc-runtime/state-graph.d.ts +1 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +5 -4
- package/dist/types/gjc-runtime/state-schema.d.ts +2 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +38 -7
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +15 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +21 -4
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +1 -1
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +1 -1
- package/dist/types/harness-control-plane/storage.d.ts +2 -1
- package/dist/types/hooks/skill-state.d.ts +12 -4
- package/dist/types/migrate/action-planner.d.ts +11 -0
- package/dist/types/migrate/adapters/claude-code.d.ts +2 -0
- package/dist/types/migrate/adapters/codex.d.ts +5 -0
- package/dist/types/migrate/adapters/index.d.ts +45 -0
- package/dist/types/migrate/adapters/opencode.d.ts +2 -0
- package/dist/types/migrate/executor.d.ts +2 -0
- package/dist/types/migrate/mcp-mapper.d.ts +20 -0
- package/dist/types/migrate/report.d.ts +18 -0
- package/dist/types/migrate/skill-normalizer.d.ts +27 -0
- package/dist/types/migrate/types.d.ts +126 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/oauth-selector.d.ts +2 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -2
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/types.d.ts +7 -1
- package/dist/types/notifications/config-commands.d.ts +26 -0
- package/dist/types/notifications/config.d.ts +61 -0
- package/dist/types/notifications/helpers.d.ts +55 -0
- package/dist/types/notifications/html-format.d.ts +62 -0
- package/dist/types/notifications/index.d.ts +28 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +93 -0
- package/dist/types/notifications/telegram-cli.d.ts +19 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +11 -0
- package/dist/types/notifications/telegram-daemon-control.d.ts +56 -0
- package/dist/types/notifications/telegram-daemon.d.ts +276 -0
- package/dist/types/notifications/telegram-reference.d.ts +111 -0
- package/dist/types/notifications/threaded-inbound.d.ts +58 -0
- package/dist/types/notifications/threaded-render.d.ts +66 -0
- package/dist/types/notifications/topic-registry.d.ts +67 -0
- package/dist/types/research-plan/index.d.ts +1 -0
- package/dist/types/research-plan/ledger.d.ts +33 -0
- package/dist/types/rlm/artifacts.d.ts +1 -1
- package/dist/types/rlm/index.d.ts +12 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +26 -0
- package/dist/types/session/agent-session.d.ts +39 -2
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/setup/credential-auto-import.d.ts +63 -0
- package/dist/types/setup/credential-import.d.ts +3 -0
- package/dist/types/setup/host-plugin-setup.d.ts +39 -0
- package/dist/types/skill-state/active-state.d.ts +6 -11
- package/dist/types/skill-state/canonical-skills.d.ts +3 -0
- package/dist/types/skill-state/workflow-hud.d.ts +2 -0
- package/dist/types/task/spawn-gate.d.ts +1 -10
- package/dist/types/tools/ask-answer-registry.d.ts +13 -0
- package/dist/types/tools/index.d.ts +18 -0
- package/dist/types/tools/subagent.d.ts +3 -0
- package/package.json +7 -7
- package/scripts/build-binary.ts +3 -0
- package/src/async/job-manager.ts +5 -1
- package/src/cli/daemon-cli.ts +122 -0
- package/src/cli/migrate-cli.ts +106 -0
- package/src/cli/notify-cli.ts +274 -0
- package/src/cli/setup-cli.ts +173 -84
- package/src/cli.ts +3 -0
- package/src/commands/daemon.ts +47 -0
- package/src/commands/deep-interview.ts +2 -2
- package/src/commands/migrate.ts +46 -0
- package/src/commands/notify.ts +61 -0
- package/src/commands/setup.ts +11 -1
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +7 -3
- package/src/config/model-profile-activation.ts +74 -5
- package/src/config/model-profiles.ts +7 -4
- package/src/config/model-registry.ts +6 -3
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +29 -0
- package/src/coordinator/contract.ts +3 -0
- package/src/coordinator-mcp/policy.ts +10 -2
- package/src/coordinator-mcp/server.ts +270 -1
- package/src/daemon/builtin.ts +46 -0
- package/src/daemon/control-types.ts +65 -0
- package/src/daemon/runtime.ts +51 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +0 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +28 -24
- package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
- package/src/defaults/gjc/skills/team/SKILL.md +51 -47
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +33 -13
- package/src/extensibility/custom-commands/loader.ts +0 -7
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +8 -0
- package/src/extensibility/gjc-plugins/injection.ts +23 -4
- package/src/extensibility/gjc-plugins/state.ts +16 -1
- package/src/gjc-runtime/deep-interview-recorder.ts +51 -18
- package/src/gjc-runtime/deep-interview-runtime.ts +49 -23
- package/src/gjc-runtime/goal-mode-request.ts +26 -11
- package/src/gjc-runtime/launch-tmux.ts +6 -1
- package/src/gjc-runtime/ralplan-runtime.ts +79 -50
- package/src/gjc-runtime/session-layout.ts +180 -0
- package/src/gjc-runtime/session-resolution.ts +217 -0
- package/src/gjc-runtime/state-graph.ts +1 -2
- package/src/gjc-runtime/state-migrations.ts +1 -0
- package/src/gjc-runtime/state-runtime.ts +247 -124
- package/src/gjc-runtime/state-schema.ts +2 -0
- package/src/gjc-runtime/state-writer.ts +289 -41
- package/src/gjc-runtime/team-runtime.ts +43 -19
- package/src/gjc-runtime/tmux-sessions.ts +7 -1
- package/src/gjc-runtime/ultragoal-guard.ts +102 -4
- package/src/gjc-runtime/ultragoal-runtime.ts +226 -60
- package/src/gjc-runtime/workflow-command-ref.ts +1 -2
- package/src/gjc-runtime/workflow-manifest.generated.json +27 -2
- package/src/gjc-runtime/workflow-manifest.ts +12 -3
- package/src/goals/tools/goal-tool.ts +11 -2
- package/src/harness-control-plane/storage.ts +14 -4
- package/src/hooks/native-skill-hook.ts +38 -12
- package/src/hooks/skill-state.ts +178 -83
- package/src/internal-urls/docs-index.generated.ts +9 -6
- package/src/main.ts +30 -0
- package/src/migrate/action-planner.ts +318 -0
- package/src/migrate/adapters/claude-code.ts +39 -0
- package/src/migrate/adapters/codex.ts +70 -0
- package/src/migrate/adapters/index.ts +277 -0
- package/src/migrate/adapters/opencode.ts +52 -0
- package/src/migrate/executor.ts +81 -0
- package/src/migrate/mcp-mapper.ts +152 -0
- package/src/migrate/report.ts +104 -0
- package/src/migrate/skill-normalizer.ts +80 -0
- package/src/migrate/types.ts +163 -0
- package/src/modes/acp/acp-event-mapper.ts +1 -0
- package/src/modes/bridge/bridge-mode.ts +2 -2
- package/src/modes/components/custom-editor.ts +30 -20
- package/src/modes/components/hook-editor.ts +7 -2
- package/src/modes/components/oauth-selector.ts +19 -0
- package/src/modes/controllers/event-controller.ts +20 -0
- package/src/modes/controllers/selector-controller.ts +80 -17
- package/src/modes/interactive-mode.ts +6 -2
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/event-contract.ts +1 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +1 -0
- package/src/modes/shared/agent-wire/event-observation.ts +16 -0
- package/src/modes/shared/agent-wire/unattended-audit.ts +3 -2
- package/src/modes/shared/agent-wire/unattended-session.ts +22 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/utils/ui-helpers.ts +23 -0
- package/src/notifications/config-commands.ts +50 -0
- package/src/notifications/config.ts +107 -0
- package/src/notifications/helpers.ts +135 -0
- package/src/notifications/html-format.ts +389 -0
- package/src/notifications/index.ts +663 -0
- package/src/notifications/rate-limit-pool.ts +179 -0
- package/src/notifications/telegram-cli.ts +194 -0
- package/src/notifications/telegram-daemon-cli.ts +74 -0
- package/src/notifications/telegram-daemon-control.ts +370 -0
- package/src/notifications/telegram-daemon.ts +1370 -0
- package/src/notifications/telegram-reference.ts +335 -0
- package/src/notifications/threaded-inbound.ts +80 -0
- package/src/notifications/threaded-render.ts +155 -0
- package/src/notifications/topic-registry.ts +133 -0
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/task.md +1 -2
- package/src/research-plan/index.ts +1 -0
- package/src/research-plan/ledger.ts +177 -0
- package/src/rlm/artifacts.ts +12 -3
- package/src/rlm/index.ts +26 -0
- package/src/runtime-mcp/config-writer.ts +46 -0
- package/src/sdk.ts +16 -0
- package/src/session/agent-session.ts +128 -24
- package/src/session/auth-storage.ts +3 -0
- package/src/session/session-dump-format.ts +43 -2
- package/src/session/session-manager.ts +39 -5
- package/src/setup/credential-auto-import.ts +258 -0
- package/src/setup/credential-import.ts +17 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +10 -0
- package/src/setup/hermes-setup.ts +1 -1
- package/src/setup/host-plugin-setup.ts +142 -0
- package/src/skill-state/active-state.ts +72 -108
- package/src/skill-state/canonical-skills.ts +4 -0
- package/src/skill-state/deep-interview-mutation-guard.ts +28 -109
- package/src/skill-state/workflow-hud.ts +4 -2
- package/src/skill-state/workflow-state-contract.ts +3 -3
- package/src/slash-commands/builtin-registry.ts +4 -1
- package/src/task/agents.ts +1 -22
- package/src/task/executor.ts +5 -1
- package/src/task/index.ts +1 -41
- package/src/task/spawn-gate.ts +1 -38
- package/src/task/types.ts +1 -1
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/ask.ts +108 -16
- package/src/tools/computer.ts +58 -4
- package/src/tools/image-gen.ts +5 -8
- package/src/tools/index.ts +19 -0
- package/src/tools/inspect-image.ts +16 -11
- package/src/tools/subagent-render.ts +7 -0
- package/src/tools/subagent.ts +38 -7
- package/dist/types/extensibility/custom-commands/bundled/review/index.d.ts +0 -10
- package/src/extensibility/custom-commands/bundled/review/index.ts +0 -456
- package/src/prompts/agents/explore.md +0 -58
- package/src/prompts/agents/plan.md +0 -49
- package/src/prompts/agents/reviewer.md +0 -141
- package/src/prompts/agents/task.md +0 -16
- package/src/prompts/review-request.md +0 -70
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notify CLI command handlers.
|
|
3
|
+
*
|
|
4
|
+
* Handles `gjc notify` setup/status and the hidden daemon entrypoint.
|
|
5
|
+
*/
|
|
6
|
+
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { APP_NAME } from "@gajae-code/utils";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { Settings } from "../config/settings";
|
|
10
|
+
import { getNotificationConfig, maskToken } from "../notifications/config";
|
|
11
|
+
|
|
12
|
+
export type NotifyAction = "setup" | "status" | "daemon-internal";
|
|
13
|
+
|
|
14
|
+
export interface NotifyCommandArgs {
|
|
15
|
+
action: NotifyAction;
|
|
16
|
+
smoke?: boolean;
|
|
17
|
+
rawArgs: string[];
|
|
18
|
+
token?: string;
|
|
19
|
+
chatId?: string;
|
|
20
|
+
redact?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface NotifyCommandDeps {
|
|
24
|
+
fetchImpl?: typeof fetch;
|
|
25
|
+
apiBase?: string;
|
|
26
|
+
settings?: Settings;
|
|
27
|
+
setupToken?: string;
|
|
28
|
+
pollTimeoutMs?: number;
|
|
29
|
+
pollIntervalMs?: number;
|
|
30
|
+
setupChatId?: string;
|
|
31
|
+
setupRedact?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface TelegramApiResponse<T> {
|
|
35
|
+
ok: boolean;
|
|
36
|
+
result?: T;
|
|
37
|
+
description?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface TelegramUpdate {
|
|
41
|
+
update_id: number;
|
|
42
|
+
message?: {
|
|
43
|
+
chat?: {
|
|
44
|
+
id?: number | string;
|
|
45
|
+
type?: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const DEFAULT_API_BASE = "https://api.telegram.org";
|
|
51
|
+
const DEFAULT_POLL_TIMEOUT_MS = 60_000;
|
|
52
|
+
const DEFAULT_POLL_INTERVAL_MS = 1_000;
|
|
53
|
+
|
|
54
|
+
export function parseNotifyArgs(args: string[]): NotifyCommandArgs | undefined {
|
|
55
|
+
if (args.length === 0 || args[0] !== "notify") {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const action = args[1];
|
|
60
|
+
if (action === "setup" || action === "status") {
|
|
61
|
+
const rest = args.slice(2);
|
|
62
|
+
const flag = (name: string): string | undefined => {
|
|
63
|
+
const i = rest.indexOf(name);
|
|
64
|
+
return i >= 0 ? rest[i + 1] : undefined;
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
action,
|
|
68
|
+
rawArgs: rest,
|
|
69
|
+
token: flag("--token"),
|
|
70
|
+
chatId: flag("--chat-id"),
|
|
71
|
+
redact: rest.includes("--redact"),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (action === "daemon-internal") {
|
|
75
|
+
return {
|
|
76
|
+
action,
|
|
77
|
+
smoke: args.slice(2).includes("--smoke"),
|
|
78
|
+
rawArgs: args.slice(2),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { action: "status", rawArgs: args.slice(1) };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function runNotifyCommand(cmd: NotifyCommandArgs, deps: NotifyCommandDeps = {}): Promise<void> {
|
|
86
|
+
switch (cmd.action) {
|
|
87
|
+
case "setup":
|
|
88
|
+
await runSetup({
|
|
89
|
+
...deps,
|
|
90
|
+
setupToken: deps.setupToken ?? cmd.token,
|
|
91
|
+
setupChatId: deps.setupChatId ?? cmd.chatId,
|
|
92
|
+
setupRedact: deps.setupRedact ?? cmd.redact,
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
case "status":
|
|
96
|
+
await runStatus(deps);
|
|
97
|
+
return;
|
|
98
|
+
case "daemon-internal": {
|
|
99
|
+
const m = await import("../notifications/telegram-daemon-cli");
|
|
100
|
+
if (cmd.smoke) {
|
|
101
|
+
await m.runDaemonSmoke();
|
|
102
|
+
} else {
|
|
103
|
+
await m.runDaemonInternal(cmd.rawArgs);
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function getSettings(deps: NotifyCommandDeps): Promise<Settings> {
|
|
111
|
+
return deps.settings ?? (await Settings.init());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function runSetup(deps: NotifyCommandDeps): Promise<void> {
|
|
115
|
+
const settings = await getSettings(deps);
|
|
116
|
+
const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
|
|
117
|
+
const apiBase = deps.apiBase ?? DEFAULT_API_BASE;
|
|
118
|
+
const token = deps.setupToken ?? (await promptForToken());
|
|
119
|
+
if (!token.trim()) {
|
|
120
|
+
throw new Error("Telegram bot token is required.");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await callTelegram(fetchImpl, apiBase, token, "getMe", {});
|
|
124
|
+
process.stdout.write(
|
|
125
|
+
"Token validated. Message your bot now from the private Telegram chat to pair notifications.\n",
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
let chatId: string;
|
|
129
|
+
if (deps.setupChatId?.trim()) {
|
|
130
|
+
chatId = deps.setupChatId.trim();
|
|
131
|
+
process.stdout.write(`Using provided chat id ${chatId} (non-interactive).\n`);
|
|
132
|
+
} else {
|
|
133
|
+
const stale = await getUpdates(fetchImpl, apiBase, token, { timeout: 0, allowed_updates: ["message"] });
|
|
134
|
+
const offset = nextOffset(stale);
|
|
135
|
+
chatId = await waitForPrivateChat(fetchImpl, apiBase, token, {
|
|
136
|
+
offset,
|
|
137
|
+
pollTimeoutMs: deps.pollTimeoutMs ?? DEFAULT_POLL_TIMEOUT_MS,
|
|
138
|
+
pollIntervalMs: deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
settings.set("notifications.telegram.botToken", token);
|
|
143
|
+
settings.set("notifications.telegram.chatId", chatId);
|
|
144
|
+
settings.set("notifications.enabled", true);
|
|
145
|
+
if (deps.setupRedact) settings.set("notifications.redact", true);
|
|
146
|
+
await settings.flush();
|
|
147
|
+
|
|
148
|
+
process.stdout.write(`Notifications enabled. botToken=${maskToken(token)} chatId=${chatId}\n`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function promptForToken(): Promise<string> {
|
|
152
|
+
if (!process.stdin.isTTY) {
|
|
153
|
+
throw new Error("notify setup requires an interactive TTY unless setupToken is injected.");
|
|
154
|
+
}
|
|
155
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
156
|
+
try {
|
|
157
|
+
return (await rl.question("Telegram BotFather token: ")).trim();
|
|
158
|
+
} finally {
|
|
159
|
+
rl.close();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function runStatus(deps: NotifyCommandDeps): Promise<void> {
|
|
164
|
+
const settings = await getSettings(deps);
|
|
165
|
+
const cfg = getNotificationConfig(settings);
|
|
166
|
+
process.stdout.write(
|
|
167
|
+
`${chalk.bold("Notifications")}\n` +
|
|
168
|
+
` enabled: ${cfg.enabled}\n` +
|
|
169
|
+
` botToken: ${maskToken(cfg.botToken)}\n` +
|
|
170
|
+
` chatId: ${cfg.chatId ?? "(unset)"}\n` +
|
|
171
|
+
` redact: ${cfg.redact}\n`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function waitForPrivateChat(
|
|
176
|
+
fetchImpl: typeof fetch,
|
|
177
|
+
apiBase: string,
|
|
178
|
+
token: string,
|
|
179
|
+
opts: { offset: number | undefined; pollTimeoutMs: number; pollIntervalMs: number },
|
|
180
|
+
): Promise<string> {
|
|
181
|
+
const deadline = Date.now() + opts.pollTimeoutMs;
|
|
182
|
+
let offset = opts.offset;
|
|
183
|
+
let sawRejectedChatType: string | undefined;
|
|
184
|
+
|
|
185
|
+
while (Date.now() <= deadline) {
|
|
186
|
+
const updates = await getUpdates(fetchImpl, apiBase, token, { offset, timeout: 0, allowed_updates: ["message"] });
|
|
187
|
+
offset = nextOffset(updates, offset);
|
|
188
|
+
for (const update of updates) {
|
|
189
|
+
const chat = update.message?.chat;
|
|
190
|
+
if (!chat) continue;
|
|
191
|
+
if (chat.type === "private" && chat.id !== undefined) {
|
|
192
|
+
return String(chat.id);
|
|
193
|
+
}
|
|
194
|
+
if (chat.type === "group" || chat.type === "supergroup" || chat.type === "channel") {
|
|
195
|
+
sawRejectedChatType = chat.type;
|
|
196
|
+
process.stderr.write(
|
|
197
|
+
`Rejected ${chat.type} chat. Pairing requires a private Telegram chat with the bot.\n`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (opts.pollIntervalMs > 0) {
|
|
202
|
+
await new Promise(resolve =>
|
|
203
|
+
setTimeout(resolve, Math.min(opts.pollIntervalMs, Math.max(0, deadline - Date.now()))),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (sawRejectedChatType) {
|
|
209
|
+
throw new Error(`Pairing rejected ${sawRejectedChatType} chat; message the bot from a private chat.`);
|
|
210
|
+
}
|
|
211
|
+
throw new Error("Timed out waiting for a private Telegram message to pair notifications.");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function nextOffset(updates: TelegramUpdate[], fallback?: number): number | undefined {
|
|
215
|
+
let max = fallback === undefined ? undefined : fallback - 1;
|
|
216
|
+
for (const update of updates) {
|
|
217
|
+
if (typeof update.update_id === "number" && (max === undefined || update.update_id > max)) {
|
|
218
|
+
max = update.update_id;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return max === undefined ? fallback : max + 1;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function getUpdates(
|
|
225
|
+
fetchImpl: typeof fetch,
|
|
226
|
+
apiBase: string,
|
|
227
|
+
token: string,
|
|
228
|
+
params: Record<string, unknown>,
|
|
229
|
+
): Promise<TelegramUpdate[]> {
|
|
230
|
+
return await callTelegram<TelegramUpdate[]>(fetchImpl, apiBase, token, "getUpdates", params);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function callTelegram<T>(
|
|
234
|
+
fetchImpl: typeof fetch,
|
|
235
|
+
apiBase: string,
|
|
236
|
+
token: string,
|
|
237
|
+
method: string,
|
|
238
|
+
body: Record<string, unknown>,
|
|
239
|
+
): Promise<T> {
|
|
240
|
+
const response = await fetchImpl(`${apiBase.replace(/\/$/, "")}/bot${token}/${method}`, {
|
|
241
|
+
method: "POST",
|
|
242
|
+
headers: { "content-type": "application/json" },
|
|
243
|
+
body: JSON.stringify(body),
|
|
244
|
+
});
|
|
245
|
+
let payload: TelegramApiResponse<T>;
|
|
246
|
+
try {
|
|
247
|
+
payload = (await response.json()) as TelegramApiResponse<T>;
|
|
248
|
+
} catch {
|
|
249
|
+
throw new Error(`Telegram ${method} returned invalid JSON.`);
|
|
250
|
+
}
|
|
251
|
+
if (!response.ok || !payload.ok) {
|
|
252
|
+
throw new Error(`Telegram ${method} failed: ${payload.description ?? response.statusText}`);
|
|
253
|
+
}
|
|
254
|
+
return payload.result as T;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function printNotifyHelp(): void {
|
|
258
|
+
process.stdout.write(`${chalk.bold(`${APP_NAME} notify`)} - Configure Telegram notifications
|
|
259
|
+
|
|
260
|
+
${chalk.bold("Usage:")}
|
|
261
|
+
${APP_NAME} notify setup
|
|
262
|
+
${APP_NAME} notify setup --token <botToken> --chat-id <chatId> [--redact]
|
|
263
|
+
${APP_NAME} notify status
|
|
264
|
+
|
|
265
|
+
${chalk.bold("Subcommands:")}
|
|
266
|
+
setup Pair a Telegram bot token with a private chat
|
|
267
|
+
status Show notification configuration without secrets
|
|
268
|
+
|
|
269
|
+
${chalk.bold("Examples:")}
|
|
270
|
+
${APP_NAME} notify setup
|
|
271
|
+
${APP_NAME} notify setup --token <botToken> --chat-id <chatId> [--redact]
|
|
272
|
+
${APP_NAME} notify status
|
|
273
|
+
`);
|
|
274
|
+
}
|
package/src/cli/setup-cli.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { createInterface } from "node:readline/promises";
|
|
9
|
-
import { SqliteAuthCredentialStore } from "@gajae-code/ai";
|
|
9
|
+
import { AuthStorage, SqliteAuthCredentialStore } from "@gajae-code/ai";
|
|
10
10
|
import { $which, APP_NAME, getAgentDbPath, getPythonEnvDir } from "@gajae-code/utils";
|
|
11
11
|
import { $ } from "bun";
|
|
12
12
|
import chalk from "chalk";
|
|
@@ -17,13 +17,15 @@ import {
|
|
|
17
17
|
readGjcManagedCodexHooksStatus,
|
|
18
18
|
} from "../hooks/codex-native-hooks-config";
|
|
19
19
|
import { theme } from "../modes/theme/theme";
|
|
20
|
-
import {
|
|
20
|
+
import { formatCredentialAutoImportResult, runExternalCredentialAutoImport } from "../setup/credential-auto-import";
|
|
21
|
+
import { filterAutoImportOAuthCredentials, formatDiscoverySummary } from "../setup/credential-import";
|
|
21
22
|
import {
|
|
22
23
|
formatHermesSetupResult,
|
|
23
24
|
type HermesSetupFlags,
|
|
24
25
|
hermesSetupExitCode,
|
|
25
26
|
runHermesSetup,
|
|
26
27
|
} from "../setup/hermes-setup";
|
|
28
|
+
import { buildHostPluginSetup, formatHostPluginSetup, type HostPluginKind } from "../setup/host-plugin-setup";
|
|
27
29
|
import {
|
|
28
30
|
addApiCompatibleProvider,
|
|
29
31
|
formatProviderPresetList,
|
|
@@ -31,7 +33,16 @@ import {
|
|
|
31
33
|
parseProviderCompatibility,
|
|
32
34
|
} from "../setup/provider-onboarding";
|
|
33
35
|
|
|
34
|
-
export type SetupComponent =
|
|
36
|
+
export type SetupComponent =
|
|
37
|
+
| "claude"
|
|
38
|
+
| "codex"
|
|
39
|
+
| "credentials"
|
|
40
|
+
| "defaults"
|
|
41
|
+
| "hermes"
|
|
42
|
+
| "hooks"
|
|
43
|
+
| "provider"
|
|
44
|
+
| "python"
|
|
45
|
+
| "stt";
|
|
35
46
|
|
|
36
47
|
export interface SetupCommandArgs {
|
|
37
48
|
component: SetupComponent;
|
|
@@ -63,10 +74,21 @@ export interface SetupCommandArgs {
|
|
|
63
74
|
profileDir?: string;
|
|
64
75
|
yes?: boolean;
|
|
65
76
|
dryRun?: boolean;
|
|
77
|
+
keychain?: boolean;
|
|
66
78
|
};
|
|
67
79
|
}
|
|
68
80
|
|
|
69
|
-
const VALID_COMPONENTS: SetupComponent[] = [
|
|
81
|
+
const VALID_COMPONENTS: SetupComponent[] = [
|
|
82
|
+
"claude",
|
|
83
|
+
"codex",
|
|
84
|
+
"credentials",
|
|
85
|
+
"defaults",
|
|
86
|
+
"hermes",
|
|
87
|
+
"hooks",
|
|
88
|
+
"provider",
|
|
89
|
+
"python",
|
|
90
|
+
"stt",
|
|
91
|
+
];
|
|
70
92
|
|
|
71
93
|
function hasProviderSetupFlags(flags: SetupCommandArgs["flags"]): boolean {
|
|
72
94
|
return (
|
|
@@ -123,6 +145,8 @@ export function parseSetupArgs(args: string[]): SetupCommandArgs | undefined {
|
|
|
123
145
|
flags.yes = true;
|
|
124
146
|
} else if (arg === "--dry-run") {
|
|
125
147
|
flags.dryRun = true;
|
|
148
|
+
} else if (arg === "--keychain") {
|
|
149
|
+
flags.keychain = true;
|
|
126
150
|
} else if (arg === "--root") {
|
|
127
151
|
flags.root = [...(flags.root ?? []), args[++i] ?? ""];
|
|
128
152
|
} else if (arg === "--repo") {
|
|
@@ -235,6 +259,12 @@ async function checkPythonSetup(): Promise<PythonCheckResult> {
|
|
|
235
259
|
export async function runSetupCommand(cmd: SetupCommandArgs): Promise<void> {
|
|
236
260
|
rejectProviderFlagsOutsideProvider(cmd.component, cmd.flags);
|
|
237
261
|
switch (cmd.component) {
|
|
262
|
+
case "claude":
|
|
263
|
+
handleHostPluginSetup("claude", cmd.flags);
|
|
264
|
+
break;
|
|
265
|
+
case "codex":
|
|
266
|
+
handleHostPluginSetup("codex", cmd.flags);
|
|
267
|
+
break;
|
|
238
268
|
case "defaults":
|
|
239
269
|
await handleDefaultsSetup(cmd.flags);
|
|
240
270
|
break;
|
|
@@ -279,6 +309,22 @@ async function handleHermesSetup(flags: HermesSetupFlags): Promise<void> {
|
|
|
279
309
|
process.exit(hermesSetupExitCode(error));
|
|
280
310
|
}
|
|
281
311
|
}
|
|
312
|
+
|
|
313
|
+
function handleHostPluginSetup(host: HostPluginKind, flags: SetupCommandArgs["flags"]): void {
|
|
314
|
+
const result = buildHostPluginSetup(host, {
|
|
315
|
+
json: flags.json,
|
|
316
|
+
check: flags.check,
|
|
317
|
+
root: flags.root,
|
|
318
|
+
repo: flags.repo,
|
|
319
|
+
});
|
|
320
|
+
if (flags.json) {
|
|
321
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const label = host === "claude" ? "Claude Code" : "Codex";
|
|
325
|
+
process.stdout.write(`${chalk.green(`${theme.status.success} ${label} plugin setup ready`)}\n`);
|
|
326
|
+
process.stdout.write(`${chalk.dim(formatHostPluginSetup(result))}\n`);
|
|
327
|
+
}
|
|
282
328
|
async function handleProviderSetup(flags: {
|
|
283
329
|
json?: boolean;
|
|
284
330
|
force?: boolean;
|
|
@@ -516,99 +562,141 @@ async function confirmImport(count: number): Promise<boolean> {
|
|
|
516
562
|
* gjc credential store after a redacted preview + confirmation. Falls back to
|
|
517
563
|
* manual-setup guidance when nothing importable is found.
|
|
518
564
|
*/
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
kind: c.kind,
|
|
525
|
-
source: c.source,
|
|
526
|
-
identity: c.identity,
|
|
527
|
-
expiresAt: c.expiresAt,
|
|
528
|
-
redactedToken: c.redactedToken,
|
|
529
|
-
})),
|
|
530
|
-
skipped: result.skipped,
|
|
531
|
-
environment: result.environment,
|
|
532
|
-
};
|
|
565
|
+
export interface CredentialsSetupDependencies {
|
|
566
|
+
openStore?: typeof SqliteAuthCredentialStore.open;
|
|
567
|
+
createAuthStorage?: (store: Awaited<ReturnType<typeof SqliteAuthCredentialStore.open>>) => AuthStorage;
|
|
568
|
+
discover?: Parameters<typeof runExternalCredentialAutoImport>[0]["discover"];
|
|
569
|
+
}
|
|
533
570
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
571
|
+
export async function handleCredentialsSetup(
|
|
572
|
+
flags: {
|
|
573
|
+
json?: boolean;
|
|
574
|
+
yes?: boolean;
|
|
575
|
+
dryRun?: boolean;
|
|
576
|
+
keychain?: boolean;
|
|
577
|
+
},
|
|
578
|
+
deps: CredentialsSetupDependencies = {},
|
|
579
|
+
): Promise<void> {
|
|
580
|
+
const discoveryOptions = flags.keychain ? undefined : { readClaudeKeychain: async () => null };
|
|
581
|
+
const store = await (deps.openStore ?? SqliteAuthCredentialStore.open)(getAgentDbPath());
|
|
582
|
+
const authStorage = deps.createAuthStorage?.(store) ?? new AuthStorage(store);
|
|
583
|
+
await authStorage.reload();
|
|
584
|
+
try {
|
|
585
|
+
const preview = await runExternalCredentialAutoImport({
|
|
586
|
+
authStorage: {
|
|
587
|
+
importCredentialIfAbsent: async () => ({
|
|
588
|
+
inserted: false,
|
|
589
|
+
reason: "skipped-existing",
|
|
590
|
+
provider: "",
|
|
591
|
+
entries: [],
|
|
592
|
+
}),
|
|
593
|
+
},
|
|
594
|
+
discover: deps.discover,
|
|
595
|
+
discoveryOptions,
|
|
596
|
+
trigger: "setup-cli",
|
|
597
|
+
});
|
|
598
|
+
const result = preview.discovery ?? { importable: [], skipped: [], environment: [] };
|
|
599
|
+
const candidates = filterAutoImportOAuthCredentials(result.importable);
|
|
600
|
+
const filteredResult = { ...result, importable: candidates };
|
|
601
|
+
const redactedPlan = {
|
|
602
|
+
importable: candidates.map(c => ({
|
|
603
|
+
provider: c.provider,
|
|
604
|
+
kind: c.kind,
|
|
605
|
+
source: c.source,
|
|
606
|
+
identity: c.identity,
|
|
607
|
+
expiresAt: c.expiresAt,
|
|
608
|
+
redactedToken: c.redactedToken,
|
|
609
|
+
})),
|
|
610
|
+
skipped: result.skipped,
|
|
611
|
+
environment: result.environment,
|
|
612
|
+
keychainChecked: flags.keychain === true,
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
if (!flags.keychain && !flags.json) {
|
|
616
|
+
process.stdout.write(chalk.dim("Claude Keychain not checked (pass --keychain to include it)\n"));
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (candidates.length === 0) {
|
|
620
|
+
if (flags.json) {
|
|
621
|
+
process.stdout.write(`${JSON.stringify({ ...redactedPlan, imported: [] })}\n`);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
for (const line of formatDiscoverySummary(filteredResult)) process.stdout.write(` ${line}\n`);
|
|
625
|
+
process.stdout.write(
|
|
626
|
+
chalk.yellow(
|
|
627
|
+
`\nNo importable Claude/Codex credentials found. Continue with manual setup:\n` +
|
|
628
|
+
` ${APP_NAME} setup provider (add an API-compatible provider)\n` +
|
|
629
|
+
` ${APP_NAME} (then /login) (interactive OAuth/subscription login)\n`,
|
|
630
|
+
),
|
|
631
|
+
);
|
|
537
632
|
return;
|
|
538
633
|
}
|
|
539
|
-
for (const line of formatDiscoverySummary(result)) process.stdout.write(` ${line}\n`);
|
|
540
|
-
process.stdout.write(
|
|
541
|
-
chalk.yellow(
|
|
542
|
-
`\nNo importable Claude/Codex credentials found. Continue with manual setup:\n` +
|
|
543
|
-
` ${APP_NAME} setup provider (add an API-compatible provider)\n` +
|
|
544
|
-
` ${APP_NAME} (then /login) (interactive OAuth/subscription login)\n`,
|
|
545
|
-
),
|
|
546
|
-
);
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
634
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
635
|
+
if (!flags.json) {
|
|
636
|
+
process.stdout.write(chalk.bold("Discovered credentials (redacted):\n"));
|
|
637
|
+
for (const line of formatDiscoverySummary(filteredResult)) process.stdout.write(` ${line}\n`);
|
|
638
|
+
}
|
|
554
639
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
640
|
+
if (flags.dryRun) {
|
|
641
|
+
if (flags.json) process.stdout.write(`${JSON.stringify({ ...redactedPlan, dryRun: true, imported: [] })}\n`);
|
|
642
|
+
else process.stdout.write(chalk.dim(`\nDry run — no credentials imported.\n`));
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const confirmed = flags.yes || (await confirmImport(candidates.length));
|
|
647
|
+
if (!confirmed) {
|
|
648
|
+
if (flags.json) {
|
|
649
|
+
process.stdout.write(`${JSON.stringify({ ...redactedPlan, imported: [] })}\n`);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
process.stdout.write(chalk.dim(`\nImport cancelled. Re-run with --yes to import non-interactively.\n`));
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const summary = await runExternalCredentialAutoImport({
|
|
657
|
+
authStorage,
|
|
658
|
+
discover: deps.discover,
|
|
659
|
+
discoveryOptions,
|
|
660
|
+
trigger: "setup-cli",
|
|
661
|
+
});
|
|
560
662
|
|
|
561
|
-
const confirmed = flags.yes || (await confirmImport(result.importable.length));
|
|
562
|
-
if (!confirmed) {
|
|
563
663
|
if (flags.json) {
|
|
564
|
-
process.stdout.write(
|
|
664
|
+
process.stdout.write(
|
|
665
|
+
`${JSON.stringify({
|
|
666
|
+
...redactedPlan,
|
|
667
|
+
imported: summary.imported.map(c => ({ provider: c.provider, kind: c.kind, source: c.source })),
|
|
668
|
+
skippedImport: summary.skipped.map(s => ({
|
|
669
|
+
provider: s.credential.provider,
|
|
670
|
+
source: s.credential.source,
|
|
671
|
+
reason: s.reason,
|
|
672
|
+
})),
|
|
673
|
+
failed: summary.failures.map(f => ({
|
|
674
|
+
provider: f.credential?.provider,
|
|
675
|
+
source: f.credential?.source ?? f.source,
|
|
676
|
+
error: f.failureClass,
|
|
677
|
+
})),
|
|
678
|
+
})}\n`,
|
|
679
|
+
);
|
|
680
|
+
if (summary.failures.length > 0) process.exitCode = 1;
|
|
565
681
|
return;
|
|
566
682
|
}
|
|
567
|
-
process.stdout.write(chalk.dim(`\nImport cancelled. Re-run with --yes to import non-interactively.\n`));
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
683
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
)
|
|
684
|
+
for (const credential of summary.imported) {
|
|
685
|
+
process.stdout.write(
|
|
686
|
+
`${chalk.green(`${theme.status.success} imported`)} ${formatCredentialSummaryLine(credential)}\n`,
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
for (const line of formatCredentialAutoImportResult({ ...summary, imported: [], skipped: [] })) {
|
|
690
|
+
process.stdout.write(`${chalk.dim(line)}\n`);
|
|
691
|
+
}
|
|
692
|
+
if (summary.failures.length > 0) {
|
|
693
|
+
process.exitCode = 1;
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
process.stdout.write(chalk.dim(`\nCredentials saved to ${getAgentDbPath()}\n`));
|
|
577
697
|
} finally {
|
|
578
698
|
store.close();
|
|
579
699
|
}
|
|
580
|
-
|
|
581
|
-
if (flags.json) {
|
|
582
|
-
process.stdout.write(
|
|
583
|
-
`${JSON.stringify({
|
|
584
|
-
...redactedPlan,
|
|
585
|
-
imported: summary.imported.map(c => ({ provider: c.provider, kind: c.kind, source: c.source })),
|
|
586
|
-
failed: summary.failed.map(f => ({
|
|
587
|
-
provider: f.credential.provider,
|
|
588
|
-
source: f.credential.source,
|
|
589
|
-
error: f.error,
|
|
590
|
-
})),
|
|
591
|
-
})}\n`,
|
|
592
|
-
);
|
|
593
|
-
if (summary.failed.length > 0) process.exitCode = 1;
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
for (const credential of summary.imported) {
|
|
598
|
-
process.stdout.write(
|
|
599
|
-
`${chalk.green(`${theme.status.success} imported`)} ${formatCredentialSummaryLine(credential)}\n`,
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
|
-
for (const failure of summary.failed) {
|
|
603
|
-
process.stdout.write(
|
|
604
|
-
`${chalk.red(`${theme.status.error} failed`)} ${failure.credential.provider} (${failure.credential.source}): ${failure.error}\n`,
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
if (summary.failed.length > 0) {
|
|
608
|
-
process.exitCode = 1;
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
process.stdout.write(chalk.dim(`\nCredentials saved to ${getAgentDbPath()}\n`));
|
|
612
700
|
}
|
|
613
701
|
|
|
614
702
|
function formatCredentialSummaryLine(credential: { provider: string; kind: string; source: string }): string {
|
|
@@ -669,6 +757,7 @@ ${chalk.bold("Options:")}
|
|
|
669
757
|
--profile-dir Hermes profile directory for full setup install
|
|
670
758
|
--dry-run Preview discovered credentials without importing (credentials)
|
|
671
759
|
-y, --yes Import discovered credentials without an interactive prompt (credentials)
|
|
760
|
+
--keychain Include Claude macOS Keychain when discovering credentials
|
|
672
761
|
|
|
673
762
|
${chalk.bold("Examples:")}
|
|
674
763
|
${APP_NAME} setup Install bundled GJC default workflow skills
|
package/src/cli.ts
CHANGED
|
@@ -35,6 +35,8 @@ const commands: CommandEntry[] = [
|
|
|
35
35
|
{ name: "gc", load: () => import("./commands/gc").then(m => m.default) },
|
|
36
36
|
{ name: "ralplan", load: () => import("./commands/ralplan").then(m => m.default) },
|
|
37
37
|
{ name: "config", load: () => import("./commands/config").then(m => m.default) },
|
|
38
|
+
{ name: "notify", load: () => import("./commands/notify").then(m => m.default) },
|
|
39
|
+
{ name: "daemon", load: () => import("./commands/daemon").then(m => m.default) },
|
|
38
40
|
{ name: "web-search", aliases: ["q"], load: () => import("./commands/web-search").then(m => m.default) },
|
|
39
41
|
{ name: "mcp-serve", load: () => import("./commands/mcp-serve").then(m => m.default) },
|
|
40
42
|
{
|
|
@@ -43,6 +45,7 @@ const commands: CommandEntry[] = [
|
|
|
43
45
|
load: () => import("./commands/contribution-prep").then(m => m.default),
|
|
44
46
|
},
|
|
45
47
|
{ name: "deep-interview", load: () => import("./commands/deep-interview").then(m => m.default) },
|
|
48
|
+
{ name: "migrate", load: () => import("./commands/migrate").then(m => m.default) },
|
|
46
49
|
{ name: "rlm", load: () => import("./commands/rlm").then(m => m.default) },
|
|
47
50
|
{ name: "update", load: () => import("./commands/update").then(m => m.default) },
|
|
48
51
|
{ name: "launch", load: () => import("./commands/launch").then(m => m.default) },
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manage GJC background daemons (status/list/stop/reload).
|
|
3
|
+
*/
|
|
4
|
+
import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
5
|
+
import { type DaemonCliAction, type DaemonCommandArgs, runDaemonCommand } from "../cli/daemon-cli";
|
|
6
|
+
import type { DaemonKind } from "../daemon/control-types";
|
|
7
|
+
import { initTheme } from "../modes/theme/theme";
|
|
8
|
+
|
|
9
|
+
const ACTIONS: DaemonCliAction[] = ["list", "status", "stop", "reload"];
|
|
10
|
+
|
|
11
|
+
export default class Daemon extends Command {
|
|
12
|
+
static description = "Manage GJC background daemons (status, list, stop, reload)";
|
|
13
|
+
|
|
14
|
+
static args = {
|
|
15
|
+
action: Args.string({ description: "Daemon action", required: false, options: ACTIONS }),
|
|
16
|
+
kind: Args.string({ description: "Daemon kind(s) to target", required: false, multiple: true }),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
static flags = {
|
|
20
|
+
all: Flags.boolean({ description: "Target all registered daemon kinds" }),
|
|
21
|
+
json: Flags.boolean({ description: "Emit JSON output" }),
|
|
22
|
+
force: Flags.boolean({ description: "Allow hard-kill escalation when graceful stop times out" }),
|
|
23
|
+
"graceful-timeout-ms": Flags.integer({ description: "Cooperative stop timeout before escalation" }),
|
|
24
|
+
"kill-timeout-ms": Flags.integer({ description: "Wait for old pid death after SIGKILL" }),
|
|
25
|
+
"spawn-if-stopped": Flags.boolean({ description: "On reload, spawn even when no daemon is running" }),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
async run(): Promise<void> {
|
|
29
|
+
const { args, flags } = await this.parse(Daemon);
|
|
30
|
+
const action = (args.action ?? "status") as DaemonCliAction;
|
|
31
|
+
const kinds = (Array.isArray(args.kind) ? args.kind : args.kind ? [args.kind] : []) as DaemonKind[];
|
|
32
|
+
const flagRec = flags as Record<string, unknown>;
|
|
33
|
+
const cmd: DaemonCommandArgs = {
|
|
34
|
+
action,
|
|
35
|
+
kinds,
|
|
36
|
+
all: Boolean(flags.all),
|
|
37
|
+
json: Boolean(flags.json),
|
|
38
|
+
force: Boolean(flags.force),
|
|
39
|
+
gracefulTimeoutMs: flagRec["graceful-timeout-ms"] as number | undefined,
|
|
40
|
+
killTimeoutMs: flagRec["kill-timeout-ms"] as number | undefined,
|
|
41
|
+
spawnIfStopped: flagRec["spawn-if-stopped"] as boolean | undefined,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
await initTheme();
|
|
45
|
+
await runDaemonCommand(cmd);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -11,11 +11,11 @@ export default class DeepInterview extends Command {
|
|
|
11
11
|
threshold: Flags.string({ description: "Override ambiguity threshold for kickoff" }),
|
|
12
12
|
"threshold-source": Flags.string({ description: "Describe the threshold override source" }),
|
|
13
13
|
"session-id": Flags.string({
|
|
14
|
-
description: "Route state/spec handoff through a session-scoped .gjc
|
|
14
|
+
description: "Route state/spec handoff through a session-scoped .gjc/_session-{sessionid} directory",
|
|
15
15
|
}),
|
|
16
16
|
write: Flags.boolean({ description: "Persist a final deep-interview spec through the sanctioned GJC CLI/API" }),
|
|
17
17
|
stage: Flags.string({ description: 'Spec stage for --write (currently "final")' }),
|
|
18
|
-
slug: Flags.string({ description: "Safe slug for .gjc/specs/deep-interview-<slug>.md" }),
|
|
18
|
+
slug: Flags.string({ description: "Safe slug for .gjc/_session-{sessionid}/specs/deep-interview-<slug>.md" }),
|
|
19
19
|
spec: Flags.string({ description: "Final spec markdown or a path to the final spec markdown" }),
|
|
20
20
|
handoff: Flags.string({ description: 'After --write, hand off to a workflow target (currently "ralplan")' }),
|
|
21
21
|
deliberate: Flags.boolean({
|