@ghl-ai/aw 0.1.35-beta.24 → 0.1.35-beta.26

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/commands/init.mjs CHANGED
@@ -19,7 +19,6 @@ import { generateCommands, copyInstructions, initAwDocs } from '../integrate.mjs
19
19
  import { setupMcp } from '../mcp.mjs';
20
20
  import { autoUpdate, promptUpdate } from '../update.mjs';
21
21
  import { installGlobalHooks } from '../hooks.mjs';
22
- import { installAwEcc } from '../ecc.mjs';
23
22
 
24
23
  const __dirname = dirname(fileURLToPath(import.meta.url));
25
24
  const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version;
@@ -201,7 +200,6 @@ export async function initCommand(args) {
201
200
 
202
201
  // Re-link IDE dirs + hooks (idempotent)
203
202
  linkWorkspace(HOME);
204
- await installAwEcc(cwd, { silent });
205
203
  generateCommands(HOME);
206
204
  copyInstructions(HOME, null, freshCfg?.namespace || team) || [];
207
205
  initAwDocs(HOME);
@@ -282,7 +280,6 @@ export async function initCommand(args) {
282
280
  // Step 3: Link IDE dirs + setup tasks
283
281
  fmt.logStep('Linking IDE symlinks...');
284
282
  linkWorkspace(HOME);
285
- await installAwEcc(cwd, { silent });
286
283
  generateCommands(HOME);
287
284
  const instructionFiles = copyInstructions(HOME, null, team) || [];
288
285
  initAwDocs(HOME);
package/commands/nuke.mjs CHANGED
@@ -9,7 +9,6 @@ import { execSync } from 'node:child_process';
9
9
  import * as fmt from '../fmt.mjs';
10
10
  import { chalk } from '../fmt.mjs';
11
11
  import { removeGlobalHooks } from '../hooks.mjs';
12
- import { uninstallAwEcc } from '../ecc.mjs';
13
12
 
14
13
  const HOME = homedir();
15
14
  const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
@@ -244,31 +243,28 @@ export function nukeCommand(args) {
244
243
  // 2. Remove IDE symlinks (only those pointing to .aw_registry)
245
244
  removeIdeSymlinks();
246
245
 
247
- // 3. Remove aw-ecc installed files (agents, commands, rules, skills, hooks)
248
- uninstallAwEcc();
249
-
250
- // 4. Remove .aw_registry symlinks from ALL project directories
246
+ // 3. Remove .aw_registry symlinks from ALL project directories
251
247
  removeProjectSymlinks();
252
248
 
253
- // 5. Remove git hooks (core.hooksPath + legacy template)
249
+ // 4. Remove git hooks (core.hooksPath + legacy template)
254
250
  removeGitHooks(manifest);
255
251
 
256
- // 6. Remove IDE auto-init tasks
252
+ // 5. Remove IDE auto-init tasks
257
253
  removeIdeTasks();
258
254
 
259
- // Remove upgrade lock/log (inside .aw_registry, must happen before dir removal)
255
+ // 5b. Remove upgrade lock/log (inside .aw_registry, must happen before dir removal)
260
256
  for (const p of [join(GLOBAL_AW_DIR, '.aw-upgrade.lock'), join(GLOBAL_AW_DIR, '.aw-upgrade.log')]) {
261
257
  try { if (existsSync(p)) rmSync(p, { recursive: true, force: true }); } catch { /* best effort */ }
262
258
  }
263
259
 
264
- // 7. Remove ~/.aw_docs/
260
+ // 6. Remove ~/.aw_docs/
265
261
  const awDocs = join(HOME, '.aw_docs');
266
262
  if (existsSync(awDocs)) {
267
263
  rmSync(awDocs, { recursive: true, force: true });
268
264
  fmt.logStep('Removed ~/.aw_docs/');
269
265
  }
270
266
 
271
- // 8. Remove any manual `aw` symlinks (e.g. ~/.local/bin/aw)
267
+ // 7. Remove any manual `aw` symlinks (e.g. ~/.local/bin/aw)
272
268
  const manualBins = [
273
269
  join(HOME, '.local', 'bin', 'aw'),
274
270
  join(HOME, 'bin', 'aw'),
@@ -282,7 +278,7 @@ export function nukeCommand(args) {
282
278
  } catch { /* doesn't exist */ }
283
279
  }
284
280
 
285
- // 9. Uninstall npm global package (skip if already in npm uninstall lifecycle)
281
+ // 8. Uninstall npm global package (skip if already in npm uninstall lifecycle)
286
282
  if (!process.env.npm_lifecycle_event) {
287
283
  try {
288
284
  execSync('npm uninstall -g @ghl-ai/aw', { stdio: 'pipe', timeout: 15000 });
@@ -290,7 +286,7 @@ export function nukeCommand(args) {
290
286
  } catch { /* not installed via npm or no permissions */ }
291
287
  }
292
288
 
293
- // 10. Remove ~/.aw_registry/ itself (source of truth — last!)
289
+ // 9. Remove ~/.aw_registry/ itself (source of truth — last!)
294
290
  rmSync(GLOBAL_AW_DIR, { recursive: true, force: true });
295
291
  fmt.logStep('Removed ~/.aw_registry/');
296
292
 
@@ -299,7 +295,6 @@ export function nukeCommand(args) {
299
295
  '',
300
296
  ` ${chalk.green('✓')} Generated files cleaned`,
301
297
  ` ${chalk.green('✓')} IDE symlinks cleaned`,
302
- ` ${chalk.green('✓')} aw-ecc engine removed`,
303
298
  ` ${chalk.green('✓')} Project symlinks cleaned`,
304
299
  ` ${chalk.green('✓')} Git hooks removed`,
305
300
  ` ${chalk.green('✓')} IDE auto-sync tasks removed`,
package/integrate.mjs CHANGED
@@ -56,13 +56,11 @@ function findFiles(dir, typeName) {
56
56
  }
57
57
 
58
58
  /**
59
- * Copy AGENTS.md to project root.
60
- * CLAUDE.md is intentionally NOT generated — its routing rule hijacks plugin
61
- * commands like /aw:plan, preventing proper agent dispatch.
59
+ * Copy CLAUDE.md and AGENTS.md to project root.
62
60
  */
63
61
  export function copyInstructions(cwd, tempDir, namespace) {
64
62
  const createdFiles = [];
65
- for (const file of ['AGENTS.md']) {
63
+ for (const file of ['CLAUDE.md', 'AGENTS.md']) {
66
64
  const dest = join(cwd, file);
67
65
  if (existsSync(dest)) continue;
68
66
 
@@ -80,7 +78,9 @@ export function copyInstructions(cwd, tempDir, namespace) {
80
78
  }
81
79
  }
82
80
 
83
- const content = generateAgentsMd(cwd, namespace);
81
+ const content = file === 'CLAUDE.md'
82
+ ? generateClaudeMd(cwd, namespace)
83
+ : generateAgentsMd(cwd, namespace);
84
84
  if (content) {
85
85
  writeFileSync(dest, content);
86
86
  fmt.logSuccess(`Created ${file}`);
@@ -100,9 +100,7 @@ Team: ${team} | Local-first orchestration via \`.aw_docs/\` | MCPs: \`memory/*\`
100
100
 
101
101
  > **Every non-trivial task MUST call \`Skill(skill: "platform-ai-task-router")\` BEFORE any response.**
102
102
  >
103
- > **Exempt from routing (execute directly):**
104
- > - **Plugin commands**: any \`/aw:*\` slash command — these have their own agent dispatch via the plugin system. Execute the plugin command as-is, do NOT re-route through the task router.
105
- > - **Trivial tasks**: typo fixes, single-line edits, git ops, file exploration, factual code questions.
103
+ > **Trivial** (do directly): typo fixes, single-line edits, git ops, file exploration, factual code questions.
106
104
  >
107
105
  > Everything else — including tasks phrased as questions, suggestions, or discussions — routes first.
108
106
  > **No conversational responses first. No planning first. Route first.**
package/mcp.mjs CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'node:fs';
5
5
  import { execSync } from 'node:child_process';
6
+ import { createInterface } from 'node:readline';
6
7
  import { join, resolve } from 'node:path';
7
8
  import { homedir } from 'node:os';
8
9
  import * as fmt from './fmt.mjs';
@@ -111,12 +112,69 @@ function resolveGitHubUser(token) {
111
112
  }
112
113
  }
113
114
 
115
+ /**
116
+ * Resolve ClickUp personal API token. Opens browser to ClickUp settings page
117
+ * and prompts the user to paste their token.
118
+ *
119
+ * Resolution chain:
120
+ * 1. $CLICKUP_API_TOKEN env var (already set)
121
+ * 2. Open browser + prompt user (one-time)
122
+ * 3. null (skip ClickUp — other MCP tools still work)
123
+ */
124
+ async function resolveClickUpToken() {
125
+ // 1. Environment variable
126
+ if (process.env.CLICKUP_API_TOKEN) {
127
+ fmt.logStep('Using CLICKUP_API_TOKEN from environment');
128
+ return process.env.CLICKUP_API_TOKEN;
129
+ }
130
+
131
+ // 2. Open browser and prompt
132
+ fmt.logStep('ClickUp API token needed for per-user task attribution');
133
+ fmt.logStep('Opening ClickUp settings page — copy your API token from there...');
134
+
135
+ try {
136
+ const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
137
+ execSync(`${openCmd} "https://app.clickup.com/settings/apps"`, { stdio: 'ignore', timeout: 5000 });
138
+ } catch { /* browser open failed — user can navigate manually */ }
139
+
140
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
141
+ const token = await new Promise((resolve) => {
142
+ rl.question(' Paste your ClickUp API Token (pk_...): ', (answer) => {
143
+ rl.close();
144
+ resolve(answer.trim());
145
+ });
146
+ });
147
+
148
+ if (!token || !token.startsWith('pk_')) {
149
+ fmt.logWarn('No valid ClickUp token provided — skipping (ClickUp tools will use shared token)');
150
+ return null;
151
+ }
152
+
153
+ // Validate token
154
+ try {
155
+ const res = execSync(`curl -sf -H "Authorization: ${token}" "https://api.clickup.com/api/v2/team"`, {
156
+ encoding: 'utf8',
157
+ stdio: ['pipe', 'pipe', 'pipe'],
158
+ timeout: 10000,
159
+ });
160
+ const data = JSON.parse(res);
161
+ const teamName = data.teams?.[0]?.name;
162
+ if (teamName) {
163
+ fmt.logSuccess(`ClickUp token valid — workspace: ${fmt.chalk.cyan(teamName)}`);
164
+ }
165
+ } catch {
166
+ fmt.logWarn('ClickUp token validation failed — using it anyway');
167
+ }
168
+
169
+ return token;
170
+ }
171
+
114
172
  /**
115
173
  * Setup MCP configs globally for Claude Code and Cursor.
116
174
  * Merges ghl-ai server into existing configs without overwriting other servers.
117
175
  * Returns list of file paths that were created or updated.
118
176
  */
119
- export function setupMcp(cwd, namespace) {
177
+ export async function setupMcp(cwd, namespace) {
120
178
  const paths = detectPaths();
121
179
  const updatedFiles = [];
122
180
 
@@ -131,11 +189,19 @@ export function setupMcp(cwd, namespace) {
131
189
  }
132
190
  }
133
191
 
134
- // Server config with resolved token for local IDE configs (not committed, safe)
192
+ // Resolve ClickUp token for per-user attribution
193
+ const clickupToken = await resolveClickUpToken();
194
+
195
+ // Server config with resolved tokens for local IDE configs (not committed, safe)
196
+ const headers = { Authorization: `Bearer ${ghToken || '${GITHUB_TOKEN}'}` };
197
+ if (clickupToken) {
198
+ headers['X-ClickUp-Token'] = clickupToken;
199
+ }
200
+
135
201
  const ghlAiServerLocal = {
136
202
  type: 'http',
137
203
  url: mcpUrl,
138
- headers: { Authorization: `Bearer ${ghToken || '${GITHUB_TOKEN}'}` },
204
+ headers,
139
205
  };
140
206
 
141
207
  const gitJenkinsServer = paths.gitJenkinsPath
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.35-beta.24",
3
+ "version": "0.1.35-beta.26",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,8 +24,7 @@
24
24
  "registry.mjs",
25
25
  "apply.mjs",
26
26
  "update.mjs",
27
- "hooks.mjs",
28
- "ecc.mjs"
27
+ "hooks.mjs"
29
28
  ],
30
29
  "engines": {
31
30
  "node": ">=18.0.0"
package/ecc.mjs DELETED
@@ -1,180 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import {
3
- existsSync, readFileSync, readdirSync,
4
- mkdirSync, rmSync, writeFileSync,
5
- } from "node:fs";
6
- import { dirname, join } from "node:path";
7
- import { homedir } from "node:os";
8
- import * as fmt from "./fmt.mjs";
9
-
10
- const AW_ECC_REPO_SSH = "git@github.com:shreyansh-ghl/aw-ecc.git";
11
- const AW_ECC_REPO_HTTPS = "https://github.com/shreyansh-ghl/aw-ecc.git";
12
- const AW_ECC_TAG = "v1.2.2";
13
-
14
- const MARKETPLACE_NAME = "aw-marketplace";
15
- const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
16
-
17
- function eccDir() { return join(homedir(), ".aw-ecc"); }
18
-
19
- const FILE_COPY_TARGETS = ["cursor", "codex"];
20
-
21
- const TARGET_STATE = {
22
- cursor: { state: ".cursor/ecc-install-state.json" },
23
- codex: { state: ".codex/ecc-install-state.json" },
24
- };
25
-
26
- function run(cmd, opts = {}) {
27
- return execSync(cmd, { stdio: "pipe", ...opts });
28
- }
29
-
30
- function cloneOrUpdate(tag, dest) {
31
- if (existsSync(join(dest, ".git"))) {
32
- try {
33
- run(`git -C ${dest} fetch --quiet --depth 1 origin tag ${tag}`);
34
- run(`git -C ${dest} checkout --quiet ${tag}`);
35
- return;
36
- } catch { /* fall through to fresh clone */ }
37
- }
38
- if (existsSync(dest)) rmSync(dest, { recursive: true, force: true });
39
- try {
40
- run(`git clone --quiet --depth 1 --branch ${tag} ${AW_ECC_REPO_SSH} ${dest}`);
41
- } catch {
42
- run(`git clone --quiet --depth 1 --branch ${tag} ${AW_ECC_REPO_HTTPS} ${dest}`);
43
- }
44
- }
45
-
46
- function installClaudePlugin(repoDir) {
47
- try {
48
- run(`claude plugin marketplace add ${repoDir} --scope user`);
49
- } catch {
50
- try { run(`claude plugin marketplace update ${MARKETPLACE_NAME}`); } catch { /* ok */ }
51
- }
52
- run(`claude plugin install ${PLUGIN_KEY} --scope user`);
53
- }
54
-
55
- function uninstallClaudePlugin() {
56
- try { run(`claude plugin uninstall ${PLUGIN_KEY} --scope user`); } catch { /* not installed */ }
57
- try { run(`claude plugin marketplace remove ${MARKETPLACE_NAME}`); } catch { /* not registered */ }
58
- }
59
-
60
- export async function installAwEcc(
61
- cwd,
62
- { targets = ["cursor", "claude", "codex"], silent = false } = {},
63
- ) {
64
- if (!silent) fmt.logStep("Installing aw-ecc engine...");
65
-
66
- const repoDir = eccDir();
67
-
68
- try {
69
- cloneOrUpdate(AW_ECC_TAG, repoDir);
70
-
71
- // Claude Code: plugin install via marketplace CLI (proper agent dispatch)
72
- if (targets.includes("claude")) {
73
- try {
74
- installClaudePlugin(repoDir);
75
- } catch (err) {
76
- if (!silent) fmt.logWarn(`Claude plugin install skipped: ${err.message}`);
77
- }
78
- }
79
-
80
- // Cursor + Codex: file-copy via install-apply.js
81
- const fileCopyTargets = targets.filter((t) => FILE_COPY_TARGETS.includes(t));
82
- if (fileCopyTargets.length > 0) {
83
- run("npm install --no-audit --no-fund --ignore-scripts --loglevel=error", {
84
- cwd: repoDir,
85
- });
86
- for (const target of fileCopyTargets) {
87
- try {
88
- run(
89
- `node ${join(repoDir, "scripts/install-apply.js")} --target ${target} --profile full`,
90
- { cwd },
91
- );
92
- } catch { /* target not supported — skip */ }
93
- }
94
- }
95
-
96
- if (!silent) fmt.logSuccess("aw-ecc engine installed");
97
- } catch (err) {
98
- if (!silent) fmt.logWarn(`aw-ecc install failed: ${err.message}`);
99
- }
100
- }
101
-
102
- export function uninstallAwEcc({ silent = false } = {}) {
103
- const HOME = homedir();
104
- let removed = 0;
105
-
106
- // Claude Code: uninstall plugin + remove marketplace via CLI
107
- try {
108
- uninstallClaudePlugin();
109
- removed++;
110
- } catch { /* best effort */ }
111
-
112
- // Cursor + Codex: remove file-copied content via install-state
113
- for (const cfg of Object.values(TARGET_STATE)) {
114
- const statePath = join(HOME, cfg.state);
115
- if (!existsSync(statePath)) continue;
116
-
117
- try {
118
- const data = JSON.parse(readFileSync(statePath, "utf8"));
119
- for (const op of data.operations || []) {
120
- if (op.destinationPath && existsSync(op.destinationPath)) {
121
- rmSync(op.destinationPath, { recursive: true, force: true });
122
- removed++;
123
- pruneEmptyParents(op.destinationPath, join(HOME, cfg.state.split("/")[0]));
124
- }
125
- }
126
- rmSync(statePath, { force: true });
127
- pruneEmptyParents(statePath, join(HOME, cfg.state.split("/")[0]));
128
- } catch { /* corrupted state — skip */ }
129
- }
130
-
131
- // Clean leftover claude install-state from older file-copy versions
132
- const claudeState = join(HOME, ".claude", "ecc", "install-state.json");
133
- if (existsSync(claudeState)) {
134
- try {
135
- const data = JSON.parse(readFileSync(claudeState, "utf8"));
136
- for (const op of data.operations || []) {
137
- if (op.destinationPath && existsSync(op.destinationPath)) {
138
- rmSync(op.destinationPath, { recursive: true, force: true });
139
- removed++;
140
- }
141
- }
142
- rmSync(claudeState, { force: true });
143
- pruneEmptyParents(claudeState, join(HOME, ".claude"));
144
- } catch { /* best effort */ }
145
- }
146
-
147
- // Clean leftover manual plugin cache from older versions
148
- const oldCache = join(HOME, ".claude", "plugins", "cache", "aw");
149
- if (existsSync(oldCache)) {
150
- rmSync(oldCache, { recursive: true, force: true });
151
- removed++;
152
- }
153
-
154
- // Remove permanent aw-ecc repo clone
155
- const repoDir = eccDir();
156
- if (existsSync(repoDir)) {
157
- rmSync(repoDir, { recursive: true, force: true });
158
- removed++;
159
- }
160
-
161
- if (!silent && removed > 0)
162
- fmt.logStep(`Removed ${removed} aw-ecc file${removed > 1 ? "s" : ""}`);
163
- return removed;
164
- }
165
-
166
- function pruneEmptyParents(filePath, stopAt) {
167
- let dir = dirname(filePath);
168
- while (dir !== stopAt && dir.startsWith(stopAt)) {
169
- try {
170
- if (readdirSync(dir).length === 0) {
171
- rmSync(dir);
172
- dir = dirname(dir);
173
- } else {
174
- break;
175
- }
176
- } catch {
177
- break;
178
- }
179
- }
180
- }