@gethmy/mcp 2.8.2 → 2.8.3
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 +99 -7
- package/package.json +1 -1
- package/src/cli.ts +5 -0
- package/src/tui/setup.ts +155 -6
- package/src/tui/writer.ts +6 -5
package/dist/cli.js
CHANGED
|
@@ -6357,9 +6357,10 @@ function appendToToml(filePath, section, content, options = {}) {
|
|
|
6357
6357
|
}
|
|
6358
6358
|
try {
|
|
6359
6359
|
const existing = readFileSync6(filePath, "utf-8");
|
|
6360
|
-
if (existing.includes(section)) {
|
|
6360
|
+
if (existing.includes(`[${section}]`)) {
|
|
6361
6361
|
if (options.force) {
|
|
6362
|
-
const
|
|
6362
|
+
const escaped = section.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6363
|
+
const updated = existing.replace(new RegExp(`(?:#[^\\n]*\\n)*\\[${escaped}\\][\\s\\S]*?(?=\\n\\[|$)`), content.trim() + `
|
|
6363
6364
|
|
|
6364
6365
|
`);
|
|
6365
6366
|
writeFileSync4(filePath, updated, { mode: 420 });
|
|
@@ -6433,6 +6434,53 @@ function getWriteSummary(files, options = {}) {
|
|
|
6433
6434
|
}
|
|
6434
6435
|
|
|
6435
6436
|
// src/tui/setup.ts
|
|
6437
|
+
var SAFE_HARMONY_TOOLS = [
|
|
6438
|
+
"harmony_get_card",
|
|
6439
|
+
"harmony_get_card_by_short_id",
|
|
6440
|
+
"harmony_search_cards",
|
|
6441
|
+
"harmony_get_board",
|
|
6442
|
+
"harmony_get_context",
|
|
6443
|
+
"harmony_list_projects",
|
|
6444
|
+
"harmony_list_workspaces",
|
|
6445
|
+
"harmony_get_card_links",
|
|
6446
|
+
"harmony_get_card_attachments",
|
|
6447
|
+
"harmony_get_card_external_links",
|
|
6448
|
+
"harmony_get_comments",
|
|
6449
|
+
"harmony_get_plan",
|
|
6450
|
+
"harmony_list_plans",
|
|
6451
|
+
"harmony_get_agent_session",
|
|
6452
|
+
"harmony_get_workspace_members",
|
|
6453
|
+
"harmony_resolve_links",
|
|
6454
|
+
"harmony_recall",
|
|
6455
|
+
"harmony_memory_search",
|
|
6456
|
+
"harmony_generate_prompt",
|
|
6457
|
+
"harmony_create_card",
|
|
6458
|
+
"harmony_update_card",
|
|
6459
|
+
"harmony_move_card",
|
|
6460
|
+
"harmony_assign_card",
|
|
6461
|
+
"harmony_create_subtask",
|
|
6462
|
+
"harmony_toggle_subtask",
|
|
6463
|
+
"harmony_add_label_to_card",
|
|
6464
|
+
"harmony_remove_label_from_card",
|
|
6465
|
+
"harmony_create_label",
|
|
6466
|
+
"harmony_add_comment",
|
|
6467
|
+
"harmony_update_comment",
|
|
6468
|
+
"harmony_add_link_to_card",
|
|
6469
|
+
"harmony_remove_link_from_card",
|
|
6470
|
+
"harmony_start_agent_session",
|
|
6471
|
+
"harmony_update_agent_progress",
|
|
6472
|
+
"harmony_end_agent_session",
|
|
6473
|
+
"harmony_set_project_context",
|
|
6474
|
+
"harmony_set_workspace_context",
|
|
6475
|
+
"harmony_create_plan",
|
|
6476
|
+
"harmony_update_plan",
|
|
6477
|
+
"harmony_advance_plan",
|
|
6478
|
+
"harmony_remember",
|
|
6479
|
+
"harmony_relate",
|
|
6480
|
+
"harmony_update_memory",
|
|
6481
|
+
"harmony_process_command",
|
|
6482
|
+
"harmony_sync"
|
|
6483
|
+
];
|
|
6436
6484
|
var GLOBAL_SKILLS_DIR = join7(homedir6(), ".agents", "skills");
|
|
6437
6485
|
var API_URL = "https://app.gethmy.com/api";
|
|
6438
6486
|
async function registerMcpServer() {
|
|
@@ -6455,9 +6503,7 @@ async function writeMcpConfigFallback(home) {
|
|
|
6455
6503
|
}
|
|
6456
6504
|
let settings = {};
|
|
6457
6505
|
if (existsSync9(settingsPath)) {
|
|
6458
|
-
|
|
6459
|
-
settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
|
|
6460
|
-
} catch {}
|
|
6506
|
+
settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
|
|
6461
6507
|
}
|
|
6462
6508
|
const mcpServers = settings.mcpServers || {};
|
|
6463
6509
|
mcpServers.harmony = {
|
|
@@ -6467,6 +6513,31 @@ async function writeMcpConfigFallback(home) {
|
|
|
6467
6513
|
settings.mcpServers = mcpServers;
|
|
6468
6514
|
writeFileSync5(settingsPath, JSON.stringify(settings, null, 2));
|
|
6469
6515
|
}
|
|
6516
|
+
async function allowlistHarmonyTools(home, allowAll) {
|
|
6517
|
+
const { readFileSync: readFileSync7, writeFileSync: writeFileSync5, mkdirSync: mkdirSync6, existsSync: existsSync9 } = await import("node:fs");
|
|
6518
|
+
const settingsPath = join7(home, ".claude", "settings.json");
|
|
6519
|
+
const settingsDir = dirname3(settingsPath);
|
|
6520
|
+
if (!existsSync9(settingsDir)) {
|
|
6521
|
+
mkdirSync6(settingsDir, { recursive: true });
|
|
6522
|
+
}
|
|
6523
|
+
let settings = {};
|
|
6524
|
+
if (existsSync9(settingsPath)) {
|
|
6525
|
+
settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
|
|
6526
|
+
}
|
|
6527
|
+
const permissions = settings.permissions || {};
|
|
6528
|
+
const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
|
|
6529
|
+
const hasBlanket = allow.includes("mcp__harmony") || allow.includes("mcp__harmony__*");
|
|
6530
|
+
const wanted = allowAll ? ["mcp__harmony"] : SAFE_HARMONY_TOOLS.map((t) => `mcp__harmony__${t}`);
|
|
6531
|
+
const missing = hasBlanket ? [] : wanted.filter((r) => !allow.includes(r));
|
|
6532
|
+
if (missing.length === 0) {
|
|
6533
|
+
return "already";
|
|
6534
|
+
}
|
|
6535
|
+
allow.push(...missing);
|
|
6536
|
+
permissions.allow = allow;
|
|
6537
|
+
settings.permissions = permissions;
|
|
6538
|
+
writeFileSync5(settingsPath, JSON.stringify(settings, null, 2));
|
|
6539
|
+
return "added";
|
|
6540
|
+
}
|
|
6470
6541
|
async function validateApiKey(apiKey, apiUrl = API_URL) {
|
|
6471
6542
|
try {
|
|
6472
6543
|
const response = await fetch(`${apiUrl}/v1/workspaces`, {
|
|
@@ -7231,6 +7302,26 @@ async function runSetup(options = {}) {
|
|
|
7231
7302
|
}
|
|
7232
7303
|
}
|
|
7233
7304
|
}
|
|
7305
|
+
if (claudeDetected || selectedAgents.includes("claude")) {
|
|
7306
|
+
const allowAll = options.allowAllTools === true;
|
|
7307
|
+
const message = allowAll ? "Allowlist EVERY Harmony tool without confirmation, including destructive ones (delete/archive/api-key/invite)?" : "Allowlist common Harmony tools (reads + create/update/move/comment) so /hmy doesn't prompt each time? Destructive tools (delete/archive/api-key/invite) will still ask.";
|
|
7308
|
+
const allowTools = await p3.confirm({ message, initialValue: true });
|
|
7309
|
+
if (p3.isCancel(allowTools)) {
|
|
7310
|
+
p3.cancel("Setup cancelled.");
|
|
7311
|
+
process.exit(0);
|
|
7312
|
+
}
|
|
7313
|
+
if (allowTools) {
|
|
7314
|
+
try {
|
|
7315
|
+
const result = await allowlistHarmonyTools(home, allowAll);
|
|
7316
|
+
const scope = allowAll ? "all tools" : "safe tools";
|
|
7317
|
+
console.log(` ${colors.success("✓")} ${colors.dim(formatPath(join7(home, ".claude", "settings.json"), home))} ${colors.dim(result === "added" ? `(${scope} allowlisted)` : `(${scope} already allowlisted)`)}`);
|
|
7318
|
+
} catch {
|
|
7319
|
+
p3.log.warning("Could not allowlist Harmony tools. Run /permissions in Claude Code and choose “always allow” for Harmony, or add mcp__harmony to permissions.allow in ~/.claude/settings.json.");
|
|
7320
|
+
}
|
|
7321
|
+
} else {
|
|
7322
|
+
console.log(` ${colors.dim("Skipped tool allowlist — you'll be prompted per tool, or run /permissions in Claude Code later.")}`);
|
|
7323
|
+
}
|
|
7324
|
+
}
|
|
7234
7325
|
if (selectedWorkspaceId || selectedProjectId) {
|
|
7235
7326
|
const localConfig = {};
|
|
7236
7327
|
if (selectedWorkspaceId)
|
|
@@ -7361,7 +7452,7 @@ program.command("reset").description("Remove stored configuration").action(() =>
|
|
|
7361
7452
|
console.log(`
|
|
7362
7453
|
To reconfigure, run: npx @gethmy/mcp setup`);
|
|
7363
7454
|
});
|
|
7364
|
-
program.command("setup").description("Smart setup wizard for Harmony MCP (recommended)").argument("[slug]", "Project slug — resolves to workspace + project in one step (e.g. harmony-6590761b)").option("-f, --force", "Overwrite existing configuration files").option("-k, --api-key <key>", "API key (skips prompt)").option("-e, --email <email>", "Your email for auto-assignment").option("-a, --agents <agents...>", "Agents to configure: claude, codex, cursor, windsurf").option("-l, --local", "Install skills locally in project directory").option("-g, --global", "Install skills globally (recommended)").option("-w, --workspace <id>", "Set workspace context (UUID)").option("-p, --project <id>", "Set project context (UUID)").option("--skip-context", "Skip workspace/project selection").option("--skip-docs", "Skip project docs scaffold/verification").option("--new", "Create a new account (skip the choice prompt)").option("-n, --name <name>", "Full name (for account creation)").action(async (slug, options) => {
|
|
7455
|
+
program.command("setup").description("Smart setup wizard for Harmony MCP (recommended)").argument("[slug]", "Project slug — resolves to workspace + project in one step (e.g. harmony-6590761b)").option("-f, --force", "Overwrite existing configuration files").option("-k, --api-key <key>", "API key (skips prompt)").option("-e, --email <email>", "Your email for auto-assignment").option("-a, --agents <agents...>", "Agents to configure: claude, codex, cursor, windsurf").option("-l, --local", "Install skills locally in project directory").option("-g, --global", "Install skills globally (recommended)").option("-w, --workspace <id>", "Set workspace context (UUID)").option("-p, --project <id>", "Set project context (UUID)").option("--skip-context", "Skip workspace/project selection").option("--skip-docs", "Skip project docs scaffold/verification").option("--new", "Create a new account (skip the choice prompt)").option("-n, --name <name>", "Full name (for account creation)").option("--allow-all-tools", "Allowlist every Harmony tool (incl. destructive: delete/archive/api-key/invite) without confirmation. Default allowlists only read + routine-write tools; destructive tools keep prompting.").action(async (slug, options) => {
|
|
7365
7456
|
await runSetup({
|
|
7366
7457
|
force: options.force,
|
|
7367
7458
|
apiKey: options.apiKey,
|
|
@@ -7374,7 +7465,8 @@ program.command("setup").description("Smart setup wizard for Harmony MCP (recomm
|
|
|
7374
7465
|
skipContext: options.skipContext,
|
|
7375
7466
|
skipDocs: options.skipDocs,
|
|
7376
7467
|
newAccount: options.new,
|
|
7377
|
-
name: options.name
|
|
7468
|
+
name: options.name,
|
|
7469
|
+
allowAllTools: options.allowAllTools
|
|
7378
7470
|
});
|
|
7379
7471
|
});
|
|
7380
7472
|
program.parse();
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -158,6 +158,10 @@ program
|
|
|
158
158
|
.option("--skip-docs", "Skip project docs scaffold/verification")
|
|
159
159
|
.option("--new", "Create a new account (skip the choice prompt)")
|
|
160
160
|
.option("-n, --name <name>", "Full name (for account creation)")
|
|
161
|
+
.option(
|
|
162
|
+
"--allow-all-tools",
|
|
163
|
+
"Allowlist every Harmony tool (incl. destructive: delete/archive/api-key/invite) without confirmation. Default allowlists only read + routine-write tools; destructive tools keep prompting.",
|
|
164
|
+
)
|
|
161
165
|
.action(async (slug, options) => {
|
|
162
166
|
await runSetup({
|
|
163
167
|
force: options.force,
|
|
@@ -176,6 +180,7 @@ program
|
|
|
176
180
|
skipDocs: options.skipDocs,
|
|
177
181
|
newAccount: options.new,
|
|
178
182
|
name: options.name,
|
|
183
|
+
allowAllTools: options.allowAllTools,
|
|
179
184
|
});
|
|
180
185
|
});
|
|
181
186
|
|
package/src/tui/setup.ts
CHANGED
|
@@ -44,8 +44,68 @@ export interface SetupOptions {
|
|
|
44
44
|
skipDocs?: boolean;
|
|
45
45
|
newAccount?: boolean;
|
|
46
46
|
name?: string;
|
|
47
|
+
allowAllTools?: boolean;
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Harmony tools that are safe to run without per-call confirmation: reads and
|
|
52
|
+
* routine writes the /hmy flow leans on. Destructive or sensitive tools are
|
|
53
|
+
* deliberately ABSENT so they keep prompting — deletes, archives, API-key
|
|
54
|
+
* minting, and invitations are exactly where the permission prompt earns its
|
|
55
|
+
* keep, especially under autonomous agent runs. New tools default to prompting
|
|
56
|
+
* until someone classifies them here (safe failure mode). `--allow-all-tools`
|
|
57
|
+
* overrides this with a blanket grant.
|
|
58
|
+
*/
|
|
59
|
+
const SAFE_HARMONY_TOOLS = [
|
|
60
|
+
// Reads
|
|
61
|
+
"harmony_get_card",
|
|
62
|
+
"harmony_get_card_by_short_id",
|
|
63
|
+
"harmony_search_cards",
|
|
64
|
+
"harmony_get_board",
|
|
65
|
+
"harmony_get_context",
|
|
66
|
+
"harmony_list_projects",
|
|
67
|
+
"harmony_list_workspaces",
|
|
68
|
+
"harmony_get_card_links",
|
|
69
|
+
"harmony_get_card_attachments",
|
|
70
|
+
"harmony_get_card_external_links",
|
|
71
|
+
"harmony_get_comments",
|
|
72
|
+
"harmony_get_plan",
|
|
73
|
+
"harmony_list_plans",
|
|
74
|
+
"harmony_get_agent_session",
|
|
75
|
+
"harmony_get_workspace_members",
|
|
76
|
+
"harmony_resolve_links",
|
|
77
|
+
"harmony_recall",
|
|
78
|
+
"harmony_memory_search",
|
|
79
|
+
"harmony_generate_prompt",
|
|
80
|
+
// Routine writes
|
|
81
|
+
"harmony_create_card",
|
|
82
|
+
"harmony_update_card",
|
|
83
|
+
"harmony_move_card",
|
|
84
|
+
"harmony_assign_card",
|
|
85
|
+
"harmony_create_subtask",
|
|
86
|
+
"harmony_toggle_subtask",
|
|
87
|
+
"harmony_add_label_to_card",
|
|
88
|
+
"harmony_remove_label_from_card",
|
|
89
|
+
"harmony_create_label",
|
|
90
|
+
"harmony_add_comment",
|
|
91
|
+
"harmony_update_comment",
|
|
92
|
+
"harmony_add_link_to_card",
|
|
93
|
+
"harmony_remove_link_from_card",
|
|
94
|
+
"harmony_start_agent_session",
|
|
95
|
+
"harmony_update_agent_progress",
|
|
96
|
+
"harmony_end_agent_session",
|
|
97
|
+
"harmony_set_project_context",
|
|
98
|
+
"harmony_set_workspace_context",
|
|
99
|
+
"harmony_create_plan",
|
|
100
|
+
"harmony_update_plan",
|
|
101
|
+
"harmony_advance_plan",
|
|
102
|
+
"harmony_remember",
|
|
103
|
+
"harmony_relate",
|
|
104
|
+
"harmony_update_memory",
|
|
105
|
+
"harmony_process_command",
|
|
106
|
+
"harmony_sync",
|
|
107
|
+
];
|
|
108
|
+
|
|
49
109
|
// Central skills directory for global installation
|
|
50
110
|
const GLOBAL_SKILLS_DIR = join(homedir(), ".agents", "skills");
|
|
51
111
|
|
|
@@ -87,14 +147,12 @@ async function writeMcpConfigFallback(home: string): Promise<void> {
|
|
|
87
147
|
mkdirSync(settingsDir, { recursive: true });
|
|
88
148
|
}
|
|
89
149
|
|
|
90
|
-
// Read existing settings
|
|
150
|
+
// Read existing settings. If the file exists but doesn't parse, bail rather
|
|
151
|
+
// than overwrite — don't wipe a user's hand-edited settings.json to register
|
|
152
|
+
// one MCP server. The caller falls back to a manual-setup instruction.
|
|
91
153
|
let settings: Record<string, unknown> = {};
|
|
92
154
|
if (existsSync(settingsPath)) {
|
|
93
|
-
|
|
94
|
-
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
95
|
-
} catch {
|
|
96
|
-
// Invalid JSON, start fresh
|
|
97
|
-
}
|
|
155
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
98
156
|
}
|
|
99
157
|
|
|
100
158
|
// Merge mcpServers config
|
|
@@ -109,6 +167,65 @@ async function writeMcpConfigFallback(home: string): Promise<void> {
|
|
|
109
167
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
110
168
|
}
|
|
111
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Allowlist Harmony MCP tools in Claude Code settings so the /hmy flow runs
|
|
172
|
+
* without per-tool permission prompts. Merges rules into permissions.allow in
|
|
173
|
+
* ~/.claude/settings.json, preserving existing settings. Idempotent.
|
|
174
|
+
*
|
|
175
|
+
* Default (allowAll=false): writes a per-tool rule (`mcp__harmony__<tool>`) for
|
|
176
|
+
* each entry in SAFE_HARMONY_TOOLS only — destructive tools keep prompting.
|
|
177
|
+
* allowAll=true: writes the bare `mcp__harmony` rule (every tool, incl.
|
|
178
|
+
* destructive). The bare form grants all tools and is supported across all
|
|
179
|
+
* Claude Code versions.
|
|
180
|
+
*/
|
|
181
|
+
async function allowlistHarmonyTools(
|
|
182
|
+
home: string,
|
|
183
|
+
allowAll: boolean,
|
|
184
|
+
): Promise<"added" | "already"> {
|
|
185
|
+
const { readFileSync, writeFileSync, mkdirSync, existsSync } = await import(
|
|
186
|
+
"node:fs"
|
|
187
|
+
);
|
|
188
|
+
const settingsPath = join(home, ".claude", "settings.json");
|
|
189
|
+
const settingsDir = dirname(settingsPath);
|
|
190
|
+
|
|
191
|
+
if (!existsSync(settingsDir)) {
|
|
192
|
+
mkdirSync(settingsDir, { recursive: true });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Read existing settings. If the file exists but doesn't parse, bail rather
|
|
196
|
+
// than overwrite — clobbering a user's hand-edited settings.json to add a
|
|
197
|
+
// permission rule is never worth it. The caller surfaces the manual-fix path.
|
|
198
|
+
let settings: Record<string, unknown> = {};
|
|
199
|
+
if (existsSync(settingsPath)) {
|
|
200
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const permissions = (settings.permissions as Record<string, unknown>) || {};
|
|
204
|
+
const allow = Array.isArray(permissions.allow)
|
|
205
|
+
? (permissions.allow as string[])
|
|
206
|
+
: [];
|
|
207
|
+
|
|
208
|
+
// A pre-existing blanket grant already covers everything either mode adds.
|
|
209
|
+
const hasBlanket =
|
|
210
|
+
allow.includes("mcp__harmony") || allow.includes("mcp__harmony__*");
|
|
211
|
+
|
|
212
|
+
const wanted = allowAll
|
|
213
|
+
? ["mcp__harmony"]
|
|
214
|
+
: SAFE_HARMONY_TOOLS.map((t) => `mcp__harmony__${t}`);
|
|
215
|
+
|
|
216
|
+
const missing = hasBlanket ? [] : wanted.filter((r) => !allow.includes(r));
|
|
217
|
+
if (missing.length === 0) {
|
|
218
|
+
return "already";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
allow.push(...missing);
|
|
222
|
+
permissions.allow = allow;
|
|
223
|
+
settings.permissions = permissions;
|
|
224
|
+
|
|
225
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
226
|
+
return "added";
|
|
227
|
+
}
|
|
228
|
+
|
|
112
229
|
interface Workspace {
|
|
113
230
|
id: string;
|
|
114
231
|
name: string;
|
|
@@ -1187,6 +1304,38 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
|
|
|
1187
1304
|
}
|
|
1188
1305
|
}
|
|
1189
1306
|
|
|
1307
|
+
// Step 8c: Allowlist Harmony tools so /hmy runs without permission prompts.
|
|
1308
|
+
// Default = safe subset (reads + routine writes); destructive tools still
|
|
1309
|
+
// prompt. `--allow-all-tools` opts into a blanket grant.
|
|
1310
|
+
if (claudeDetected || selectedAgents.includes("claude")) {
|
|
1311
|
+
const allowAll = options.allowAllTools === true;
|
|
1312
|
+
const message = allowAll
|
|
1313
|
+
? "Allowlist EVERY Harmony tool without confirmation, including destructive ones (delete/archive/api-key/invite)?"
|
|
1314
|
+
: "Allowlist common Harmony tools (reads + create/update/move/comment) so /hmy doesn't prompt each time? Destructive tools (delete/archive/api-key/invite) will still ask.";
|
|
1315
|
+
const allowTools = await p.confirm({ message, initialValue: true });
|
|
1316
|
+
if (p.isCancel(allowTools)) {
|
|
1317
|
+
p.cancel("Setup cancelled.");
|
|
1318
|
+
process.exit(0);
|
|
1319
|
+
}
|
|
1320
|
+
if (allowTools) {
|
|
1321
|
+
try {
|
|
1322
|
+
const result = await allowlistHarmonyTools(home, allowAll);
|
|
1323
|
+
const scope = allowAll ? "all tools" : "safe tools";
|
|
1324
|
+
console.log(
|
|
1325
|
+
` ${colors.success("✓")} ${colors.dim(formatPath(join(home, ".claude", "settings.json"), home))} ${colors.dim(result === "added" ? `(${scope} allowlisted)` : `(${scope} already allowlisted)`)}`,
|
|
1326
|
+
);
|
|
1327
|
+
} catch {
|
|
1328
|
+
p.log.warning(
|
|
1329
|
+
"Could not allowlist Harmony tools. Run /permissions in Claude Code and choose “always allow” for Harmony, or add mcp__harmony to permissions.allow in ~/.claude/settings.json.",
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
} else {
|
|
1333
|
+
console.log(
|
|
1334
|
+
` ${colors.dim("Skipped tool allowlist — you'll be prompted per tool, or run /permissions in Claude Code later.")}`,
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1190
1339
|
// Step 9: Save local context
|
|
1191
1340
|
if (selectedWorkspaceId || selectedProjectId) {
|
|
1192
1341
|
const localConfig: { workspaceId?: string; projectId?: string } = {};
|
package/src/tui/writer.ts
CHANGED
|
@@ -156,13 +156,14 @@ export function appendToToml(
|
|
|
156
156
|
try {
|
|
157
157
|
const existing = readFileSync(filePath, "utf-8");
|
|
158
158
|
|
|
159
|
-
if (existing.includes(section)) {
|
|
159
|
+
if (existing.includes(`[${section}]`)) {
|
|
160
160
|
if (options.force) {
|
|
161
|
-
// Replace existing section
|
|
161
|
+
// Replace existing section, including any comment lines directly above
|
|
162
|
+
// it. The end anchor is `\n[` (a table header at line start) or EOF —
|
|
163
|
+
// NOT a bare `[`, so we don't stop early on the `[` inside `args = [...]`.
|
|
164
|
+
const escaped = section.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
162
165
|
const updated = existing.replace(
|
|
163
|
-
new RegExp(
|
|
164
|
-
`\\[${section.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\][\\s\\S]*?(?=\\[|$)`,
|
|
165
|
-
),
|
|
166
|
+
new RegExp(`(?:#[^\\n]*\\n)*\\[${escaped}\\][\\s\\S]*?(?=\\n\\[|$)`),
|
|
166
167
|
content.trim() + "\n\n",
|
|
167
168
|
);
|
|
168
169
|
writeFileSync(filePath, updated, { mode: 0o644 });
|