workos 0.12.3 → 0.13.1

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.
@@ -6,6 +6,7 @@ interface DoctorArgs {
6
6
  installDir?: string;
7
7
  json?: boolean;
8
8
  copy?: boolean;
9
+ fix?: boolean;
9
10
  }
10
11
  export declare function handleDoctor(argv: ArgumentsCamelCase<DoctorArgs>): Promise<void>;
11
12
  export {};
@@ -8,6 +8,7 @@ export async function handleDoctor(argv) {
8
8
  skipAi: argv.skipAi ?? false,
9
9
  json: argv.json ?? false,
10
10
  copy: argv.copy ?? false,
11
+ fix: argv.fix ?? false,
11
12
  };
12
13
  try {
13
14
  const report = await runDoctor(options);
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,MAAM,mBAAmB,CAAC;AAWtC,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAoC;IACrE,MAAM,OAAO,GAAG;QACd,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE;QAC5C,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK;QAC9B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK;QAC9B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK;QAC5B,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,KAAK;QACxB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,KAAK;KACzB,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAEpC,gDAAgD;QAChD,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAClB,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,kBAAkB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;QAChG,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;QACrG,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC","sourcesContent":["import type { ArgumentsCamelCase } from 'yargs';\nimport { runDoctor, outputReport } from '../doctor/index.js';\nimport clack from '../utils/clack.js';\n\ninterface DoctorArgs {\n verbose?: boolean;\n skipApi?: boolean;\n skipAi?: boolean;\n installDir?: string;\n json?: boolean;\n copy?: boolean;\n}\n\nexport async function handleDoctor(argv: ArgumentsCamelCase<DoctorArgs>): Promise<void> {\n const options = {\n installDir: argv.installDir ?? process.cwd(),\n verbose: argv.verbose ?? false,\n skipApi: argv.skipApi ?? false,\n skipAi: argv.skipAi ?? false,\n json: argv.json ?? false,\n copy: argv.copy ?? false,\n };\n\n try {\n const report = await runDoctor(options);\n await outputReport(report, options);\n\n // Exit with error code if critical issues found\n if (report.summary.errors > 0) {\n process.exit(1);\n }\n process.exit(0);\n } catch (error) {\n if (!options.json) {\n clack.log.error(`Doctor failed: ${error instanceof Error ? error.message : 'Unknown error'}`);\n } else {\n console.error(JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' }));\n }\n process.exit(1);\n }\n}\n"]}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,MAAM,mBAAmB,CAAC;AAYtC,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAoC;IACrE,MAAM,OAAO,GAAG;QACd,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE;QAC5C,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK;QAC9B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK;QAC9B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK;QAC5B,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,KAAK;QACxB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,KAAK;QACxB,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,KAAK;KACvB,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAEpC,gDAAgD;QAChD,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAClB,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,kBAAkB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;QAChG,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;QACrG,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC","sourcesContent":["import type { ArgumentsCamelCase } from 'yargs';\nimport { runDoctor, outputReport } from '../doctor/index.js';\nimport clack from '../utils/clack.js';\n\ninterface DoctorArgs {\n verbose?: boolean;\n skipApi?: boolean;\n skipAi?: boolean;\n installDir?: string;\n json?: boolean;\n copy?: boolean;\n fix?: boolean;\n}\n\nexport async function handleDoctor(argv: ArgumentsCamelCase<DoctorArgs>): Promise<void> {\n const options = {\n installDir: argv.installDir ?? process.cwd(),\n verbose: argv.verbose ?? false,\n skipApi: argv.skipApi ?? false,\n skipAi: argv.skipAi ?? false,\n json: argv.json ?? false,\n copy: argv.copy ?? false,\n fix: argv.fix ?? false,\n };\n\n try {\n const report = await runDoctor(options);\n await outputReport(report, options);\n\n // Exit with error code if critical issues found\n if (report.summary.errors > 0) {\n process.exit(1);\n }\n process.exit(0);\n } catch (error) {\n if (!options.json) {\n clack.log.error(`Doctor failed: ${error instanceof Error ? error.message : 'Unknown error'}`);\n } else {\n console.error(JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' }));\n }\n process.exit(1);\n }\n}\n"]}
@@ -1,3 +1,12 @@
1
+ export declare const SKILL_VERSION_MARKER_FILENAME = ".workos-skill-version";
2
+ /**
3
+ * Read the bundled @workos/skills version by walking up from the skills
4
+ * directory to the package.json. The package's `exports` map doesn't expose
5
+ * package.json, so we resolve it by filesystem convention.
6
+ * Returns null if the version can't be determined — callers treat that as
7
+ * "no marker written" rather than failing the install.
8
+ */
9
+ export declare function getBundledSkillsVersion(skillsDir?: string): Promise<string | null>;
1
10
  export interface AgentConfig {
2
11
  name: string;
3
12
  displayName: string;
@@ -12,13 +21,65 @@ export interface InstallSkillOptions {
12
21
  export declare function getSkillsDir(): string;
13
22
  export declare function discoverSkills(skillsDir: string): Promise<string[]>;
14
23
  export declare function detectAgents(agents: Record<string, AgentConfig>, filter?: string[]): AgentConfig[];
24
+ /**
25
+ * Recursively install a skill directory (SKILL.md + references/ + any other
26
+ * files) with prune-replace semantics. Uses a sibling temp dir + backup-rename
27
+ * pattern so the operation is effectively atomic per skill: the target either
28
+ * matches the source exactly, or (on rollback) is restored to its prior state.
29
+ *
30
+ * Returns `{ success, error }` rather than throwing — callers (autoInstallSkills,
31
+ * runInstallSkill) accumulate failures across the (skill × agent) matrix.
32
+ */
15
33
  export declare function installSkill(skillsDir: string, skillName: string, agent: AgentConfig): Promise<{
16
34
  success: boolean;
17
35
  error?: string;
18
36
  }>;
19
37
  export declare function runInstallSkill(options: InstallSkillOptions): Promise<void>;
38
+ export interface AutoInstallResult {
39
+ skills: string[];
40
+ agents: string[];
41
+ version: string | null;
42
+ }
43
+ export interface RefreshOptions {
44
+ /** Pre-detected agents. Default: detect from $HOME. */
45
+ agents?: AgentConfig[];
46
+ /** Skill names to install. Default: all bundled skills. */
47
+ skills?: string[];
48
+ /** Whether to write the version marker after a successful per-agent install. Default: true. */
49
+ writeMarker?: boolean;
50
+ }
51
+ export interface RefreshResult {
52
+ /** Agents where at least one skill installed successfully. */
53
+ agents: AgentConfig[];
54
+ /** Skills that were attempted (the resolved set after filtering). */
55
+ skills: string[];
56
+ /** Bundled skills package version, or null if it couldn't be resolved. */
57
+ version: string | null;
58
+ /** Marker version per agent.name BEFORE refresh (null = no marker / unreadable). */
59
+ perAgentBefore: Record<string, string | null>;
60
+ /** Marker version per agent.name AFTER refresh. */
61
+ perAgentAfter: Record<string, string | null>;
62
+ }
63
+ /**
64
+ * Reusable primitive: discover bundled skills, install each one to each agent,
65
+ * write per-agent version markers, and report before/after marker state.
66
+ *
67
+ * Both `autoInstallSkills` (best-effort hook called from install/login) and
68
+ * `doctor --fix` (Phase 3) call this — there is no duplicate copy logic.
69
+ *
70
+ * Returns null when nothing applied (no agents detected, no skills found, or
71
+ * every install attempt failed).
72
+ */
73
+ export declare function refreshWorkOSSkills(opts?: RefreshOptions): Promise<RefreshResult | null>;
20
74
  /**
21
- * Silently install all bundled skills to all detected coding agents.
22
- * Errors are swallowed this must never disrupt the calling flow.
75
+ * Install all bundled skills to all detected coding agents.
76
+ * Returns a summary when anything was installed, or null when nothing applied.
77
+ * Performs minimal IO: writes a version marker file alongside installed
78
+ * skills so `workos doctor` can detect staleness later. Errors are swallowed
79
+ * so skill install never disrupts the calling flow.
80
+ *
81
+ * Thin back-compat wrapper around `refreshWorkOSSkills` — the install/auth-login
82
+ * call sites use this; doctor `--fix` (Phase 3) calls `refreshWorkOSSkills`
83
+ * directly to surface the per-agent before/after marker state.
23
84
  */
24
- export declare function autoInstallSkills(): Promise<void>;
85
+ export declare function autoInstallSkills(): Promise<AutoInstallResult | null>;
@@ -1,9 +1,42 @@
1
1
  import { homedir } from 'os';
2
- import { join } from 'path';
2
+ import { dirname, join } from 'path';
3
3
  import { existsSync } from 'fs';
4
- import { mkdir, copyFile, readdir } from 'fs/promises';
4
+ import { mkdir, mkdtemp, cp, rename, rm, readdir, readFile, stat, access, writeFile } from 'fs/promises';
5
5
  import chalk from 'chalk';
6
6
  import { getSkillsDir as getSkillsPackageDir } from '@workos/skills';
7
+ export const SKILL_VERSION_MARKER_FILENAME = '.workos-skill-version';
8
+ // Stale-orphan cutoff for `.workos.tmp-*` / `.workos.bak-*` siblings left behind
9
+ // by a crashed prior run. Anything younger may belong to a concurrent install
10
+ // and must NOT be removed.
11
+ const ORPHAN_STALE_MS = 60 * 60 * 1000;
12
+ /** Async equivalent of `existsSync` — `access` rejects with ENOENT when missing. */
13
+ async function pathExists(p) {
14
+ try {
15
+ await access(p);
16
+ return true;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ /**
23
+ * Read the bundled @workos/skills version by walking up from the skills
24
+ * directory to the package.json. The package's `exports` map doesn't expose
25
+ * package.json, so we resolve it by filesystem convention.
26
+ * Returns null if the version can't be determined — callers treat that as
27
+ * "no marker written" rather than failing the install.
28
+ */
29
+ export async function getBundledSkillsVersion(skillsDir = getSkillsPackageDir()) {
30
+ try {
31
+ // skillsDir = <packageRoot>/plugins/workos/skills
32
+ const packageRoot = dirname(dirname(dirname(skillsDir)));
33
+ const pkgJson = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8'));
34
+ return typeof pkgJson.version === 'string' ? pkgJson.version : null;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
7
40
  export function createAgents(home) {
8
41
  return {
9
42
  'claude-code': {
@@ -37,7 +70,9 @@ export function getSkillsDir() {
37
70
  }
38
71
  export async function discoverSkills(skillsDir) {
39
72
  const entries = await readdir(skillsDir, { withFileTypes: true });
40
- return entries.filter((e) => e.isDirectory() && existsSync(join(skillsDir, e.name, 'SKILL.md'))).map((e) => e.name);
73
+ const dirs = entries.filter((e) => e.isDirectory());
74
+ const checks = await Promise.all(dirs.map((e) => pathExists(join(skillsDir, e.name, 'SKILL.md'))));
75
+ return dirs.filter((_, i) => checks[i]).map((e) => e.name);
41
76
  }
42
77
  export function detectAgents(agents, filter) {
43
78
  const detected = [];
@@ -50,22 +85,84 @@ export function detectAgents(agents, filter) {
50
85
  }
51
86
  return detected;
52
87
  }
88
+ /**
89
+ * Recursively install a skill directory (SKILL.md + references/ + any other
90
+ * files) with prune-replace semantics. Uses a sibling temp dir + backup-rename
91
+ * pattern so the operation is effectively atomic per skill: the target either
92
+ * matches the source exactly, or (on rollback) is restored to its prior state.
93
+ *
94
+ * Returns `{ success, error }` rather than throwing — callers (autoInstallSkills,
95
+ * runInstallSkill) accumulate failures across the (skill × agent) matrix.
96
+ */
53
97
  export async function installSkill(skillsDir, skillName, agent) {
54
- const sourceFile = join(skillsDir, skillName, 'SKILL.md');
98
+ const sourceDir = join(skillsDir, skillName);
55
99
  const targetDir = join(agent.globalSkillsDir, skillName);
56
- const targetFile = join(targetDir, 'SKILL.md');
100
+ const parent = dirname(targetDir);
101
+ // Setup (mkdir parent, mkdtemp) is inside the try so EACCES / ENOTDIR / etc.
102
+ // surface as `{ success: false }` rather than rejecting — runInstallSkill and
103
+ // refreshWorkOSSkills accumulate failures across the (skill × agent) matrix
104
+ // and would otherwise abort the whole batch on a single bad agent dir.
105
+ let tempDir;
57
106
  try {
58
- await mkdir(targetDir, { recursive: true });
59
- await copyFile(sourceFile, targetFile);
107
+ await mkdir(parent, { recursive: true });
108
+ // Best-effort cleanup of OLD orphans only — never current-run paths.
109
+ await cleanupStaleOrphans(parent, skillName).catch(() => { });
110
+ // mkdtemp gives us atomic creation + a random suffix that prevents
111
+ // collisions between concurrent installers.
112
+ tempDir = await mkdtemp(join(parent, `.workos.tmp-${skillName}-`));
113
+ const backupDir = tempDir.replace('.workos.tmp-', '.workos.bak-');
114
+ await cp(sourceDir, tempDir, { recursive: true, errorOnExist: false });
115
+ const targetExisted = await pathExists(targetDir);
116
+ if (targetExisted) {
117
+ await rename(targetDir, backupDir);
118
+ }
119
+ try {
120
+ await rename(tempDir, targetDir);
121
+ }
122
+ catch (renameErr) {
123
+ if (targetExisted) {
124
+ await rename(backupDir, targetDir).catch(() => { });
125
+ }
126
+ throw renameErr;
127
+ }
128
+ // Backup cleanup is best-effort: target is already in place, so failure
129
+ // here leaves a stale backup that the next run's cleanup handles after 1h.
130
+ if (targetExisted) {
131
+ await rm(backupDir, { recursive: true, force: true }).catch(() => { });
132
+ }
60
133
  return { success: true };
61
134
  }
62
135
  catch (error) {
136
+ if (tempDir) {
137
+ await rm(tempDir, { recursive: true, force: true }).catch(() => { });
138
+ }
63
139
  return {
64
140
  success: false,
65
141
  error: error instanceof Error ? error.message : 'Unknown error',
66
142
  };
67
143
  }
68
144
  }
145
+ /**
146
+ * Remove `.workos.tmp-{skillName}-*` and `.workos.bak-{skillName}-*` siblings
147
+ * older than ORPHAN_STALE_MS. Fresh siblings (from a concurrent install) are
148
+ * preserved — destroying them would race the other run's final rename.
149
+ */
150
+ async function cleanupStaleOrphans(parent, skillName) {
151
+ if (!(await pathExists(parent)))
152
+ return;
153
+ const entries = await readdir(parent).catch(() => []);
154
+ const cutoff = Date.now() - ORPHAN_STALE_MS;
155
+ for (const entry of entries) {
156
+ const isOrphan = entry.startsWith(`.workos.tmp-${skillName}-`) || entry.startsWith(`.workos.bak-${skillName}-`);
157
+ if (!isOrphan)
158
+ continue;
159
+ const path = join(parent, entry);
160
+ const st = await stat(path).catch(() => null);
161
+ if (st && st.mtimeMs < cutoff) {
162
+ await rm(path, { recursive: true, force: true }).catch(() => { });
163
+ }
164
+ }
165
+ }
69
166
  export async function runInstallSkill(options) {
70
167
  const home = homedir();
71
168
  const agents = createAgents(home);
@@ -93,11 +190,7 @@ export async function runInstallSkill(options) {
93
190
  for (const skill of targetSkills) {
94
191
  for (const agent of targetAgents) {
95
192
  const result = await installSkill(skillsDir, skill, agent);
96
- results.push({
97
- skill,
98
- agent: agent.displayName,
99
- ...result,
100
- });
193
+ results.push({ skill, agent, ...result });
101
194
  }
102
195
  }
103
196
  const successful = results.filter((r) => r.success);
@@ -105,39 +198,132 @@ export async function runInstallSkill(options) {
105
198
  if (successful.length > 0) {
106
199
  console.log(chalk.green(`✓ Installed ${successful.length} skill(s):\n`));
107
200
  for (const r of successful) {
108
- console.log(` ${chalk.cyan(r.skill)} → ${chalk.dim(r.agent)}`);
201
+ console.log(` ${chalk.cyan(r.skill)} → ${chalk.dim(r.agent.displayName)}`);
202
+ }
203
+ }
204
+ // Write per-agent version markers for any agent that had at least one
205
+ // successful install, so `workos doctor` doesn't immediately flag the
206
+ // freshly-installed skills as stale or missing. Same primitive as
207
+ // refreshWorkOSSkills — single source of truth for marker semantics.
208
+ const version = await getBundledSkillsVersion(skillsDir);
209
+ if (version) {
210
+ const succeededAgents = new Set();
211
+ for (const r of successful)
212
+ succeededAgents.add(r.agent);
213
+ for (const agent of succeededAgents) {
214
+ await writeAgentSkillMarker(agent, version);
109
215
  }
110
216
  }
111
217
  if (failed.length > 0) {
112
218
  console.log(chalk.red(`\n✗ Failed to install ${failed.length}:\n`));
113
219
  for (const r of failed) {
114
- console.log(` ${r.skill} → ${r.agent}: ${chalk.dim(r.error)}`);
220
+ console.log(` ${r.skill} → ${r.agent.displayName}: ${chalk.dim(r.error)}`);
115
221
  }
116
222
  process.exit(1);
117
223
  }
118
224
  console.log(chalk.green('\nDone!'));
119
225
  }
226
+ async function readSkillVersionMarker(agent) {
227
+ const path = join(agent.globalSkillsDir, SKILL_VERSION_MARKER_FILENAME);
228
+ try {
229
+ return (await readFile(path, 'utf8')).trim() || null;
230
+ }
231
+ catch {
232
+ return null;
233
+ }
234
+ }
120
235
  /**
121
- * Silently install all bundled skills to all detected coding agents.
122
- * Errors are swallowed this must never disrupt the calling flow.
236
+ * Best-effort marker write any failure is swallowed (filesystem permission
237
+ * errors shouldn't fail the install; doctor treats missing markers as "unknown").
238
+ * Single source of truth for the .workos-skill-version write semantics.
123
239
  */
124
- export async function autoInstallSkills() {
240
+ async function writeAgentSkillMarker(agent, version) {
125
241
  try {
126
- const home = homedir();
127
- const agents = createAgents(home);
128
- const skillsDir = getSkillsDir();
129
- const skills = await discoverSkills(skillsDir);
130
- const targetAgents = detectAgents(agents);
131
- if (skills.length === 0 || targetAgents.length === 0)
132
- return;
242
+ await writeFile(join(agent.globalSkillsDir, SKILL_VERSION_MARKER_FILENAME), version, 'utf8');
243
+ }
244
+ catch {
245
+ // Marker is best-effort; doctor treats missing marker as "unknown".
246
+ }
247
+ }
248
+ /**
249
+ * Reusable primitive: discover bundled skills, install each one to each agent,
250
+ * write per-agent version markers, and report before/after marker state.
251
+ *
252
+ * Both `autoInstallSkills` (best-effort hook called from install/login) and
253
+ * `doctor --fix` (Phase 3) call this — there is no duplicate copy logic.
254
+ *
255
+ * Returns null when nothing applied (no agents detected, no skills found, or
256
+ * every install attempt failed).
257
+ */
258
+ export async function refreshWorkOSSkills(opts = {}) {
259
+ const home = homedir();
260
+ const skillsDir = getSkillsDir();
261
+ const detected = opts.agents ?? detectAgents(createAgents(home));
262
+ const allSkills = await discoverSkills(skillsDir).catch(() => []);
263
+ const skills = opts.skills ? allSkills.filter((s) => opts.skills.includes(s)) : allSkills;
264
+ const writeMarker = opts.writeMarker ?? true;
265
+ if (skills.length === 0 || detected.length === 0)
266
+ return null;
267
+ const version = await getBundledSkillsVersion(skillsDir);
268
+ const perAgentBefore = {};
269
+ const perAgentAfter = {};
270
+ const succeededAgents = [];
271
+ // Union of skills that succeeded for at least one agent. Returning the full
272
+ // attempted list would inflate "Installed N skills" copy when some skills
273
+ // failed to copy; only count what actually landed somewhere.
274
+ const installedSkills = new Set();
275
+ for (const agent of detected) {
276
+ perAgentBefore[agent.name] = await readSkillVersionMarker(agent);
277
+ let agentSucceeded = false;
133
278
  for (const skill of skills) {
134
- for (const agent of targetAgents) {
135
- await installSkill(skillsDir, skill, agent);
279
+ const result = await installSkill(skillsDir, skill, agent);
280
+ if (result.success) {
281
+ agentSucceeded = true;
282
+ installedSkills.add(skill);
283
+ }
284
+ }
285
+ if (agentSucceeded) {
286
+ succeededAgents.push(agent);
287
+ if (writeMarker && version) {
288
+ await writeAgentSkillMarker(agent, version);
136
289
  }
137
290
  }
291
+ perAgentAfter[agent.name] = await readSkillVersionMarker(agent);
292
+ }
293
+ if (succeededAgents.length === 0)
294
+ return null;
295
+ return {
296
+ agents: succeededAgents,
297
+ skills: skills.filter((s) => installedSkills.has(s)),
298
+ version,
299
+ perAgentBefore,
300
+ perAgentAfter,
301
+ };
302
+ }
303
+ /**
304
+ * Install all bundled skills to all detected coding agents.
305
+ * Returns a summary when anything was installed, or null when nothing applied.
306
+ * Performs minimal IO: writes a version marker file alongside installed
307
+ * skills so `workos doctor` can detect staleness later. Errors are swallowed
308
+ * so skill install never disrupts the calling flow.
309
+ *
310
+ * Thin back-compat wrapper around `refreshWorkOSSkills` — the install/auth-login
311
+ * call sites use this; doctor `--fix` (Phase 3) calls `refreshWorkOSSkills`
312
+ * directly to surface the per-agent before/after marker state.
313
+ */
314
+ export async function autoInstallSkills() {
315
+ try {
316
+ const result = await refreshWorkOSSkills();
317
+ if (!result)
318
+ return null;
319
+ return {
320
+ skills: result.skills,
321
+ agents: result.agents.map((a) => a.displayName),
322
+ version: result.version,
323
+ };
138
324
  }
139
325
  catch {
140
- // Intentionally swallowed — skill install is best-effort
326
+ return null;
141
327
  }
142
328
  }
143
329
  //# sourceMappingURL=install-skill.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"install-skill.js","sourceRoot":"","sources":["../../src/commands/install-skill.ts"],"names":[],"mappings":"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","sourcesContent":["import { homedir } from 'os';\nimport { join } from 'path';\nimport { existsSync } from 'fs';\nimport { mkdir, copyFile, readdir } from 'fs/promises';\nimport chalk from 'chalk';\nimport { getSkillsDir as getSkillsPackageDir } from '@workos/skills';\n\nexport interface AgentConfig {\n name: string;\n displayName: string;\n globalSkillsDir: string;\n detect: () => boolean;\n}\n\nexport function createAgents(home: string): Record<string, AgentConfig> {\n return {\n 'claude-code': {\n name: 'claude-code',\n displayName: 'Claude Code',\n globalSkillsDir: join(home, '.claude/skills'),\n detect: () => existsSync(join(home, '.claude')),\n },\n codex: {\n name: 'codex',\n displayName: 'Codex',\n globalSkillsDir: join(home, '.codex/skills'),\n detect: () => existsSync(join(home, '.codex')),\n },\n cursor: {\n name: 'cursor',\n displayName: 'Cursor',\n globalSkillsDir: join(home, '.cursor/skills'),\n detect: () => existsSync(join(home, '.cursor')),\n },\n goose: {\n name: 'goose',\n displayName: 'Goose',\n globalSkillsDir: join(home, '.config/goose/skills'),\n detect: () => existsSync(join(home, '.config/goose')),\n },\n };\n}\n\nexport interface InstallSkillOptions {\n skill?: string[];\n agent?: string[];\n}\n\nexport function getSkillsDir(): string {\n return getSkillsPackageDir();\n}\n\nexport async function discoverSkills(skillsDir: string): Promise<string[]> {\n const entries = await readdir(skillsDir, { withFileTypes: true });\n\n return entries.filter((e) => e.isDirectory() && existsSync(join(skillsDir, e.name, 'SKILL.md'))).map((e) => e.name);\n}\n\nexport function detectAgents(agents: Record<string, AgentConfig>, filter?: string[]): AgentConfig[] {\n const detected: AgentConfig[] = [];\n\n for (const [key, config] of Object.entries(agents)) {\n if (filter && !filter.includes(key)) continue;\n if (config.detect()) {\n detected.push(config);\n }\n }\n\n return detected;\n}\n\nexport async function installSkill(\n skillsDir: string,\n skillName: string,\n agent: AgentConfig,\n): Promise<{ success: boolean; error?: string }> {\n const sourceFile = join(skillsDir, skillName, 'SKILL.md');\n const targetDir = join(agent.globalSkillsDir, skillName);\n const targetFile = join(targetDir, 'SKILL.md');\n\n try {\n await mkdir(targetDir, { recursive: true });\n await copyFile(sourceFile, targetFile);\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n}\n\nexport async function runInstallSkill(options: InstallSkillOptions): Promise<void> {\n const home = homedir();\n const agents = createAgents(home);\n const skillsDir = getSkillsDir();\n const skills = await discoverSkills(skillsDir);\n\n const targetSkills = options.skill ? skills.filter((s) => options.skill!.includes(s)) : skills;\n\n if (targetSkills.length === 0) {\n console.error(chalk.red('No matching skills found.'));\n console.log('Available skills:', skills.join(', '));\n process.exit(1);\n }\n\n const targetAgents = detectAgents(agents, options.agent);\n\n if (targetAgents.length === 0) {\n if (options.agent) {\n console.error(chalk.red('Specified agents not found.'));\n } else {\n console.error(chalk.red('No coding agents detected.'));\n }\n console.log('Supported agents:', Object.keys(agents).join(', '));\n process.exit(1);\n }\n\n console.log(chalk.bold('\\nInstalling skills...\\n'));\n\n const results: Array<{\n skill: string;\n agent: string;\n success: boolean;\n error?: string;\n }> = [];\n\n for (const skill of targetSkills) {\n for (const agent of targetAgents) {\n const result = await installSkill(skillsDir, skill, agent);\n results.push({\n skill,\n agent: agent.displayName,\n ...result,\n });\n }\n }\n\n const successful = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n if (successful.length > 0) {\n console.log(chalk.green(`✓ Installed ${successful.length} skill(s):\\n`));\n for (const r of successful) {\n console.log(` ${chalk.cyan(r.skill)} → ${chalk.dim(r.agent)}`);\n }\n }\n\n if (failed.length > 0) {\n console.log(chalk.red(`\\n✗ Failed to install ${failed.length}:\\n`));\n for (const r of failed) {\n console.log(` ${r.skill} → ${r.agent}: ${chalk.dim(r.error)}`);\n }\n process.exit(1);\n }\n\n console.log(chalk.green('\\nDone!'));\n}\n\n/**\n * Silently install all bundled skills to all detected coding agents.\n * Errors are swallowed — this must never disrupt the calling flow.\n */\nexport async function autoInstallSkills(): Promise<void> {\n try {\n const home = homedir();\n const agents = createAgents(home);\n const skillsDir = getSkillsDir();\n const skills = await discoverSkills(skillsDir);\n const targetAgents = detectAgents(agents);\n\n if (skills.length === 0 || targetAgents.length === 0) return;\n\n for (const skill of skills) {\n for (const agent of targetAgents) {\n await installSkill(skillsDir, skill, agent);\n }\n }\n } catch {\n // Intentionally swallowed — skill install is best-effort\n }\n}\n"]}
1
+ {"version":3,"file":"install-skill.js","sourceRoot":"","sources":["../../src/commands/install-skill.ts"],"names":[],"mappings":"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","sourcesContent":["import { homedir } from 'os';\nimport { dirname, join } from 'path';\nimport { existsSync } from 'fs';\nimport { mkdir, mkdtemp, cp, rename, rm, readdir, readFile, stat, access, writeFile } from 'fs/promises';\nimport chalk from 'chalk';\nimport { getSkillsDir as getSkillsPackageDir } from '@workos/skills';\n\nexport const SKILL_VERSION_MARKER_FILENAME = '.workos-skill-version';\n\n// Stale-orphan cutoff for `.workos.tmp-*` / `.workos.bak-*` siblings left behind\n// by a crashed prior run. Anything younger may belong to a concurrent install\n// and must NOT be removed.\nconst ORPHAN_STALE_MS = 60 * 60 * 1000;\n\n/** Async equivalent of `existsSync` — `access` rejects with ENOENT when missing. */\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Read the bundled @workos/skills version by walking up from the skills\n * directory to the package.json. The package's `exports` map doesn't expose\n * package.json, so we resolve it by filesystem convention.\n * Returns null if the version can't be determined — callers treat that as\n * \"no marker written\" rather than failing the install.\n */\nexport async function getBundledSkillsVersion(skillsDir: string = getSkillsPackageDir()): Promise<string | null> {\n try {\n // skillsDir = <packageRoot>/plugins/workos/skills\n const packageRoot = dirname(dirname(dirname(skillsDir)));\n const pkgJson = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8'));\n return typeof pkgJson.version === 'string' ? pkgJson.version : null;\n } catch {\n return null;\n }\n}\n\nexport interface AgentConfig {\n name: string;\n displayName: string;\n globalSkillsDir: string;\n detect: () => boolean;\n}\n\nexport function createAgents(home: string): Record<string, AgentConfig> {\n return {\n 'claude-code': {\n name: 'claude-code',\n displayName: 'Claude Code',\n globalSkillsDir: join(home, '.claude/skills'),\n detect: () => existsSync(join(home, '.claude')),\n },\n codex: {\n name: 'codex',\n displayName: 'Codex',\n globalSkillsDir: join(home, '.codex/skills'),\n detect: () => existsSync(join(home, '.codex')),\n },\n cursor: {\n name: 'cursor',\n displayName: 'Cursor',\n globalSkillsDir: join(home, '.cursor/skills'),\n detect: () => existsSync(join(home, '.cursor')),\n },\n goose: {\n name: 'goose',\n displayName: 'Goose',\n globalSkillsDir: join(home, '.config/goose/skills'),\n detect: () => existsSync(join(home, '.config/goose')),\n },\n };\n}\n\nexport interface InstallSkillOptions {\n skill?: string[];\n agent?: string[];\n}\n\nexport function getSkillsDir(): string {\n return getSkillsPackageDir();\n}\n\nexport async function discoverSkills(skillsDir: string): Promise<string[]> {\n const entries = await readdir(skillsDir, { withFileTypes: true });\n\n const dirs = entries.filter((e) => e.isDirectory());\n const checks = await Promise.all(dirs.map((e) => pathExists(join(skillsDir, e.name, 'SKILL.md'))));\n return dirs.filter((_, i) => checks[i]).map((e) => e.name);\n}\n\nexport function detectAgents(agents: Record<string, AgentConfig>, filter?: string[]): AgentConfig[] {\n const detected: AgentConfig[] = [];\n\n for (const [key, config] of Object.entries(agents)) {\n if (filter && !filter.includes(key)) continue;\n if (config.detect()) {\n detected.push(config);\n }\n }\n\n return detected;\n}\n\n/**\n * Recursively install a skill directory (SKILL.md + references/ + any other\n * files) with prune-replace semantics. Uses a sibling temp dir + backup-rename\n * pattern so the operation is effectively atomic per skill: the target either\n * matches the source exactly, or (on rollback) is restored to its prior state.\n *\n * Returns `{ success, error }` rather than throwing — callers (autoInstallSkills,\n * runInstallSkill) accumulate failures across the (skill × agent) matrix.\n */\nexport async function installSkill(\n skillsDir: string,\n skillName: string,\n agent: AgentConfig,\n): Promise<{ success: boolean; error?: string }> {\n const sourceDir = join(skillsDir, skillName);\n const targetDir = join(agent.globalSkillsDir, skillName);\n const parent = dirname(targetDir);\n\n // Setup (mkdir parent, mkdtemp) is inside the try so EACCES / ENOTDIR / etc.\n // surface as `{ success: false }` rather than rejecting — runInstallSkill and\n // refreshWorkOSSkills accumulate failures across the (skill × agent) matrix\n // and would otherwise abort the whole batch on a single bad agent dir.\n let tempDir: string | undefined;\n try {\n await mkdir(parent, { recursive: true });\n // Best-effort cleanup of OLD orphans only — never current-run paths.\n await cleanupStaleOrphans(parent, skillName).catch(() => {});\n\n // mkdtemp gives us atomic creation + a random suffix that prevents\n // collisions between concurrent installers.\n tempDir = await mkdtemp(join(parent, `.workos.tmp-${skillName}-`));\n const backupDir = tempDir.replace('.workos.tmp-', '.workos.bak-');\n\n await cp(sourceDir, tempDir, { recursive: true, errorOnExist: false });\n\n const targetExisted = await pathExists(targetDir);\n if (targetExisted) {\n await rename(targetDir, backupDir);\n }\n try {\n await rename(tempDir, targetDir);\n } catch (renameErr) {\n if (targetExisted) {\n await rename(backupDir, targetDir).catch(() => {});\n }\n throw renameErr;\n }\n // Backup cleanup is best-effort: target is already in place, so failure\n // here leaves a stale backup that the next run's cleanup handles after 1h.\n if (targetExisted) {\n await rm(backupDir, { recursive: true, force: true }).catch(() => {});\n }\n return { success: true };\n } catch (error) {\n if (tempDir) {\n await rm(tempDir, { recursive: true, force: true }).catch(() => {});\n }\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Remove `.workos.tmp-{skillName}-*` and `.workos.bak-{skillName}-*` siblings\n * older than ORPHAN_STALE_MS. Fresh siblings (from a concurrent install) are\n * preserved — destroying them would race the other run's final rename.\n */\nasync function cleanupStaleOrphans(parent: string, skillName: string): Promise<void> {\n if (!(await pathExists(parent))) return;\n const entries = await readdir(parent).catch(() => []);\n const cutoff = Date.now() - ORPHAN_STALE_MS;\n for (const entry of entries) {\n const isOrphan = entry.startsWith(`.workos.tmp-${skillName}-`) || entry.startsWith(`.workos.bak-${skillName}-`);\n if (!isOrphan) continue;\n const path = join(parent, entry);\n const st = await stat(path).catch(() => null);\n if (st && st.mtimeMs < cutoff) {\n await rm(path, { recursive: true, force: true }).catch(() => {});\n }\n }\n}\n\nexport async function runInstallSkill(options: InstallSkillOptions): Promise<void> {\n const home = homedir();\n const agents = createAgents(home);\n const skillsDir = getSkillsDir();\n const skills = await discoverSkills(skillsDir);\n\n const targetSkills = options.skill ? skills.filter((s) => options.skill!.includes(s)) : skills;\n\n if (targetSkills.length === 0) {\n console.error(chalk.red('No matching skills found.'));\n console.log('Available skills:', skills.join(', '));\n process.exit(1);\n }\n\n const targetAgents = detectAgents(agents, options.agent);\n\n if (targetAgents.length === 0) {\n if (options.agent) {\n console.error(chalk.red('Specified agents not found.'));\n } else {\n console.error(chalk.red('No coding agents detected.'));\n }\n console.log('Supported agents:', Object.keys(agents).join(', '));\n process.exit(1);\n }\n\n console.log(chalk.bold('\\nInstalling skills...\\n'));\n\n const results: Array<{\n skill: string;\n agent: AgentConfig;\n success: boolean;\n error?: string;\n }> = [];\n\n for (const skill of targetSkills) {\n for (const agent of targetAgents) {\n const result = await installSkill(skillsDir, skill, agent);\n results.push({ skill, agent, ...result });\n }\n }\n\n const successful = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n if (successful.length > 0) {\n console.log(chalk.green(`✓ Installed ${successful.length} skill(s):\\n`));\n for (const r of successful) {\n console.log(` ${chalk.cyan(r.skill)} → ${chalk.dim(r.agent.displayName)}`);\n }\n }\n\n // Write per-agent version markers for any agent that had at least one\n // successful install, so `workos doctor` doesn't immediately flag the\n // freshly-installed skills as stale or missing. Same primitive as\n // refreshWorkOSSkills — single source of truth for marker semantics.\n const version = await getBundledSkillsVersion(skillsDir);\n if (version) {\n const succeededAgents = new Set<AgentConfig>();\n for (const r of successful) succeededAgents.add(r.agent);\n for (const agent of succeededAgents) {\n await writeAgentSkillMarker(agent, version);\n }\n }\n\n if (failed.length > 0) {\n console.log(chalk.red(`\\n✗ Failed to install ${failed.length}:\\n`));\n for (const r of failed) {\n console.log(` ${r.skill} → ${r.agent.displayName}: ${chalk.dim(r.error)}`);\n }\n process.exit(1);\n }\n\n console.log(chalk.green('\\nDone!'));\n}\n\nexport interface AutoInstallResult {\n skills: string[];\n agents: string[];\n version: string | null;\n}\n\nexport interface RefreshOptions {\n /** Pre-detected agents. Default: detect from $HOME. */\n agents?: AgentConfig[];\n /** Skill names to install. Default: all bundled skills. */\n skills?: string[];\n /** Whether to write the version marker after a successful per-agent install. Default: true. */\n writeMarker?: boolean;\n}\n\nexport interface RefreshResult {\n /** Agents where at least one skill installed successfully. */\n agents: AgentConfig[];\n /** Skills that were attempted (the resolved set after filtering). */\n skills: string[];\n /** Bundled skills package version, or null if it couldn't be resolved. */\n version: string | null;\n /** Marker version per agent.name BEFORE refresh (null = no marker / unreadable). */\n perAgentBefore: Record<string, string | null>;\n /** Marker version per agent.name AFTER refresh. */\n perAgentAfter: Record<string, string | null>;\n}\n\nasync function readSkillVersionMarker(agent: AgentConfig): Promise<string | null> {\n const path = join(agent.globalSkillsDir, SKILL_VERSION_MARKER_FILENAME);\n try {\n return (await readFile(path, 'utf8')).trim() || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Best-effort marker write — any failure is swallowed (filesystem permission\n * errors shouldn't fail the install; doctor treats missing markers as \"unknown\").\n * Single source of truth for the .workos-skill-version write semantics.\n */\nasync function writeAgentSkillMarker(agent: AgentConfig, version: string): Promise<void> {\n try {\n await writeFile(join(agent.globalSkillsDir, SKILL_VERSION_MARKER_FILENAME), version, 'utf8');\n } catch {\n // Marker is best-effort; doctor treats missing marker as \"unknown\".\n }\n}\n\n/**\n * Reusable primitive: discover bundled skills, install each one to each agent,\n * write per-agent version markers, and report before/after marker state.\n *\n * Both `autoInstallSkills` (best-effort hook called from install/login) and\n * `doctor --fix` (Phase 3) call this — there is no duplicate copy logic.\n *\n * Returns null when nothing applied (no agents detected, no skills found, or\n * every install attempt failed).\n */\nexport async function refreshWorkOSSkills(opts: RefreshOptions = {}): Promise<RefreshResult | null> {\n const home = homedir();\n const skillsDir = getSkillsDir();\n const detected = opts.agents ?? detectAgents(createAgents(home));\n const allSkills = await discoverSkills(skillsDir).catch(() => []);\n const skills = opts.skills ? allSkills.filter((s) => opts.skills!.includes(s)) : allSkills;\n const writeMarker = opts.writeMarker ?? true;\n\n if (skills.length === 0 || detected.length === 0) return null;\n\n const version = await getBundledSkillsVersion(skillsDir);\n const perAgentBefore: Record<string, string | null> = {};\n const perAgentAfter: Record<string, string | null> = {};\n const succeededAgents: AgentConfig[] = [];\n // Union of skills that succeeded for at least one agent. Returning the full\n // attempted list would inflate \"Installed N skills\" copy when some skills\n // failed to copy; only count what actually landed somewhere.\n const installedSkills = new Set<string>();\n\n for (const agent of detected) {\n perAgentBefore[agent.name] = await readSkillVersionMarker(agent);\n\n let agentSucceeded = false;\n for (const skill of skills) {\n const result = await installSkill(skillsDir, skill, agent);\n if (result.success) {\n agentSucceeded = true;\n installedSkills.add(skill);\n }\n }\n\n if (agentSucceeded) {\n succeededAgents.push(agent);\n if (writeMarker && version) {\n await writeAgentSkillMarker(agent, version);\n }\n }\n\n perAgentAfter[agent.name] = await readSkillVersionMarker(agent);\n }\n\n if (succeededAgents.length === 0) return null;\n\n return {\n agents: succeededAgents,\n skills: skills.filter((s) => installedSkills.has(s)),\n version,\n perAgentBefore,\n perAgentAfter,\n };\n}\n\n/**\n * Install all bundled skills to all detected coding agents.\n * Returns a summary when anything was installed, or null when nothing applied.\n * Performs minimal IO: writes a version marker file alongside installed\n * skills so `workos doctor` can detect staleness later. Errors are swallowed\n * so skill install never disrupts the calling flow.\n *\n * Thin back-compat wrapper around `refreshWorkOSSkills` — the install/auth-login\n * call sites use this; doctor `--fix` (Phase 3) calls `refreshWorkOSSkills`\n * directly to surface the per-agent before/after marker state.\n */\nexport async function autoInstallSkills(): Promise<AutoInstallResult | null> {\n try {\n const result = await refreshWorkOSSkills();\n if (!result) return null;\n return {\n skills: result.skills,\n agents: result.agents.map((a) => a.displayName),\n version: result.version,\n };\n } catch {\n return null;\n }\n}\n"]}
@@ -24,7 +24,11 @@ export async function handleInstall(argv) {
24
24
  }
25
25
  try {
26
26
  await runInstaller(options);
27
- await autoInstallSkills();
27
+ const skillResult = await autoInstallSkills();
28
+ if (skillResult && !isJsonMode()) {
29
+ const skillWord = skillResult.skills.length === 1 ? 'skill' : 'skills';
30
+ clack.log.info(`Installed ${skillResult.skills.length} WorkOS ${skillWord} for ${skillResult.agents.join(', ')}. Your coding agent now has up-to-date WorkOS guidance.`);
31
+ }
28
32
  process.exit(0);
29
33
  }
30
34
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,MAAM,mBAAmB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAuC;IACzE,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAE5B,qBAAqB;IACrB,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACf,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,aAAa,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,oDAAoD,EAAE,CAAC,CAAC;QACzG,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YACtB,aAAa,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,4DAA4D,EAAE,CAAC,CAAC;QACjH,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;YACxB,aAAa,CAAC;gBACZ,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,yEAAyE;aACnF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;QAC5B,MAAM,iBAAiB,EAAE,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;QAC7D,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;QAEjC,IAAI,UAAU,EAAE,EAAE,CAAC;YACjB,aAAa,CAAC;gBACZ,IAAI,EAAE,iBAAiB;gBACvB,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;aAC9D,CAAC,CAAC;QACL,CAAC;QAED,IAAI,OAAO,CAAC,KAAK,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACvD,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,OAAO,EAAE,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC","sourcesContent":["import { runInstaller } from '../run.js';\nimport type { InstallerArgs } from '../run.js';\nimport clack from '../utils/clack.js';\nimport { exitWithError, isJsonMode } from '../utils/output.js';\nimport type { ArgumentsCamelCase } from 'yargs';\nimport { autoInstallSkills } from './install-skill.js';\n\n/**\n * Handle install command execution.\n */\nexport async function handleInstall(argv: ArgumentsCamelCase<InstallerArgs>): Promise<void> {\n const options = { ...argv };\n\n // CI mode validation\n if (options.ci) {\n if (!options.apiKey) {\n exitWithError({ code: 'missing_args', message: 'CI mode requires --api-key (WorkOS API key sk_xxx)' });\n }\n if (!options.clientId) {\n exitWithError({ code: 'missing_args', message: 'CI mode requires --client-id (WorkOS Client ID client_xxx)' });\n }\n if (!options.installDir) {\n exitWithError({\n code: 'missing_args',\n message: 'CI mode requires --install-dir (directory to install WorkOS AuthKit in)',\n });\n }\n }\n\n try {\n await runInstaller(options);\n await autoInstallSkills();\n process.exit(0);\n } catch (err) {\n const { getLogFilePath } = await import('../utils/debug.js');\n const logPath = getLogFilePath();\n\n if (isJsonMode()) {\n exitWithError({\n code: 'installer_error',\n message: err instanceof Error ? err.message : 'Unknown error',\n });\n }\n\n if (options.debug && err instanceof Error && err.stack) {\n console.error(err.stack);\n }\n if (logPath) {\n clack.log.info(`Debug logs: ${logPath}`);\n }\n process.exit(1);\n }\n}\n"]}
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,MAAM,mBAAmB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAuC;IACzE,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAE5B,qBAAqB;IACrB,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACf,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,aAAa,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,oDAAoD,EAAE,CAAC,CAAC;QACzG,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YACtB,aAAa,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,4DAA4D,EAAE,CAAC,CAAC;QACjH,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;YACxB,aAAa,CAAC;gBACZ,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,yEAAyE;aACnF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;QAC5B,MAAM,WAAW,GAAG,MAAM,iBAAiB,EAAE,CAAC;QAC9C,IAAI,WAAW,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;YACjC,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;YACvE,KAAK,CAAC,GAAG,CAAC,IAAI,CACZ,aAAa,WAAW,CAAC,MAAM,CAAC,MAAM,WAAW,SAAS,QAAQ,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,yDAAyD,CACzJ,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;QAC7D,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;QAEjC,IAAI,UAAU,EAAE,EAAE,CAAC;YACjB,aAAa,CAAC;gBACZ,IAAI,EAAE,iBAAiB;gBACvB,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;aAC9D,CAAC,CAAC;QACL,CAAC;QAED,IAAI,OAAO,CAAC,KAAK,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACvD,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,OAAO,EAAE,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC","sourcesContent":["import { runInstaller } from '../run.js';\nimport type { InstallerArgs } from '../run.js';\nimport clack from '../utils/clack.js';\nimport { exitWithError, isJsonMode } from '../utils/output.js';\nimport type { ArgumentsCamelCase } from 'yargs';\nimport { autoInstallSkills } from './install-skill.js';\n\n/**\n * Handle install command execution.\n */\nexport async function handleInstall(argv: ArgumentsCamelCase<InstallerArgs>): Promise<void> {\n const options = { ...argv };\n\n // CI mode validation\n if (options.ci) {\n if (!options.apiKey) {\n exitWithError({ code: 'missing_args', message: 'CI mode requires --api-key (WorkOS API key sk_xxx)' });\n }\n if (!options.clientId) {\n exitWithError({ code: 'missing_args', message: 'CI mode requires --client-id (WorkOS Client ID client_xxx)' });\n }\n if (!options.installDir) {\n exitWithError({\n code: 'missing_args',\n message: 'CI mode requires --install-dir (directory to install WorkOS AuthKit in)',\n });\n }\n }\n\n try {\n await runInstaller(options);\n const skillResult = await autoInstallSkills();\n if (skillResult && !isJsonMode()) {\n const skillWord = skillResult.skills.length === 1 ? 'skill' : 'skills';\n clack.log.info(\n `Installed ${skillResult.skills.length} WorkOS ${skillWord} for ${skillResult.agents.join(', ')}. Your coding agent now has up-to-date WorkOS guidance.`,\n );\n }\n process.exit(0);\n } catch (err) {\n const { getLogFilePath } = await import('../utils/debug.js');\n const logPath = getLogFilePath();\n\n if (isJsonMode()) {\n exitWithError({\n code: 'installer_error',\n message: err instanceof Error ? err.message : 'Unknown error',\n });\n }\n\n if (options.debug && err instanceof Error && err.stack) {\n console.error(err.stack);\n }\n if (logPath) {\n clack.log.info(`Debug logs: ${logPath}`);\n }\n process.exit(1);\n }\n}\n"]}
@@ -1,3 +1,16 @@
1
+ /**
2
+ * Best-effort skill install after a successful auth-login.
3
+ *
4
+ * Mirrors the install.ts hook copy, but wraps `autoInstallSkills` in its own
5
+ * try/catch AND a 30s timeout so a skill install hang (e.g. blocked filesystem
6
+ * call) never blocks login completion. Login already succeeded by the time
7
+ * this runs — the user having a working session is the contract that must hold.
8
+ *
9
+ * Extracted from runLogin so it can be unit-tested without standing up the
10
+ * device-auth polling loop.
11
+ */
12
+ export declare const SKILL_INSTALL_TIMEOUT_MS: number;
13
+ export declare function installSkillsAfterLogin(): Promise<void>;
1
14
  /**
2
15
  * Auto-provision a staging environment after login.
3
16
  *
@@ -8,6 +8,8 @@ import { logInfo, logError } from '../utils/debug.js';
8
8
  import { fetchStagingCredentials } from '../lib/staging-api.js';
9
9
  import { getConfig, saveConfig } from '../lib/config-store.js';
10
10
  import { formatWorkOSCommand } from '../utils/command-invocation.js';
11
+ import { autoInstallSkills } from './install-skill.js';
12
+ import { isJsonMode } from '../utils/output.js';
11
13
  /**
12
14
  * Parse JWT payload
13
15
  */
@@ -45,6 +47,41 @@ function getConnectEndpoints() {
45
47
  function sleep(ms) {
46
48
  return new Promise((resolve) => setTimeout(resolve, ms));
47
49
  }
50
+ /**
51
+ * Best-effort skill install after a successful auth-login.
52
+ *
53
+ * Mirrors the install.ts hook copy, but wraps `autoInstallSkills` in its own
54
+ * try/catch AND a 30s timeout so a skill install hang (e.g. blocked filesystem
55
+ * call) never blocks login completion. Login already succeeded by the time
56
+ * this runs — the user having a working session is the contract that must hold.
57
+ *
58
+ * Extracted from runLogin so it can be unit-tested without standing up the
59
+ * device-auth polling loop.
60
+ */
61
+ export const SKILL_INSTALL_TIMEOUT_MS = 30 * 1000;
62
+ export async function installSkillsAfterLogin() {
63
+ let timeoutHandle;
64
+ try {
65
+ const timeout = new Promise((resolve) => {
66
+ timeoutHandle = setTimeout(() => resolve(null), SKILL_INSTALL_TIMEOUT_MS);
67
+ // Don't keep the event loop alive on this timer — process should exit
68
+ // immediately if everything else has resolved.
69
+ timeoutHandle.unref?.();
70
+ });
71
+ const result = await Promise.race([autoInstallSkills(), timeout]);
72
+ if (result && !isJsonMode()) {
73
+ const skillWord = result.skills.length === 1 ? 'skill' : 'skills';
74
+ clack.log.info(`Installed ${result.skills.length} WorkOS ${skillWord} for ${result.agents.join(', ')}.`);
75
+ }
76
+ }
77
+ catch {
78
+ // Skill install must never fail login.
79
+ }
80
+ finally {
81
+ if (timeoutHandle)
82
+ clearTimeout(timeoutHandle);
83
+ }
84
+ }
48
85
  /**
49
86
  * Auto-provision a staging environment after login.
50
87
  *
@@ -177,6 +214,9 @@ export async function runLogin() {
177
214
  else {
178
215
  clack.log.info(chalk.dim('Run `workos env add` to configure an environment manually'));
179
216
  }
217
+ // Best-effort skill install. Wrapped helper guarantees login never
218
+ // fails on skill errors.
219
+ await installSkillsAfterLogin();
180
220
  return;
181
221
  }
182
222
  const errorData = data;