@indigoai-us/hq-cli 5.345.104 → 5.345.106
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 +100 -0
- package/assets/bot-workers/setup/skills/add-a-pack.md +130 -0
- package/assets/bot-workers/setup/worker.yaml +3 -0
- package/dist/command-catalog.generated.d.ts +206 -0
- package/dist/command-catalog.generated.js +246 -0
- package/dist/commands/bot.d.ts +10 -0
- package/dist/commands/bot.js +20 -12
- package/dist/commands/daemon.js +385 -1
- package/dist/commands/integrations-connect.d.ts +6 -0
- package/dist/commands/integrations-connect.js +36 -0
- package/dist/commands/integrations-slack.d.ts +30 -0
- package/dist/commands/integrations-slack.js +81 -0
- package/dist/commands/integrations.js +2 -0
- package/dist/commands/logout.d.ts +6 -1
- package/dist/commands/logout.js +18 -2
- package/dist/commands/pack-install.d.ts +20 -1
- package/dist/commands/pack-install.js +80 -5
- package/dist/commands/pack-start-bot.d.ts +24 -0
- package/dist/commands/pack-start-bot.js +76 -0
- package/dist/commands/packs.d.ts +9 -0
- package/dist/commands/packs.js +26 -3
- package/dist/commands/pkg-install.js +11 -2
- package/dist/commands/secrets.d.ts +1 -5
- package/dist/commands/secrets.js +2 -92
- package/dist/fast-scaffold.d.ts +13 -0
- package/dist/fast-scaffold.js +136 -0
- package/dist/index.js +15 -1
- package/dist/lib/bot/config.d.ts +7 -0
- package/dist/lib/bot/daemon.js +2 -1
- package/dist/lib/bot/index.d.ts +2 -0
- package/dist/lib/bot/index.js +2 -0
- package/dist/lib/bot/lean-settings.d.ts +16 -0
- package/dist/lib/bot/lean-settings.js +34 -0
- package/dist/lib/bot/pack-link.d.ts +66 -0
- package/dist/lib/bot/pack-link.js +153 -0
- package/dist/lib/bot/pack-setup-driver.d.ts +37 -0
- package/dist/lib/bot/pack-setup-driver.js +83 -0
- package/dist/lib/bot/pack-setup.d.ts +62 -0
- package/dist/lib/bot/pack-setup.js +103 -0
- package/dist/lib/bot/prompt.d.ts +8 -0
- package/dist/lib/bot/prompt.js +5 -0
- package/dist/lib/bot/run.d.ts +18 -2
- package/dist/lib/bot/run.js +163 -25
- package/dist/lib/bot/runtime/claude.d.ts +27 -2
- package/dist/lib/bot/runtime/claude.js +49 -4
- package/dist/lib/bot/runtime/types.d.ts +5 -0
- package/dist/lib/bot/runtime-failure.d.ts +18 -2
- package/dist/lib/bot/runtime-failure.js +21 -1
- package/dist/lib/bot/toolchain.d.ts +25 -0
- package/dist/lib/bot/toolchain.js +33 -0
- package/dist/lib/bot/worker-source.d.ts +1 -0
- package/dist/lib/bot/worker-source.js +7 -1
- package/dist/lib/daemon/catalog.d.ts +15 -0
- package/dist/lib/daemon/catalog.js +49 -0
- package/dist/lib/daemon/paths.d.ts +1 -0
- package/dist/lib/daemon/paths.js +1 -0
- package/dist/lib/daemon/remote-control-forwarder.d.ts +14 -0
- package/dist/lib/daemon/remote-control-forwarder.js +104 -0
- package/dist/lib/daemon/remote-control-pools.d.ts +44 -0
- package/dist/lib/daemon/remote-control-pools.js +245 -0
- package/dist/lib/daemon/remote-control.d.ts +4 -0
- package/dist/lib/daemon/remote-control.js +17 -2
- package/dist/lib/daemon/run.js +126 -0
- package/dist/lib/doctor/fix/refresh-sync-progress.d.ts +5 -4
- package/dist/lib/doctor/fix/refresh-sync-progress.js +5 -4
- package/dist/lib/doctor/json-output.d.ts +5 -5
- package/dist/lib/doctor/json-output.js +2 -5
- package/dist/lib/packages/setup-block.d.ts +43 -0
- package/dist/lib/packages/setup-block.js +236 -0
- package/dist/lib/packages/setup-prompt.d.ts +34 -0
- package/dist/lib/packages/setup-prompt.js +128 -0
- package/dist/lib/packages/setup-takeover.d.ts +50 -0
- package/dist/lib/packages/setup-takeover.js +75 -0
- package/dist/lib/packages/start-bot.d.ts +47 -0
- package/dist/lib/packages/start-bot.js +66 -0
- package/dist/lib/session-company-lock.d.ts +7 -0
- package/dist/lib/session-company-lock.js +97 -0
- package/dist/types.d.ts +82 -0
- package/dist/utils/cognito-config.d.ts +5 -0
- package/dist/utils/cognito-config.js +14 -0
- package/dist/utils/cognito-revoke.d.ts +33 -0
- package/dist/utils/cognito-revoke.js +42 -0
- package/package.json +1 -1
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `setup:` block of a content pack's package.yaml (sample-pack-in-app-setup
|
|
3
|
+
* US-001, build contract section 1).
|
|
4
|
+
*
|
|
5
|
+
* A pack that declares one can be set up from the HQ app chat: the pack's own
|
|
6
|
+
* bot (`setup.bot.name`, also a worker under `contributes.workers`) asks the
|
|
7
|
+
* questions in order with tap-to-answer suggestions, then runs `setup.run`
|
|
8
|
+
* with the answers. `hq packages install` validates the block and fails naming
|
|
9
|
+
* the field; a pack without the block is untouched.
|
|
10
|
+
*
|
|
11
|
+
* Validation (contract):
|
|
12
|
+
* - `setup.bot.name` matches ^[a-z0-9-]{2,32}$ and appears in contributes.workers
|
|
13
|
+
* - question ids unique, ^[a-z_]+$; options is a list of strings (may be empty)
|
|
14
|
+
* - `run.script` exists in the pack; every `run.args` key is a question id;
|
|
15
|
+
* every `then` entry's first token exists in the pack
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from "node:fs";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
export const SETUP_BOT_NAME_RE = /^[a-z0-9-]{2,32}$/;
|
|
20
|
+
export const SETUP_QUESTION_ID_RE = /^[a-z_]+$/;
|
|
21
|
+
/** A validation failure; `field` is the dotted path of the offending field. */
|
|
22
|
+
export class PackSetupError extends Error {
|
|
23
|
+
field;
|
|
24
|
+
constructor(field, detail) {
|
|
25
|
+
super(`${field} ${detail}`);
|
|
26
|
+
this.field = field;
|
|
27
|
+
this.name = "PackSetupError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function isMapping(v) {
|
|
31
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
32
|
+
}
|
|
33
|
+
function requireString(value, field) {
|
|
34
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
35
|
+
throw new PackSetupError(field, "must be a non-empty string");
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function optionalBoolean(value, field, fallback) {
|
|
40
|
+
if (value === undefined)
|
|
41
|
+
return fallback;
|
|
42
|
+
if (typeof value !== "boolean")
|
|
43
|
+
throw new PackSetupError(field, "must be true or false");
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
/** A relative pack path that stays inside the pack root; null when it escapes. */
|
|
47
|
+
function insidePack(packDir, rel) {
|
|
48
|
+
if (path.isAbsolute(rel))
|
|
49
|
+
return null;
|
|
50
|
+
const root = path.resolve(packDir);
|
|
51
|
+
const abs = path.resolve(root, rel);
|
|
52
|
+
if (abs !== root && !abs.startsWith(`${root}${path.sep}`))
|
|
53
|
+
return null;
|
|
54
|
+
return abs;
|
|
55
|
+
}
|
|
56
|
+
function requirePackFile(ctx, rel, field) {
|
|
57
|
+
const exists = ctx.existsSync ?? fs.existsSync;
|
|
58
|
+
const abs = insidePack(ctx.packDir, rel);
|
|
59
|
+
if (!abs)
|
|
60
|
+
throw new PackSetupError(field, `"${rel}" must be a relative path inside the pack`);
|
|
61
|
+
if (!exists(abs))
|
|
62
|
+
throw new PackSetupError(field, `"${rel}" does not exist in the pack`);
|
|
63
|
+
}
|
|
64
|
+
function parseQuestion(raw, index) {
|
|
65
|
+
const field = `setup.questions[${index}]`;
|
|
66
|
+
if (!isMapping(raw))
|
|
67
|
+
throw new PackSetupError(field, "must be a mapping with id and text");
|
|
68
|
+
const id = requireString(raw.id, `${field}.id`);
|
|
69
|
+
if (!SETUP_QUESTION_ID_RE.test(id)) {
|
|
70
|
+
throw new PackSetupError(`${field}.id`, `"${id}" must match ${SETUP_QUESTION_ID_RE.source}`);
|
|
71
|
+
}
|
|
72
|
+
const text = requireString(raw.text, `${field}.text`);
|
|
73
|
+
let options = [];
|
|
74
|
+
if (raw.options !== undefined) {
|
|
75
|
+
if (!Array.isArray(raw.options) || !raw.options.every((o) => typeof o === "string")) {
|
|
76
|
+
throw new PackSetupError(`${field}.options`, "must be a list of strings (it may be empty)");
|
|
77
|
+
}
|
|
78
|
+
options = raw.options;
|
|
79
|
+
}
|
|
80
|
+
const skippable = optionalBoolean(raw.skippable, `${field}.skippable`, false);
|
|
81
|
+
let defaultFrom;
|
|
82
|
+
if (raw.default_from !== undefined)
|
|
83
|
+
defaultFrom = requireString(raw.default_from, `${field}.default_from`);
|
|
84
|
+
let goal;
|
|
85
|
+
if (raw.goal !== undefined)
|
|
86
|
+
goal = requireString(raw.goal, `${field}.goal`);
|
|
87
|
+
return { id, text, options, skippable, ...(defaultFrom ? { default_from: defaultFrom } : {}), ...(goal ? { goal } : {}) };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Validate a raw `setup:` value. Returns the typed block, or undefined when the
|
|
91
|
+
* manifest has no block (a legacy pack). Throws PackSetupError naming the field.
|
|
92
|
+
*/
|
|
93
|
+
export function parseSetupBlock(raw, ctx) {
|
|
94
|
+
if (raw === undefined || raw === null)
|
|
95
|
+
return undefined;
|
|
96
|
+
if (!isMapping(raw))
|
|
97
|
+
throw new PackSetupError("setup", "must be a mapping (bot, questions, run, done)");
|
|
98
|
+
// bot
|
|
99
|
+
if (!isMapping(raw.bot))
|
|
100
|
+
throw new PackSetupError("setup.bot", "must be a mapping with name, display_name, intro");
|
|
101
|
+
const botName = requireString(raw.bot.name, "setup.bot.name");
|
|
102
|
+
if (!SETUP_BOT_NAME_RE.test(botName)) {
|
|
103
|
+
throw new PackSetupError("setup.bot.name", `"${botName}" must match ${SETUP_BOT_NAME_RE.source}`);
|
|
104
|
+
}
|
|
105
|
+
if (!ctx.workers.includes(botName)) {
|
|
106
|
+
throw new PackSetupError("setup.bot.name", `"${botName}" is not listed under contributes.workers` +
|
|
107
|
+
(ctx.workers.length ? ` (declared: ${ctx.workers.join(", ")})` : " (none declared)"));
|
|
108
|
+
}
|
|
109
|
+
const displayName = requireString(raw.bot.display_name, "setup.bot.display_name");
|
|
110
|
+
const intro = requireString(raw.bot.intro, "setup.bot.intro");
|
|
111
|
+
const takeoverSetup = optionalBoolean(raw.takeover_setup, "setup.takeover_setup", false);
|
|
112
|
+
// questions
|
|
113
|
+
if (!Array.isArray(raw.questions) || raw.questions.length === 0) {
|
|
114
|
+
throw new PackSetupError("setup.questions", "must be a non-empty list");
|
|
115
|
+
}
|
|
116
|
+
const questions = raw.questions.map(parseQuestion);
|
|
117
|
+
const ids = new Set();
|
|
118
|
+
questions.forEach((q, i) => {
|
|
119
|
+
if (ids.has(q.id))
|
|
120
|
+
throw new PackSetupError(`setup.questions[${i}].id`, `"${q.id}" is used more than once`);
|
|
121
|
+
ids.add(q.id);
|
|
122
|
+
});
|
|
123
|
+
// run
|
|
124
|
+
if (!isMapping(raw.run))
|
|
125
|
+
throw new PackSetupError("setup.run", "must be a mapping with script (and optional args, then)");
|
|
126
|
+
const script = requireString(raw.run.script, "setup.run.script");
|
|
127
|
+
requirePackFile(ctx, script, "setup.run.script");
|
|
128
|
+
const args = {};
|
|
129
|
+
if (raw.run.args !== undefined) {
|
|
130
|
+
if (!isMapping(raw.run.args))
|
|
131
|
+
throw new PackSetupError("setup.run.args", "must be a mapping of question id to flag");
|
|
132
|
+
for (const [key, flag] of Object.entries(raw.run.args)) {
|
|
133
|
+
if (!ids.has(key)) {
|
|
134
|
+
throw new PackSetupError(`setup.run.args.${key}`, `is not a question id (questions: ${[...ids].join(", ")})`);
|
|
135
|
+
}
|
|
136
|
+
args[key] = requireString(flag, `setup.run.args.${key}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const then = [];
|
|
140
|
+
if (raw.run.then !== undefined) {
|
|
141
|
+
if (!Array.isArray(raw.run.then))
|
|
142
|
+
throw new PackSetupError("setup.run.then", "must be a list of commands");
|
|
143
|
+
raw.run.then.forEach((entry, i) => {
|
|
144
|
+
const field = `setup.run.then[${i}]`;
|
|
145
|
+
const command = requireString(entry, field);
|
|
146
|
+
const first = command.trim().split(/\s+/)[0];
|
|
147
|
+
requirePackFile(ctx, first, field);
|
|
148
|
+
then.push(command.trim());
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// done
|
|
152
|
+
if (!isMapping(raw.done))
|
|
153
|
+
throw new PackSetupError("setup.done", "must be a mapping with message (and optional suggestion)");
|
|
154
|
+
const message = requireString(raw.done.message, "setup.done.message");
|
|
155
|
+
let suggestion;
|
|
156
|
+
if (raw.done.suggestion !== undefined)
|
|
157
|
+
suggestion = requireString(raw.done.suggestion, "setup.done.suggestion");
|
|
158
|
+
// documents
|
|
159
|
+
let documents;
|
|
160
|
+
if (raw.documents !== undefined) {
|
|
161
|
+
if (!Array.isArray(raw.documents) || raw.documents.length === 0 || !raw.documents.every((d) => typeof d === "string" && d.trim())) {
|
|
162
|
+
throw new PackSetupError("setup.documents", "must be a non-empty list of document descriptions");
|
|
163
|
+
}
|
|
164
|
+
documents = raw.documents.map((d) => d.trim());
|
|
165
|
+
}
|
|
166
|
+
// sources
|
|
167
|
+
let sources;
|
|
168
|
+
if (raw.sources !== undefined) {
|
|
169
|
+
if (!Array.isArray(raw.sources) || raw.sources.length === 0) {
|
|
170
|
+
throw new PackSetupError("setup.sources", "must be a non-empty list of { kind, read, informs, options }");
|
|
171
|
+
}
|
|
172
|
+
sources = raw.sources.map((entry, i) => {
|
|
173
|
+
const field = `setup.sources[${i}]`;
|
|
174
|
+
if (!isMapping(entry))
|
|
175
|
+
throw new PackSetupError(field, "must be a mapping with kind, read, informs and options");
|
|
176
|
+
const kind = requireString(entry.kind, `${field}.kind`);
|
|
177
|
+
const read = requireString(entry.read, `${field}.read`);
|
|
178
|
+
if (!Array.isArray(entry.informs) || entry.informs.length === 0 || !entry.informs.every((x) => typeof x === "string")) {
|
|
179
|
+
throw new PackSetupError(`${field}.informs`, "must be a non-empty list of question ids");
|
|
180
|
+
}
|
|
181
|
+
for (const id of entry.informs) {
|
|
182
|
+
if (!ids.has(id))
|
|
183
|
+
throw new PackSetupError(`${field}.informs`, `"${id}" is not a question id (questions: ${[...ids].join(", ")})`);
|
|
184
|
+
}
|
|
185
|
+
if (!Array.isArray(entry.options) || entry.options.length === 0) {
|
|
186
|
+
throw new PackSetupError(`${field}.options`, "must be a non-empty list of { name, domain }");
|
|
187
|
+
}
|
|
188
|
+
const options = entry.options.map((o, j) => {
|
|
189
|
+
const of = `${field}.options[${j}]`;
|
|
190
|
+
if (!isMapping(o))
|
|
191
|
+
throw new PackSetupError(of, "must be a mapping with name and a domain or a tool");
|
|
192
|
+
const name = requireString(o.name, `${of}.name`);
|
|
193
|
+
const available = optionalBoolean(o.available, `${of}.available`, true);
|
|
194
|
+
const opt = { name, ...(available ? {} : { available: false }) };
|
|
195
|
+
for (const key of ["domain", "tool", "connect", "status", "usage", "note"]) {
|
|
196
|
+
if (o[key] !== undefined)
|
|
197
|
+
opt[key] = requireString(o[key], `${of}.${key}`);
|
|
198
|
+
}
|
|
199
|
+
if (opt.tool)
|
|
200
|
+
requirePackFile(ctx, opt.tool, `${of}.tool`);
|
|
201
|
+
if (available && !opt.domain && !opt.tool)
|
|
202
|
+
throw new PackSetupError(of, "needs a domain (HQ integrations) or a tool (pack script), or available: false");
|
|
203
|
+
return opt;
|
|
204
|
+
});
|
|
205
|
+
return { kind, read, informs: [...entry.informs], options };
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
bot: { name: botName, display_name: displayName, intro },
|
|
210
|
+
takeover_setup: takeoverSetup,
|
|
211
|
+
questions,
|
|
212
|
+
run: { script, args, then },
|
|
213
|
+
done: { message, ...(suggestion ? { suggestion } : {}) },
|
|
214
|
+
...(documents ? { documents } : {}),
|
|
215
|
+
...(sources ? { sources } : {}),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The validated setup block of an installed pack, or undefined when the pack
|
|
220
|
+
* has none or the block is invalid (`hq packages list` prints `setup: no`).
|
|
221
|
+
*/
|
|
222
|
+
export function readInstalledPackSetup(manifest, packDir, existsSync) {
|
|
223
|
+
if (!manifest || manifest.setup === undefined)
|
|
224
|
+
return undefined;
|
|
225
|
+
try {
|
|
226
|
+
return parseSetupBlock(manifest.setup, {
|
|
227
|
+
packDir,
|
|
228
|
+
workers: manifest.contributes?.workers ?? [],
|
|
229
|
+
...(existsSync ? { existsSync } : {}),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=setup-block.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The auto-generated "Pack setup" section of a pack bot's system prompt
|
|
3
|
+
* (build contract section 3). Rendered from the pack's validated `setup:`
|
|
4
|
+
* block: the questions in order with their options, the one-at-a-time rule
|
|
5
|
+
* with the `hq-block` suggestions envelope (contract section 2), the exact
|
|
6
|
+
* command to run with the collected answers, and the done message.
|
|
7
|
+
*/
|
|
8
|
+
import type { PackSetup, PackSetupQuestion } from "../../types.js";
|
|
9
|
+
export declare const SKIP_SUGGESTION = "Skip";
|
|
10
|
+
export declare const HQ_BLOCK_FENCE = "hq-block";
|
|
11
|
+
export interface PackSetupPromptInput {
|
|
12
|
+
/** Manifest name, e.g. hq-pack-sample. */
|
|
13
|
+
packName: string;
|
|
14
|
+
/** Absolute installed pack root (core/packages/<pack>). */
|
|
15
|
+
packRoot: string;
|
|
16
|
+
setup: PackSetup;
|
|
17
|
+
}
|
|
18
|
+
/** The fenced JSON envelope the app draws as buttons (contract section 2). */
|
|
19
|
+
export declare function suggestionsEnvelope(items: readonly string[]): string;
|
|
20
|
+
/** The buttons for one question: its options, plus Skip when it is skippable. */
|
|
21
|
+
export declare function questionSuggestions(q: PackSetupQuestion): string[];
|
|
22
|
+
/** `bash <packRoot>/<script> --firm <company slug> --flag '<answer>' ...` */
|
|
23
|
+
export declare function renderRunCommand(input: PackSetupPromptInput): string;
|
|
24
|
+
/** The `then` commands, each run from the pack root with the same --firm. */
|
|
25
|
+
export declare function renderThenCommands(input: PackSetupPromptInput): string[];
|
|
26
|
+
/**
|
|
27
|
+
* "Their tools": what the bot needs to know to learn from the person's tools
|
|
28
|
+
* before asking. Facts (how each tool connects and is read) and principles;
|
|
29
|
+
* the bot decides how to run the conversation.
|
|
30
|
+
*/
|
|
31
|
+
export declare function renderLearnFirst(setup: PackSetup, packRoot: string): string;
|
|
32
|
+
/** The whole "## Pack setup" section: a brief, not a script. */
|
|
33
|
+
export declare function renderPackSetupSection(input: PackSetupPromptInput): string;
|
|
34
|
+
//# sourceMappingURL=setup-prompt.d.ts.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The auto-generated "Pack setup" section of a pack bot's system prompt
|
|
3
|
+
* (build contract section 3). Rendered from the pack's validated `setup:`
|
|
4
|
+
* block: the questions in order with their options, the one-at-a-time rule
|
|
5
|
+
* with the `hq-block` suggestions envelope (contract section 2), the exact
|
|
6
|
+
* command to run with the collected answers, and the done message.
|
|
7
|
+
*/
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
export const SKIP_SUGGESTION = "Skip";
|
|
10
|
+
export const HQ_BLOCK_FENCE = "hq-block";
|
|
11
|
+
/** The fenced JSON envelope the app draws as buttons (contract section 2). */
|
|
12
|
+
export function suggestionsEnvelope(items) {
|
|
13
|
+
const json = JSON.stringify({ v: 1, blocks: [{ kind: "suggestions", items }] });
|
|
14
|
+
return `\`\`\`${HQ_BLOCK_FENCE}\n${json}\n\`\`\``;
|
|
15
|
+
}
|
|
16
|
+
/** The buttons for one question: its options, plus Skip when it is skippable. */
|
|
17
|
+
export function questionSuggestions(q) {
|
|
18
|
+
const items = [...q.options];
|
|
19
|
+
if (q.skippable && !items.includes(SKIP_SUGGESTION))
|
|
20
|
+
items.push(SKIP_SUGGESTION);
|
|
21
|
+
return items;
|
|
22
|
+
}
|
|
23
|
+
function shellQuote(value) {
|
|
24
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
25
|
+
}
|
|
26
|
+
/** `bash <packRoot>/<script> --firm <company slug> --flag '<answer>' ...` */
|
|
27
|
+
export function renderRunCommand(input) {
|
|
28
|
+
const { setup, packRoot } = input;
|
|
29
|
+
const flags = setup.questions
|
|
30
|
+
.filter((q) => setup.run.args[q.id] !== undefined)
|
|
31
|
+
.map((q) => `${setup.run.args[q.id]} ${shellQuote(`<${q.id}>`)}`);
|
|
32
|
+
return ["bash", path.join(packRoot, setup.run.script), "--firm", "<company slug>", ...flags].join(" ");
|
|
33
|
+
}
|
|
34
|
+
/** The `then` commands, each run from the pack root with the same --firm. */
|
|
35
|
+
export function renderThenCommands(input) {
|
|
36
|
+
return input.setup.run.then.map((entry) => {
|
|
37
|
+
const [first, ...rest] = entry.trim().split(/\s+/);
|
|
38
|
+
return ["bash", path.join(input.packRoot, first), "--firm", "<company slug>", ...rest].join(" ");
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** How to run a pack tool script: node for .mjs/.js, bash otherwise. */
|
|
42
|
+
function toolCommand(packRoot, tool, args) {
|
|
43
|
+
const runner = /\.(m?js|cjs)$/.test(tool) ? "node" : "bash";
|
|
44
|
+
return `${runner} ${path.join(packRoot, tool)} ${args}`.trim();
|
|
45
|
+
}
|
|
46
|
+
/** One tool option, rendered with exactly how it connects and how it is read. */
|
|
47
|
+
function renderSourceOption(packRoot, o) {
|
|
48
|
+
if (o.available === false)
|
|
49
|
+
return ` - ${o.name}: no direct connection yet${o.note ? ` (${o.note})` : ""}. Offer a workaround if one helps; otherwise say so once, plainly, and move on.`;
|
|
50
|
+
if (o.tool) {
|
|
51
|
+
return (` - ${o.name}: connect with \`${toolCommand(packRoot, o.tool, o.connect ?? "connect")}\`, check with \`${toolCommand(packRoot, o.tool, o.status ?? "status")}\`` +
|
|
52
|
+
(o.usage ? `, read with \`${toolCommand(packRoot, o.tool, o.usage)}\`` : "") +
|
|
53
|
+
` (read-only).`);
|
|
54
|
+
}
|
|
55
|
+
return (` - ${o.name}: connect with \`hq integrations connect ${o.domain} --company <company slug> --link-only\`, read with \`hq integrations tools\` / \`hq integrations call\` (read-only tools only)` +
|
|
56
|
+
(o.note ? `; ${o.note}` : "") +
|
|
57
|
+
`.`);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* "Their tools": what the bot needs to know to learn from the person's tools
|
|
61
|
+
* before asking. Facts (how each tool connects and is read) and principles;
|
|
62
|
+
* the bot decides how to run the conversation.
|
|
63
|
+
*/
|
|
64
|
+
export function renderLearnFirst(setup, packRoot) {
|
|
65
|
+
const sources = setup.sources ?? [];
|
|
66
|
+
const lines = [
|
|
67
|
+
`### Their tools`,
|
|
68
|
+
``,
|
|
69
|
+
`Start here, before asking about the topics below: most of them should come from their tools, not from questions. \`hq integrations list --usable --json --company <company slug>\` shows what this company already has connected. ` +
|
|
70
|
+
`Find out which tools they use in whatever way fits the conversation, connect the ones that help, and read them (with a yes first) to fill in as much of the setup as you can; then ask only what is still missing. ` +
|
|
71
|
+
`Talk about tools by what they are for until the person has named one.`,
|
|
72
|
+
];
|
|
73
|
+
if (sources.length) {
|
|
74
|
+
lines.push(``, `What each kind of tool is good for, and the routes known to work for common tools. These are examples, not a limit: any tool they use is worth trying. Never tell the person a tool cannot be connected before you have tried the routes below:`, ...sources.flatMap((s) => [`- ${s.kind} (to read ${s.read}; helps with ${s.informs.join(", ")}):`, ...s.options.map((o) => renderSourceOption(path.resolve(packRoot), o))]), ``, `Any other tool: work out how to connect it yourself, trying these in order and stopping at the first that works. ` +
|
|
75
|
+
`(1) HQ's catalog: \`hq integrations catalog <name> --company <company slug>\`, then \`hq integrations connect <domain> --company <company slug> --link-only\` for a sign-in link. ` +
|
|
76
|
+
`(2) An access key: if the sign-in needs a key, or the tool only offers API keys or tokens, look up where the tool issues one, tell them the exact steps, and send \`hq secrets --company <company slug> generate-link <TOOL>_API_KEY --expires 1h\` so they paste it into HQ, never into the chat. Then connect with \`hq secrets --company <company slug> get --reveal <TOOL>_API_KEY | hq integrations connect <domain> --company <company slug> --token-stdin\`, or call the tool's own read API with the key through \`hq secrets exec --company <company slug> --only <TOOL>_API_KEY -- <command>\`, never printing it. ` +
|
|
77
|
+
`(3) A docs page or MCP server: \`hq integrations discover <docs url>\`, or \`hq integrations connect --mcp-url <url> --company <company slug> --link-only\`. ` +
|
|
78
|
+
`(4) A workaround: an export (CSV, PDF) or a few shared files dropped into the chat, a public page you can read, or the same data in a tool they already connected. ` +
|
|
79
|
+
`If none works, say so once and move on; the setup does not depend on any one tool.`, ``, `Mechanics: a connect command prints JSON. A \`url\` (or \`authorizationUrl\`) is a link to send them; \`started: true\` means a sign-in page already opened in their browser. ` +
|
|
80
|
+
`Never run \`hq integrations connect\` without \`--link-only\`: it waits for a sign-in the person has not been sent. ` +
|
|
81
|
+
`After they say they are done, confirm with the tool's check command (or \`hq integrations list\`).`);
|
|
82
|
+
}
|
|
83
|
+
lines.push(``, `Reading: ask before you read, read only what you said you would, and use read-only commands; never send, post or change anything. ` +
|
|
84
|
+
`Propose what you found with where it came from and let them correct it, rather than asking questions the data already answers. ` +
|
|
85
|
+
`Describe what you found in your own words; never paste private messages back.`);
|
|
86
|
+
return lines.join("\n");
|
|
87
|
+
}
|
|
88
|
+
function renderQuestion(q) {
|
|
89
|
+
const lines = [`- \`${q.id}\`: ${q.goal ?? q.text}`];
|
|
90
|
+
if (q.goal)
|
|
91
|
+
lines.push(` The pack phrases it: "${q.text}"`);
|
|
92
|
+
if (q.options.length)
|
|
93
|
+
lines.push(` Typical answers (to help you understand, not to show as buttons): ${q.options.join(", ")}`);
|
|
94
|
+
if (q.default_from)
|
|
95
|
+
lines.push(` Start from the value of \`${q.default_from}\` and let them confirm or change it.`);
|
|
96
|
+
lines.push(q.skippable ? ` Optional: if they would rather not say, pass \`skipped\`.` : ` Required.`);
|
|
97
|
+
return lines.join("\n");
|
|
98
|
+
}
|
|
99
|
+
/** The whole "## Pack setup" section: a brief, not a script. */
|
|
100
|
+
export function renderPackSetupSection(input) {
|
|
101
|
+
const { setup, packName, packRoot } = input;
|
|
102
|
+
const parts = [];
|
|
103
|
+
parts.push(`## Pack setup (${packName})\n\n` +
|
|
104
|
+
`You are the setup bot of the \`${packName}\` pack, installed at \`${packRoot}\`. Set the pack up for this person from this chat. ` +
|
|
105
|
+
`This section is a brief, not a script: it says what you need to learn, what you can use, and what done looks like. How you run the conversation is your call. Adapt to the person: follow their lead, skip what you already know, ask follow-ups when an answer is vague, and keep messages short. ` +
|
|
106
|
+
`You run every command yourself; never ask the person to open a terminal or Claude Code. ` +
|
|
107
|
+
`Everything you write in a turn is posted to the person, including text between commands, so never narrate your steps or write notes to yourself. ` +
|
|
108
|
+
`Keep your progress in \`pack-setup-${packName}.md\` in your memory folder so a later turn carries on instead of starting over.`);
|
|
109
|
+
parts.push(renderLearnFirst(setup, packRoot));
|
|
110
|
+
parts.push(`### What you need to learn\n\n${setup.questions.map(renderQuestion).join("\n")}`);
|
|
111
|
+
if (setup.documents?.length) {
|
|
112
|
+
parts.push(`### Documents\n\nThese are worth asking for if their tools do not have them: ${setup.documents.join(", ")}. They can drop files into the chat; read what arrives and use it.`);
|
|
113
|
+
}
|
|
114
|
+
parts.push(`### Buttons\n\n` +
|
|
115
|
+
`You can offer tap-to-answer buttons by ending a message with an \`${HQ_BLOCK_FENCE}\` fenced JSON envelope, for example ${suggestionsEnvelope(["Done", "Skip"])} The app draws each item as a button and sends its text back as the person's reply. ` +
|
|
116
|
+
`Use them only when there is a real, short set of choices: at most four, all of the same kind (never mix answers with actions like connecting a tool). Leave them off questions answered by typing, and never put a guessed or example answer in one. Typed replies always work too.`);
|
|
117
|
+
const thenCommands = renderThenCommands(input);
|
|
118
|
+
parts.push(`### Setting it up\n\n` +
|
|
119
|
+
`When you know enough, run this from the HQ folder, filling each \`<question id>\` with what you learned (\`skipped\` when they skipped) and \`<company slug>\` with their company's folder under \`companies/\`:\n\n` +
|
|
120
|
+
`\`\`\`bash\n${renderRunCommand(input)}\n\`\`\`` +
|
|
121
|
+
(thenCommands.length ? `\n\nThen, with the same \`--firm\`:\n\n\`\`\`bash\n${thenCommands.join("\n")}\n\`\`\`` : "") +
|
|
122
|
+
`\n\nIf a command fails, say plainly what did not work and offer to try again; never paste a stack trace.`);
|
|
123
|
+
parts.push(`### When it is done\n\n` +
|
|
124
|
+
`Tell them what you set up: what you created and where to find it in Files, what you connected, and anything left undone. The pack's own summary of the moment is: "${setup.done.message}"` +
|
|
125
|
+
(setup.done.suggestion ? ` A natural next step to offer: "${setup.done.suggestion}".` : ""));
|
|
126
|
+
return parts.join("\n\n");
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=setup-prompt.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Takeover of HQ's generic setup by a pack bot (build contract section 3).
|
|
3
|
+
*
|
|
4
|
+
* When a pack's `setup.takeover_setup` is true and HQ's setup bot has not
|
|
5
|
+
* recorded that setup is finished, the pack bot's system prompt also carries
|
|
6
|
+
* the setup worker's skills and a note to finish HQ setup first, then run the
|
|
7
|
+
* pack setup. "Finished" follows the setup worker's own rule: its progress
|
|
8
|
+
* note (`setup-progress.md` in the setup bot's memory folder) says
|
|
9
|
+
* `Status: finished`. No note, or any other status, means not finished.
|
|
10
|
+
*
|
|
11
|
+
* Where the note lives: the setup bot's config at `~/.hq/bots/setup/bot.json`
|
|
12
|
+
* names its memory folder (`personal/workers/setup/memory` under the HQ root
|
|
13
|
+
* for synced memory, or `~/.hq/bots/setup/memory` for local memory).
|
|
14
|
+
*/
|
|
15
|
+
import type { WorkerSkillSource } from "../bot/prompt.js";
|
|
16
|
+
export declare const HQ_SETUP_BOT_NAME = "setup";
|
|
17
|
+
export declare const SETUP_PROGRESS_NOTE = "setup-progress.md";
|
|
18
|
+
/** Same rule as the setup worker: the note says `Status: finished`. */
|
|
19
|
+
export declare function progressNoteSaysFinished(note: string | null | undefined): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The HQ setup bot's progress note on this computer, or null when the setup
|
|
22
|
+
* bot was never created here or has not written one.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readHqSetupProgressNote(opts?: {
|
|
25
|
+
botsRoot?: string;
|
|
26
|
+
hqRootFallback?: string;
|
|
27
|
+
}): {
|
|
28
|
+
path: string | null;
|
|
29
|
+
note: string | null;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Whether the pack bot takes over HQ setup: the pack asked for it and the
|
|
33
|
+
* progress note does not say setup is finished.
|
|
34
|
+
*/
|
|
35
|
+
export declare function decideTakeover(input: {
|
|
36
|
+
takeoverSetup: boolean;
|
|
37
|
+
progressNote: string | null | undefined;
|
|
38
|
+
}): boolean;
|
|
39
|
+
export interface TakeoverPromptInput {
|
|
40
|
+
packName: string;
|
|
41
|
+
/** hqRoot-relative folder of HQ's setup worker. */
|
|
42
|
+
setupWorkerDir: string;
|
|
43
|
+
/** The setup worker's skills (name, description, hqRoot-relative file). */
|
|
44
|
+
skills: readonly WorkerSkillSource[];
|
|
45
|
+
/** Where HQ's setup bot keeps (or would keep) its progress note; null when unknown. */
|
|
46
|
+
progressNotePath: string | null;
|
|
47
|
+
}
|
|
48
|
+
/** The "## Finish HQ setup first" section, placed before the pack setup section. */
|
|
49
|
+
export declare function renderTakeoverSection(input: TakeoverPromptInput): string;
|
|
50
|
+
//# sourceMappingURL=setup-takeover.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Takeover of HQ's generic setup by a pack bot (build contract section 3).
|
|
3
|
+
*
|
|
4
|
+
* When a pack's `setup.takeover_setup` is true and HQ's setup bot has not
|
|
5
|
+
* recorded that setup is finished, the pack bot's system prompt also carries
|
|
6
|
+
* the setup worker's skills and a note to finish HQ setup first, then run the
|
|
7
|
+
* pack setup. "Finished" follows the setup worker's own rule: its progress
|
|
8
|
+
* note (`setup-progress.md` in the setup bot's memory folder) says
|
|
9
|
+
* `Status: finished`. No note, or any other status, means not finished.
|
|
10
|
+
*
|
|
11
|
+
* Where the note lives: the setup bot's config at `~/.hq/bots/setup/bot.json`
|
|
12
|
+
* names its memory folder (`personal/workers/setup/memory` under the HQ root
|
|
13
|
+
* for synced memory, or `~/.hq/bots/setup/memory` for local memory).
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from "node:fs";
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
import { readBotConfig, resolveBotMemoryDir } from "../bot/config.js";
|
|
18
|
+
import { botDir } from "../bot/paths.js";
|
|
19
|
+
export const HQ_SETUP_BOT_NAME = "setup";
|
|
20
|
+
export const SETUP_PROGRESS_NOTE = "setup-progress.md";
|
|
21
|
+
/** Same rule as the setup worker: the note says `Status: finished`. */
|
|
22
|
+
export function progressNoteSaysFinished(note) {
|
|
23
|
+
if (typeof note !== "string")
|
|
24
|
+
return false;
|
|
25
|
+
return /^Status:[ \t]*finished/im.test(note);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The HQ setup bot's progress note on this computer, or null when the setup
|
|
29
|
+
* bot was never created here or has not written one.
|
|
30
|
+
*/
|
|
31
|
+
export function readHqSetupProgressNote(opts = {}) {
|
|
32
|
+
const config = readBotConfig(botDir(HQ_SETUP_BOT_NAME, opts.botsRoot));
|
|
33
|
+
if (!config)
|
|
34
|
+
return { path: null, note: null };
|
|
35
|
+
const notePath = path.join(resolveBotMemoryDir(config), SETUP_PROGRESS_NOTE);
|
|
36
|
+
return { path: notePath, note: readNote(notePath) };
|
|
37
|
+
}
|
|
38
|
+
function readNote(p) {
|
|
39
|
+
try {
|
|
40
|
+
return fs.readFileSync(p, "utf8");
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Whether the pack bot takes over HQ setup: the pack asked for it and the
|
|
48
|
+
* progress note does not say setup is finished.
|
|
49
|
+
*/
|
|
50
|
+
export function decideTakeover(input) {
|
|
51
|
+
if (!input.takeoverSetup)
|
|
52
|
+
return false;
|
|
53
|
+
return !progressNoteSaysFinished(input.progressNote);
|
|
54
|
+
}
|
|
55
|
+
/** The "## Finish HQ setup first" section, placed before the pack setup section. */
|
|
56
|
+
export function renderTakeoverSection(input) {
|
|
57
|
+
const skillLines = input.skills.map((s) => {
|
|
58
|
+
const desc = s.description ? ` — ${s.description}` : "";
|
|
59
|
+
const file = s.file ? ` — ${s.file}` : " — (no skill file on disk)";
|
|
60
|
+
return `- ${s.name}${desc}${file}`;
|
|
61
|
+
});
|
|
62
|
+
return (`## Finish HQ setup first\n\n` +
|
|
63
|
+
`HQ's own setup bot has not recorded that this HQ is set up` +
|
|
64
|
+
(input.progressNotePath ? ` (its progress note at \`${input.progressNotePath}\` does not say \`Status: finished\`)` : " (it has no progress note on this computer)") +
|
|
65
|
+
`. Before the \`${input.packName}\` pack setup, finish the generic HQ setup yourself, using HQ's setup worker skills below: ` +
|
|
66
|
+
`read \`${path.join(input.setupWorkerDir, "worker.yaml")}\` for the walkthrough and its progress-note rules, do only the steps that are not done, and ` +
|
|
67
|
+
(input.progressNotePath
|
|
68
|
+
? `keep HQ's setup progress note at \`${input.progressNotePath}\` up to date as you go (not a copy in your own memory folder), ending with \`Status: finished\`, so HQ's setup bot and later packs see that setup is done. `
|
|
69
|
+
: `write the same \`setup-progress.md\` note in your memory folder as you go, ending with \`Status: finished\`. `) +
|
|
70
|
+
`Skip any step the HQ plainly shows is done (signed in, a company they belong to). Then carry on with the Pack setup section. ` +
|
|
71
|
+
`Tell the person in one line that you are finishing HQ setup first, and ask one question at a time, as the Pack setup section says.\n\n` +
|
|
72
|
+
`### HQ setup worker skills (${path.join(input.setupWorkerDir, "skills")}/)\n\n` +
|
|
73
|
+
(skillLines.length ? skillLines.join("\n") : "- (none found on disk)"));
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=setup-takeover.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hq packages install <source> --start-bot` (sample-pack-in-app-setup US-003,
|
|
3
|
+
* build contract section 3).
|
|
4
|
+
*
|
|
5
|
+
* After a pack with a valid `setup:` block is wired, create the personal bot
|
|
6
|
+
* `setup.bot.name` running the pack worker of the same id, with the display
|
|
7
|
+
* name and intro from the block, through the same code path as
|
|
8
|
+
* `hq bot create` (never a child `hq` process), and start it the way
|
|
9
|
+
* `hq bot create` does (launchd when installed). A bot of that name that
|
|
10
|
+
* already exists is restarted and re-sends its intro. If the bot cannot start,
|
|
11
|
+
* the install still succeeds: the reason is printed and the owner gets one
|
|
12
|
+
* plain DM with one next step. On success the install prints `bot: <name>`,
|
|
13
|
+
* which the desktop app reads to open the right DM.
|
|
14
|
+
*/
|
|
15
|
+
import type { PackSetup } from "../../types.js";
|
|
16
|
+
export interface StartPackBotDeps {
|
|
17
|
+
/** A bot of this name exists on this computer. */
|
|
18
|
+
botExists: (name: string) => boolean;
|
|
19
|
+
/** `hq bot create <name> --worker <id> --display-name ... --intro ... --kickoff ...` */
|
|
20
|
+
createBot: (name: string, opts: {
|
|
21
|
+
worker: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
intro: string;
|
|
24
|
+
kickoff: string;
|
|
25
|
+
}) => Promise<void>;
|
|
26
|
+
/** `hq bot restart <name>` */
|
|
27
|
+
restartBot: (name: string) => Promise<void>;
|
|
28
|
+
/** `hq bot intro <name>` (re-send the hello) */
|
|
29
|
+
sendIntro: (name: string) => Promise<void>;
|
|
30
|
+
/** One DM to the owner, best effort; failures are reported, never thrown. */
|
|
31
|
+
dmOwner: (name: string, body: string) => Promise<void>;
|
|
32
|
+
/** Human output (stdout). */
|
|
33
|
+
say: (line: string) => void;
|
|
34
|
+
}
|
|
35
|
+
export interface StartPackBotResult {
|
|
36
|
+
ok: boolean;
|
|
37
|
+
bot: string;
|
|
38
|
+
/** "created" | "restarted" */
|
|
39
|
+
action: "created" | "restarted";
|
|
40
|
+
reason?: string;
|
|
41
|
+
}
|
|
42
|
+
/** The kickoff turn that makes the bot begin the pack setup as soon as it is online. */
|
|
43
|
+
export declare function packSetupKickoff(packName: string, setup: PackSetup): string;
|
|
44
|
+
/** One plain sentence plus one next step, for the owner's DM when the start failed. */
|
|
45
|
+
export declare function startFailureDm(setup: PackSetup, reason: string): string;
|
|
46
|
+
export declare function startPackSetupBot(packName: string, setup: PackSetup, deps: StartPackBotDeps): Promise<StartPackBotResult>;
|
|
47
|
+
//# sourceMappingURL=start-bot.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hq packages install <source> --start-bot` (sample-pack-in-app-setup US-003,
|
|
3
|
+
* build contract section 3).
|
|
4
|
+
*
|
|
5
|
+
* After a pack with a valid `setup:` block is wired, create the personal bot
|
|
6
|
+
* `setup.bot.name` running the pack worker of the same id, with the display
|
|
7
|
+
* name and intro from the block, through the same code path as
|
|
8
|
+
* `hq bot create` (never a child `hq` process), and start it the way
|
|
9
|
+
* `hq bot create` does (launchd when installed). A bot of that name that
|
|
10
|
+
* already exists is restarted and re-sends its intro. If the bot cannot start,
|
|
11
|
+
* the install still succeeds: the reason is printed and the owner gets one
|
|
12
|
+
* plain DM with one next step. On success the install prints `bot: <name>`,
|
|
13
|
+
* which the desktop app reads to open the right DM.
|
|
14
|
+
*/
|
|
15
|
+
/** The kickoff turn that makes the bot begin the pack setup as soon as it is online. */
|
|
16
|
+
export function packSetupKickoff(packName, setup) {
|
|
17
|
+
return (`Kickoff: I just added the ${packName} pack and you are its setup bot. Begin now. ` +
|
|
18
|
+
`Follow the Pack setup section of your instructions: if a "Finish HQ setup first" section is present, start there; ` +
|
|
19
|
+
`otherwise ask the first setup question (\`${setup.questions[0].id}\`) with its suggestions. One question per message.`);
|
|
20
|
+
}
|
|
21
|
+
/** One plain sentence plus one next step, for the owner's DM when the start failed. */
|
|
22
|
+
export function startFailureDm(setup, reason) {
|
|
23
|
+
const next = `hq bot start ${setup.bot.name}`;
|
|
24
|
+
return `${setup.bot.display_name} could not start (${plain(reason)}). Next step: open Terminal and run \`${next}\`, or ask your setup bot to start it.`;
|
|
25
|
+
}
|
|
26
|
+
function plain(reason) {
|
|
27
|
+
return reason.replace(/\s+/g, " ").trim().replace(/[.:]+$/, "");
|
|
28
|
+
}
|
|
29
|
+
function errorMessage(err) {
|
|
30
|
+
return err instanceof Error ? err.message : String(err);
|
|
31
|
+
}
|
|
32
|
+
export async function startPackSetupBot(packName, setup, deps) {
|
|
33
|
+
const name = setup.bot.name;
|
|
34
|
+
const exists = deps.botExists(name);
|
|
35
|
+
const action = exists ? "restarted" : "created";
|
|
36
|
+
try {
|
|
37
|
+
if (exists) {
|
|
38
|
+
deps.say(`bot ${name} already exists; restarting it and sending its intro again`);
|
|
39
|
+
await deps.restartBot(name);
|
|
40
|
+
await deps.sendIntro(name);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
await deps.createBot(name, {
|
|
44
|
+
worker: name,
|
|
45
|
+
displayName: setup.bot.display_name,
|
|
46
|
+
intro: setup.bot.intro,
|
|
47
|
+
kickoff: packSetupKickoff(packName, setup),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
deps.say(`bot: ${name}`);
|
|
51
|
+
return { ok: true, bot: name, action };
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const reason = errorMessage(err);
|
|
55
|
+
deps.say(`bot ${name} could not be started: ${reason}`);
|
|
56
|
+
deps.say(`The pack is installed. Start the bot later with: hq bot start ${name}`);
|
|
57
|
+
try {
|
|
58
|
+
await deps.dmOwner(name, startFailureDm(setup, reason));
|
|
59
|
+
}
|
|
60
|
+
catch (dmErr) {
|
|
61
|
+
deps.say(`could not DM you about it: ${errorMessage(dmErr)}`);
|
|
62
|
+
}
|
|
63
|
+
return { ok: false, bot: name, action, reason };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=start-bot.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type SessionCompanyLock = {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
companies: string[];
|
|
4
|
+
} | undefined;
|
|
5
|
+
/** Read the session's validated company scope capability or legacy metadata. */
|
|
6
|
+
export declare function readSessionCompanyLock(env?: NodeJS.ProcessEnv): SessionCompanyLock;
|
|
7
|
+
//# sourceMappingURL=session-company-lock.d.ts.map
|