@assethub/cli 0.1.31 → 0.1.33

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.
@@ -4,7 +4,7 @@ import { access, copyFile, mkdir, readFile, stat, writeFile } from 'node:fs/prom
4
4
  import { homedir } from 'node:os';
5
5
  import { delimiter, dirname, join } from 'node:path';
6
6
  import { API_KEY_ENV, launchAgentPath, launchAgentPlist, loadKeyIntoLaunchd, readLaunchdKey } from './appEnv.js';
7
- import { SESSION_SAVE_DISCLOSURE } from './hooks/install.js';
7
+ import { hookScopeError, sessionSaveDisclosure } from './hooks/install.js';
8
8
  import { mcpConfig } from './setup.js';
9
9
  export const redactKey = (key) => `ah_…${key.slice(-4)}`;
10
10
  const scrub = (text, key) => key ? text.split(key).join(redactKey(key)) : text;
@@ -116,6 +116,28 @@ export const runSetup = async (options, deps) => {
116
116
  const say = (line) => deps.log(line);
117
117
  const wantClaude = options.client !== 'codex';
118
118
  const wantCodex = options.client !== 'claude';
119
+ const hookClients = [...(wantClaude ? ['claude'] : []), ...(wantCodex ? ['codex'] : [])];
120
+ const hookNames = hookClients.map(client => (client === 'claude' ? 'Claude Code' : 'Codex')).join(' and ');
121
+ let hookRefusal;
122
+ for (const client of hookClients) {
123
+ const refused = await hookScopeError(deps.home, { projectDir: deps.cwd }, client, deps.codexHome);
124
+ if (refused) {
125
+ hookRefusal = `${client}: ${refused}`;
126
+ break;
127
+ }
128
+ }
129
+ // 0. --save-sessions asks for something the home folder cannot have, so stop
130
+ // before login, MCP or app-env changes rather than half-applying setup.
131
+ if (hookRefusal && options.saveSessions && !options.noHook) {
132
+ return {
133
+ ok: false,
134
+ dryRun: options.dryRun,
135
+ profile: '',
136
+ baseUrl: '',
137
+ steps: [{ name: 'hook', status: 'fail', detail: hookRefusal }],
138
+ nextStep: 'Run `assethub setup --save-sessions` inside the project folder whose sessions should be saved, or run it without --save-sessions.',
139
+ };
140
+ }
119
141
  // 1. Login (reuses the existing `auth login` path through deps.login)
120
142
  const explicitKey = options.apiKeySource === 'flag' ? options.apiKey : undefined;
121
143
  const hadKey = explicitKey ? false : await deps.hasWorkingKey();
@@ -300,51 +322,63 @@ export const runSetup = async (options, deps) => {
300
322
  else if (options.noHook) {
301
323
  steps.push({ name: 'hook', status: 'skip', detail: 'skipped (--no-hook)' });
302
324
  }
303
- else if (!wantClaude) {
304
- steps.push({ name: 'hook', status: 'skip', detail: 'Codex session saving is not supported yet' });
325
+ else if (hookRefusal) {
326
+ // Not offered: the dry run and the question would promise an install the guard refuses.
327
+ steps.push({ name: 'hook', status: 'skip', detail: hookRefusal });
305
328
  }
306
329
  else if (options.dryRun) {
307
330
  steps.push({
308
331
  name: 'hook',
309
332
  status: 'skip',
310
333
  detail: options.saveSessions
311
- ? 'would install the session-saving hooks (dry run)'
334
+ ? `would install the ${hookNames} session-saving hooks for ${deps.cwd} only (dry run)`
312
335
  : 'would leave session saving off (enable with --save-sessions)',
313
336
  });
314
337
  }
315
338
  else {
339
+ const hookScope = { projectDir: deps.cwd };
316
340
  let consent = options.saveSessions;
341
+ const disclose = () => {
342
+ for (const client of hookClients)
343
+ say(sessionSaveDisclosure(hookScope, client));
344
+ };
317
345
  if (!consent && deps.interactive && !options.yes) {
318
- say(SESSION_SAVE_DISCLOSURE);
319
- consent = await deps.confirmOptIn('Save and upload your Claude Code sessions?');
346
+ disclose();
347
+ consent = await deps.confirmOptIn(`Save and upload your ${hookNames} sessions?`);
320
348
  }
321
349
  if (!consent) {
322
350
  steps.push({
323
351
  name: 'hook',
324
352
  status: 'skip',
325
- detail: 'session saving is off; enable it with `assethub setup --save-sessions` or `assethub hooks install --client claude`',
353
+ detail: `session saving is off; enable it with \`assethub setup --save-sessions\`, or ${hookClients.map(client => `\`assethub hooks install --client ${client}\``).join(' and ')} inside the project folder`,
326
354
  });
327
355
  }
328
356
  else {
329
357
  if (options.saveSessions)
330
- say(SESSION_SAVE_DISCLOSURE);
358
+ disclose();
331
359
  const pathNote = (await deps.findBinary('assethub'))
332
360
  ? ''
333
361
  : ' Note: the hooks run `assethub`, which is not on PATH; install the CLI globally.';
334
- try {
335
- const result = await deps.installHooks({ client: 'claude' });
336
- steps.push({
337
- name: 'hook',
338
- status: result.installed ? 'ok' : 'fail',
339
- detail: `claude: ${result.detail}${result.installed ? pathNote : ''}`,
340
- });
341
- }
342
- catch (error) {
343
- steps.push({
344
- name: 'hook',
345
- status: 'fail',
346
- detail: `claude: ${scrub(error instanceof Error ? error.message : String(error), key)}`,
347
- });
362
+ for (const client of hookClients) {
363
+ try {
364
+ const result = await deps.installHooks({
365
+ client,
366
+ ...hookScope,
367
+ ...(client === 'codex' ? { codexHome: deps.codexHome } : {}),
368
+ });
369
+ steps.push({
370
+ name: 'hook',
371
+ status: result.installed ? 'ok' : 'fail',
372
+ detail: `${client}: ${result.detail}${result.installed ? pathNote : ''}`,
373
+ });
374
+ }
375
+ catch (error) {
376
+ steps.push({
377
+ name: 'hook',
378
+ status: 'fail',
379
+ detail: `${client}: ${scrub(error instanceof Error ? error.message : String(error), key)}`,
380
+ });
381
+ }
348
382
  }
349
383
  }
350
384
  }
@@ -459,6 +493,7 @@ export const runProcess = (file, args) => new Promise(done => {
459
493
  export const defaultFileDeps = () => ({
460
494
  platform: process.platform,
461
495
  home: homedir(),
496
+ cwd: process.cwd(),
462
497
  codexHome: process.env.CODEX_HOME?.trim() || join(homedir(), '.codex'),
463
498
  now: () => new Date(),
464
499
  findBinary: (name) => findOnPath(name),
@@ -1,5 +1,9 @@
1
1
  export type InstallHooks = (opts: {
2
2
  client: 'claude' | 'codex';
3
+ /** The project folder whose .claude/settings.local.json or .codex/hooks.json gets the hooks. */
4
+ projectDir?: string;
5
+ /** Codex's user folder ($CODEX_HOME), where Codex records which projects are trusted. */
6
+ codexHome?: string;
3
7
  }) => Promise<{
4
8
  installed: boolean;
5
9
  detail: string;
@@ -1 +1 @@
1
- {"version":3,"file":"setupHooksBridge.d.ts","sourceRoot":"","sources":["../src/setupHooksBridge.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE;IAChC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAA;CAC3B,KAAK,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAAC,CAAA;AAEnD,eAAO,MAAM,mBAAmB,EAAE,YAGhC,CAAA"}
1
+ {"version":3,"file":"setupHooksBridge.d.ts","sourceRoot":"","sources":["../src/setupHooksBridge.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE;IAChC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAA;IAC1B,gGAAgG;IAChG,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,KAAK,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAAC,CAAA;AAEnD,eAAO,MAAM,mBAAmB,EAAE,YAGhC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assethub/cli",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
4
4
  "description": "Command line interface for AssetHub.",
5
5
  "type": "module",
6
6
  "bin": {