@gobing-ai/ts-ai-runner 0.2.4 → 0.2.5

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.
@@ -76,7 +76,7 @@ export class AgentDetector {
76
76
  return {
77
77
  name: agent,
78
78
  installed: true,
79
- version: output.split('\n')[0] ?? match.groups.version,
79
+ version: match.groups.version,
80
80
  channels: [],
81
81
  error: null,
82
82
  };
@@ -44,5 +44,7 @@ export declare class DoctorRunner {
44
44
  runOne(agent: string): Promise<DoctorResult>;
45
45
  private buildResult;
46
46
  private checkAuth;
47
+ /** True when the path exists and has a non-zero size. */
48
+ private hasNonEmptyFile;
47
49
  }
48
50
  //# sourceMappingURL=doctor-runner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"doctor-runner.d.ts","sourceRoot":"","sources":["../src/doctor-runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAsB,MAAM,kBAAkB,CAAC;AAErE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,gDAAgD;AAChD,MAAM,WAAW,YAAY;IACzB,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,+CAA+C;IAC/C,aAAa,EAAE,OAAO,CAAC;IACvB,iDAAiD;IACjD,MAAM,EAAE,OAAO,CAAC;IAChB,oEAAoE;IACpE,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;IACZ,wDAAwD;IACxD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,4CAA4C;AAC5C,MAAM,WAAW,mBAAmB;IAChC,uBAAuB;IACvB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,qBAAqB;IACrB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC5C;AAID,4EAA4E;AAC5E,qBAAa,YAAY;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqC;IACzD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAwB;gBAE/B,OAAO,GAAE,mBAAwB;IAO7C,kDAAkD;IAC5C,MAAM,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAKvC,uCAAuC;IACjC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;YAIpC,WAAW;YAgBX,SAAS;CAa1B"}
1
+ {"version":3,"file":"doctor-runner.d.ts","sourceRoot":"","sources":["../src/doctor-runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAsB,MAAM,kBAAkB,CAAC;AAErE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,gDAAgD;AAChD,MAAM,WAAW,YAAY;IACzB,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,+CAA+C;IAC/C,aAAa,EAAE,OAAO,CAAC;IACvB,iDAAiD;IACjD,MAAM,EAAE,OAAO,CAAC;IAChB,oEAAoE;IACpE,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;IACZ,wDAAwD;IACxD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,4CAA4C;AAC5C,MAAM,WAAW,mBAAmB;IAChC,uBAAuB;IACvB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,qBAAqB;IACrB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC5C;AASD,4EAA4E;AAC5E,qBAAa,YAAY;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqC;IACzD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAwB;gBAE/B,OAAO,GAAE,mBAAwB;IAO7C,kDAAkD;IAC5C,MAAM,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAKvC,uCAAuC;IACjC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;YAIpC,WAAW;YAgBX,SAAS;IAmBvB,yDAAyD;YAC3C,eAAe;CAKhC"}
@@ -5,6 +5,10 @@ import { AgentDetector } from './agent-detector.js';
5
5
  import { isAgentName, TIER2_AGENTS } from './agents/shims.js';
6
6
  import { AiRunner } from './ai-runner.js';
7
7
  const DEFAULT_TIMEOUT_MS = 5_000;
8
+ /** True when a value is a defined, non-blank string. */
9
+ function isNonEmpty(value) {
10
+ return value !== undefined && value.trim().length > 0;
11
+ }
8
12
  /** Runs installation and auth health checks for supported coding agents. */
9
13
  export class DoctorRunner {
10
14
  detector;
@@ -42,11 +46,16 @@ export class DoctorRunner {
42
46
  };
43
47
  }
44
48
  async checkAuth(agent) {
49
+ // gemini/codex expose no auth-status command; treat a non-empty credential
50
+ // file as authenticated. An empty/zero-byte file is a stale-credential
51
+ // false positive, so existence alone is insufficient.
45
52
  if (agent === 'gemini')
46
- return this.fs.exists(join(homedir(), '.gemini', 'settings.json'));
47
- if (agent === 'codex' && (await this.fs.exists(join(homedir(), '.codex', 'auth.json'))))
53
+ return this.hasNonEmptyFile(join(homedir(), '.gemini', 'settings.json'));
54
+ if (agent === 'codex' && (await this.hasNonEmptyFile(join(homedir(), '.codex', 'auth.json'))))
48
55
  return true;
49
- if (agent === 'pi' && (this.env.GOOGLE_API_KEY !== undefined || this.env.ANTHROPIC_API_KEY !== undefined))
56
+ // pi reads provider keys from the environment; require a non-empty value
57
+ // rather than mere presence (an empty export is not a usable credential).
58
+ if (agent === 'pi' && (isNonEmpty(this.env.GOOGLE_API_KEY) || isNonEmpty(this.env.ANTHROPIC_API_KEY)))
50
59
  return true;
51
60
  const command = this.runner.runAuthCommand(agent, { timeout: this.timeout });
52
61
  if (command === null)
@@ -55,4 +64,11 @@ export class DoctorRunner {
55
64
  return (result.exitCode === 0 &&
56
65
  !/not authenticated|not logged|unauthenticated/i.test(`${result.stdout}\n${result.stderr}`));
57
66
  }
67
+ /** True when the path exists and has a non-zero size. */
68
+ async hasNonEmptyFile(path) {
69
+ const stat = await this.fs.stat(path);
70
+ if (stat === null)
71
+ return false;
72
+ return stat.isFile() && stat.size > 0;
73
+ }
58
74
  }
package/dist/index.d.ts CHANGED
@@ -2,4 +2,5 @@ export * from './agent-detector';
2
2
  export * from './agents/shims';
3
3
  export * from './ai-runner';
4
4
  export * from './doctor-runner';
5
+ export * from './slash-command';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -2,3 +2,4 @@ export * from './agent-detector.js';
2
2
  export * from './agents/shims.js';
3
3
  export * from './ai-runner.js';
4
4
  export * from './doctor-runner.js';
5
+ export * from './slash-command.js';
@@ -0,0 +1,16 @@
1
+ import type { AgentName } from './agents/shims';
2
+ /** Whether the input is a Claude-style `/plugin:command` slash command. */
3
+ export declare function isClaudeStyleSlashCommand(input: string): boolean;
4
+ /**
5
+ * Translate a Claude-style `/plugin:command args` into the target agent's
6
+ * slash-command dialect. Non-slash-command input is returned unchanged.
7
+ *
8
+ * | Agent | `/plugin:command args` becomes |
9
+ * |------------|---------------------------------------|
10
+ * | claude | `/plugin:command args` (pass-through) |
11
+ * | codex | `$plugin-command args` |
12
+ * | pi | `/skill:plugin-command args` |
13
+ * | all others | `/plugin-command args` |
14
+ */
15
+ export declare function translateSlashCommand(agent: AgentName, input: string): string;
16
+ //# sourceMappingURL=slash-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slash-command.d.ts","sourceRoot":"","sources":["../src/slash-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAShD,2EAA2E;AAC3E,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAgB7E"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Matches a Claude-style slash command: `/plugin:command [args]`.
3
+ *
4
+ * Captures the plugin namespace, the command, and any trailing argument text.
5
+ */
6
+ const SLASH_COMMAND_RE = /^\/([a-zA-Z0-9._-]+):([a-zA-Z0-9._-]+)(\s.*)?$/;
7
+ /** Whether the input is a Claude-style `/plugin:command` slash command. */
8
+ export function isClaudeStyleSlashCommand(input) {
9
+ return SLASH_COMMAND_RE.test(input);
10
+ }
11
+ /**
12
+ * Translate a Claude-style `/plugin:command args` into the target agent's
13
+ * slash-command dialect. Non-slash-command input is returned unchanged.
14
+ *
15
+ * | Agent | `/plugin:command args` becomes |
16
+ * |------------|---------------------------------------|
17
+ * | claude | `/plugin:command args` (pass-through) |
18
+ * | codex | `$plugin-command args` |
19
+ * | pi | `/skill:plugin-command args` |
20
+ * | all others | `/plugin-command args` |
21
+ */
22
+ export function translateSlashCommand(agent, input) {
23
+ const match = SLASH_COMMAND_RE.exec(input);
24
+ if (!match)
25
+ return input;
26
+ const [, plugin, command, rest = ''] = match;
27
+ const args = rest.trimStart();
28
+ const suffix = args.length > 0 ? ` ${args}` : '';
29
+ switch (agent) {
30
+ case 'claude':
31
+ return `/${plugin}:${command}${suffix}`;
32
+ case 'codex':
33
+ return `$${plugin}-${command}${suffix}`;
34
+ case 'pi':
35
+ return `/skill:${plugin}-${command}${suffix}`;
36
+ default:
37
+ return `/${plugin}-${command}${suffix}`;
38
+ }
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-ai-runner",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "@gobing-ai/ts-ai-runner — Coding-agent shims, detection, doctor checks, and prompt execution.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -47,7 +47,7 @@
47
47
  "release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-ai-runner-v<version> && git push --tags' && exit 1"
48
48
  },
49
49
  "dependencies": {
50
- "@gobing-ai/ts-runtime": "^0.2.4"
50
+ "@gobing-ai/ts-runtime": "^0.2.5"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/bun": "1.3.14"
@@ -103,7 +103,7 @@ export class AgentDetector {
103
103
  return {
104
104
  name: agent,
105
105
  installed: true,
106
- version: output.split('\n')[0] ?? match.groups.version,
106
+ version: match.groups.version,
107
107
  channels: [],
108
108
  error: null,
109
109
  };
@@ -39,6 +39,11 @@ export interface DoctorRunnerOptions {
39
39
 
40
40
  const DEFAULT_TIMEOUT_MS = 5_000;
41
41
 
42
+ /** True when a value is a defined, non-blank string. */
43
+ function isNonEmpty(value: string | undefined): boolean {
44
+ return value !== undefined && value.trim().length > 0;
45
+ }
46
+
42
47
  /** Runs installation and auth health checks for supported coding agents. */
43
48
  export class DoctorRunner {
44
49
  private readonly detector: AgentDetector;
@@ -82,9 +87,14 @@ export class DoctorRunner {
82
87
  }
83
88
 
84
89
  private async checkAuth(agent: AgentName): Promise<boolean> {
85
- if (agent === 'gemini') return this.fs.exists(join(homedir(), '.gemini', 'settings.json'));
86
- if (agent === 'codex' && (await this.fs.exists(join(homedir(), '.codex', 'auth.json')))) return true;
87
- if (agent === 'pi' && (this.env.GOOGLE_API_KEY !== undefined || this.env.ANTHROPIC_API_KEY !== undefined))
90
+ // gemini/codex expose no auth-status command; treat a non-empty credential
91
+ // file as authenticated. An empty/zero-byte file is a stale-credential
92
+ // false positive, so existence alone is insufficient.
93
+ if (agent === 'gemini') return this.hasNonEmptyFile(join(homedir(), '.gemini', 'settings.json'));
94
+ if (agent === 'codex' && (await this.hasNonEmptyFile(join(homedir(), '.codex', 'auth.json')))) return true;
95
+ // pi reads provider keys from the environment; require a non-empty value
96
+ // rather than mere presence (an empty export is not a usable credential).
97
+ if (agent === 'pi' && (isNonEmpty(this.env.GOOGLE_API_KEY) || isNonEmpty(this.env.ANTHROPIC_API_KEY)))
88
98
  return true;
89
99
  const command = this.runner.runAuthCommand(agent, { timeout: this.timeout });
90
100
  if (command === null) return false;
@@ -94,4 +104,11 @@ export class DoctorRunner {
94
104
  !/not authenticated|not logged|unauthenticated/i.test(`${result.stdout}\n${result.stderr}`)
95
105
  );
96
106
  }
107
+
108
+ /** True when the path exists and has a non-zero size. */
109
+ private async hasNonEmptyFile(path: string): Promise<boolean> {
110
+ const stat = await this.fs.stat(path);
111
+ if (stat === null) return false;
112
+ return stat.isFile() && stat.size > 0;
113
+ }
97
114
  }
package/src/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './agent-detector';
2
2
  export * from './agents/shims';
3
3
  export * from './ai-runner';
4
4
  export * from './doctor-runner';
5
+ export * from './slash-command';
@@ -0,0 +1,42 @@
1
+ import type { AgentName } from './agents/shims';
2
+
3
+ /**
4
+ * Matches a Claude-style slash command: `/plugin:command [args]`.
5
+ *
6
+ * Captures the plugin namespace, the command, and any trailing argument text.
7
+ */
8
+ const SLASH_COMMAND_RE = /^\/([a-zA-Z0-9._-]+):([a-zA-Z0-9._-]+)(\s.*)?$/;
9
+
10
+ /** Whether the input is a Claude-style `/plugin:command` slash command. */
11
+ export function isClaudeStyleSlashCommand(input: string): boolean {
12
+ return SLASH_COMMAND_RE.test(input);
13
+ }
14
+
15
+ /**
16
+ * Translate a Claude-style `/plugin:command args` into the target agent's
17
+ * slash-command dialect. Non-slash-command input is returned unchanged.
18
+ *
19
+ * | Agent | `/plugin:command args` becomes |
20
+ * |------------|---------------------------------------|
21
+ * | claude | `/plugin:command args` (pass-through) |
22
+ * | codex | `$plugin-command args` |
23
+ * | pi | `/skill:plugin-command args` |
24
+ * | all others | `/plugin-command args` |
25
+ */
26
+ export function translateSlashCommand(agent: AgentName, input: string): string {
27
+ const match = SLASH_COMMAND_RE.exec(input);
28
+ if (!match) return input;
29
+ const [, plugin, command, rest = ''] = match;
30
+ const args = rest.trimStart();
31
+ const suffix = args.length > 0 ? ` ${args}` : '';
32
+ switch (agent) {
33
+ case 'claude':
34
+ return `/${plugin}:${command}${suffix}`;
35
+ case 'codex':
36
+ return `$${plugin}-${command}${suffix}`;
37
+ case 'pi':
38
+ return `/skill:${plugin}-${command}${suffix}`;
39
+ default:
40
+ return `/${plugin}-${command}${suffix}`;
41
+ }
42
+ }