@prohost/cli 0.5.0 → 0.7.0

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.
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `prohost agent workspace init --developer` — make a paired agent's workspace
3
+ * one that opens pull requests the way the ProhostAI fleet does.
4
+ *
5
+ * Opt-in: the default workspace is left exactly as it is. With `--developer`
6
+ * and a path to a ProhostAI checkout (`--repo`), this writes two things into
7
+ * the workspace's `.claude/`:
8
+ *
9
+ * - a skill, `prohost-pr-protocol`, that points the agent at the repo's own
10
+ * `prohost_agents/support/prompts/pr_protocol.md` — by absolute path, not a
11
+ * copy, so the protocol has one source and cannot drift;
12
+ * - the fleet's `gh pr create` gate (`prohost_agents/platform/pr_create_gate.sh`)
13
+ * as a `PreToolUse` hook in `.claude/settings.json`, registered the way
14
+ * `prohost_agents/support/handler.py` registers it (matcher `.*`, the shim
15
+ * filters cheaply itself).
16
+ *
17
+ * Claude Code loads a workspace's `.claude/` when it starts there, which is
18
+ * what `agent run` does. Re-running is idempotent; other settings are kept.
19
+ */
20
+ export declare const PROTOCOL_RELATIVE_PATH: string;
21
+ export declare const GATE_RELATIVE_PATH: string;
22
+ export declare const SKILL_NAME = "prohost-pr-protocol";
23
+ export declare class WorkspaceInitError extends Error {
24
+ }
25
+ export interface WorkspaceInitResult {
26
+ skillPath: string;
27
+ settingsPath: string;
28
+ /** Whether the hook was newly added (false when it was already there). */
29
+ hookAdded: boolean;
30
+ }
31
+ /**
32
+ * Write the developer skill and PR gate hook into ``workdir``.
33
+ *
34
+ * @throws WorkspaceInitError when ``repo`` is not a ProhostAI checkout, or the
35
+ * existing settings file is not JSON (it is never overwritten blind).
36
+ */
37
+ export declare function initDeveloperWorkspace(options: {
38
+ workdir: string;
39
+ repo: string;
40
+ }): WorkspaceInitResult;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * `prohost agent workspace init --developer` — make a paired agent's workspace
3
+ * one that opens pull requests the way the ProhostAI fleet does.
4
+ *
5
+ * Opt-in: the default workspace is left exactly as it is. With `--developer`
6
+ * and a path to a ProhostAI checkout (`--repo`), this writes two things into
7
+ * the workspace's `.claude/`:
8
+ *
9
+ * - a skill, `prohost-pr-protocol`, that points the agent at the repo's own
10
+ * `prohost_agents/support/prompts/pr_protocol.md` — by absolute path, not a
11
+ * copy, so the protocol has one source and cannot drift;
12
+ * - the fleet's `gh pr create` gate (`prohost_agents/platform/pr_create_gate.sh`)
13
+ * as a `PreToolUse` hook in `.claude/settings.json`, registered the way
14
+ * `prohost_agents/support/handler.py` registers it (matcher `.*`, the shim
15
+ * filters cheaply itself).
16
+ *
17
+ * Claude Code loads a workspace's `.claude/` when it starts there, which is
18
+ * what `agent run` does. Re-running is idempotent; other settings are kept.
19
+ */
20
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
21
+ import path from 'node:path';
22
+ import { shellQuote } from './accounts.js';
23
+ export const PROTOCOL_RELATIVE_PATH = path.join('prohost_agents', 'support', 'prompts', 'pr_protocol.md');
24
+ export const GATE_RELATIVE_PATH = path.join('prohost_agents', 'platform', 'pr_create_gate.sh');
25
+ export const SKILL_NAME = 'prohost-pr-protocol';
26
+ export class WorkspaceInitError extends Error {
27
+ }
28
+ function skillBody(protocolPath, gatePath) {
29
+ return [
30
+ '---',
31
+ `name: ${SKILL_NAME}`,
32
+ 'description: >-',
33
+ ' ProhostAI pull-request protocol. Use before creating a branch, committing, pushing,',
34
+ ' opening or updating a pull request, answering review comments, or reporting CI status',
35
+ ' in the ProhostAI repositories.',
36
+ '---',
37
+ '',
38
+ '# ProhostAI pull-request protocol',
39
+ '',
40
+ `Read \`${protocolPath}\` in full before you open or update a pull request, and follow it exactly.`,
41
+ 'That file is the single source of truth; this skill only points at it, so never work from a',
42
+ 'remembered or summarised copy — re-read it each time.',
43
+ '',
44
+ `A \`gh pr create\` gate (\`${gatePath}\`) runs before every shell command. If it refuses, fix`,
45
+ 'what it names (usually the PR body headings, or a missing test) and try again. Never work',
46
+ 'around it.',
47
+ '',
48
+ ].join('\n');
49
+ }
50
+ /**
51
+ * Write the developer skill and PR gate hook into ``workdir``.
52
+ *
53
+ * @throws WorkspaceInitError when ``repo`` is not a ProhostAI checkout, or the
54
+ * existing settings file is not JSON (it is never overwritten blind).
55
+ */
56
+ export function initDeveloperWorkspace(options) {
57
+ const repo = path.resolve(options.repo);
58
+ const protocolPath = path.join(repo, PROTOCOL_RELATIVE_PATH);
59
+ const gatePath = path.join(repo, GATE_RELATIVE_PATH);
60
+ for (const required of [protocolPath, gatePath]) {
61
+ if (!existsSync(required)) {
62
+ throw new WorkspaceInitError(`${required} does not exist — pass --repo <path to a ProhostAI backend-service checkout>.`);
63
+ }
64
+ }
65
+ const claudeDir = path.join(path.resolve(options.workdir), '.claude');
66
+ const skillDir = path.join(claudeDir, 'skills', SKILL_NAME);
67
+ mkdirSync(skillDir, { recursive: true, mode: 0o700 });
68
+ const skillPath = path.join(skillDir, 'SKILL.md');
69
+ writeFileSync(skillPath, skillBody(protocolPath, gatePath));
70
+ const settingsPath = path.join(claudeDir, 'settings.json');
71
+ let settings = {};
72
+ if (existsSync(settingsPath)) {
73
+ try {
74
+ const parsed = JSON.parse(readFileSync(settingsPath, 'utf8'));
75
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
76
+ throw new Error('not an object');
77
+ settings = parsed;
78
+ }
79
+ catch {
80
+ throw new WorkspaceInitError(`${settingsPath} is not a JSON object; fix or remove it and run this again.`);
81
+ }
82
+ }
83
+ const hooks = settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks)
84
+ ? settings.hooks
85
+ : {};
86
+ const preToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
87
+ // Claude runs a hook command through a shell, so a checkout path with a
88
+ // space or metacharacter must be quoted — unquoted, the gate would fail to
89
+ // run (or run as something else) and `gh pr create` would go ungated.
90
+ const gateCommand = shellQuote(gatePath);
91
+ const present = preToolUse.some((group) => (group.hooks ?? []).some((hook) => hook.command === gateCommand || hook.command === gatePath));
92
+ if (!present)
93
+ preToolUse.push({ matcher: '.*', hooks: [{ type: 'command', command: gateCommand }] });
94
+ settings.hooks = { ...hooks, PreToolUse: preToolUse };
95
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
96
+ return { skillPath, settingsPath, hookAdded: !present };
97
+ }
package/dist/index.d.ts CHANGED
@@ -16,6 +16,8 @@ interface ParsedArgs {
16
16
  command: string | undefined;
17
17
  /** Second positional, e.g. the ``pair`` in ``prohost agent pair``. */
18
18
  subcommand: string | undefined;
19
+ /** Any further positionals, e.g. ``add work`` in ``prohost agent account add work``. */
20
+ args: string[];
19
21
  flags: Record<string, string | boolean>;
20
22
  }
21
23
  export declare function parseArgs(argv: readonly string[]): ParsedArgs;
package/dist/index.js CHANGED
@@ -24,10 +24,14 @@ function printHelp() {
24
24
  '',
25
25
  'Usage:',
26
26
  ' prohost listen --forward-to <local-url> [--api-key <key>] [--url <ws-url>] [--machine <name>] [--subscription-id <id>]',
27
- ' prohost agent pair --code <pairing-code> [--webhook-url <https-url>] [--base-url <url>]',
27
+ ' prohost agent pair --code <pairing-code> [--account <label>] [--webhook-url <https-url>] [--base-url <url>]',
28
28
  " prohost agent run --exec '<command>' [--idle-timeout <seconds>] [--timeout <seconds>] [--dry-run] [--workdir <dir>]",
29
- " prohost agent install-daemon --exec '<command>' [--workdir <dir>] Keep the agent running",
29
+ " prohost agent install-daemon --exec '<command>' [--workdir <dir>] [--account <label>] Keep the agent running",
30
30
  ' prohost agent uninstall-daemon',
31
+ ' prohost agent account add <label> --runtime claude|codex [--billing subscription|api_key]',
32
+ ' prohost agent account list | remove <label>',
33
+ ' prohost agent list Every paired agent on this machine, and its accounts',
34
+ ' prohost agent workspace init --developer --repo <checkout> [--workdir <dir>]',
31
35
  '',
32
36
  'listen flags:',
33
37
  ' --forward-to Local URL to POST each webhook payload to (required)',
@@ -72,6 +76,20 @@ function printHelp() {
72
76
  ' will go. The name "prohost" is reserved; a malformed file is a warning and a run',
73
77
  ' without extras. Which servers loaded is printed to agent.log at start-up.',
74
78
  '',
79
+ 'Accounts:',
80
+ ' An account is one Claude Code or Codex login on this machine, in its own config directory',
81
+ ' (~/.prohost-accounts, or $PROHOST_ACCOUNTS_ROOT). `account add` prints the sign-in command',
82
+ ' (CLAUDE_CONFIG_DIR=… claude auth login / CODEX_HOME=… codex login) and waits for it; the CLI',
83
+ ' never reads the login itself. An agent uses one account — `default` is ~/.claude or ~/.codex —',
84
+ ' set with --account or picked in ProhostAI, and applied from its next run. Accounts never switch',
85
+ ' on their own: an agent whose account is used up rests until the limit resets.',
86
+ ' --no-wait `account add`: print the sign-in command and return.',
87
+ ' --wait-timeout `account add`: seconds to wait for the sign-in (default 600).',
88
+ '',
89
+ 'agent workspace init --developer:',
90
+ ' Adds the ProhostAI PR-protocol skill and the `gh pr create` gate hook from --repo (a',
91
+ ' backend-service checkout) to the agent workspace\'s .claude/. Opt-in; nothing else changes.',
92
+ '',
75
93
  'agent install-daemon flags: (macOS: launchd; Linux: prints a systemd-user unit)',
76
94
  ' --exec The same command you would pass to `agent run` (required).',
77
95
  ' --workdir, --idle-timeout, --timeout, --machine, --url, --allow-unverified, --safe-tools',
@@ -86,6 +104,7 @@ function printHelp() {
86
104
  ' PROHOST_WS_URL Used when --url is not provided.',
87
105
  ' PROHOST_PAIRING_CODE Used by `agent pair` when --code is not provided.',
88
106
  ' PROHOST_HOME Credential, ledger, workspace and log directory. Defaults to ~/.prohost.',
107
+ ' PROHOST_ACCOUNTS_ROOT Machine-wide account registry shared by every agent. Defaults to ~/.prohost-accounts.',
89
108
  ];
90
109
  process.stdout.write(`${lines.join('\n')}\n`);
91
110
  }
@@ -93,6 +112,7 @@ export function parseArgs(argv) {
93
112
  const flags = {};
94
113
  let command;
95
114
  let subcommand;
115
+ const args = [];
96
116
  let i = 0;
97
117
  while (i < argv.length) {
98
118
  const token = argv[i];
@@ -101,6 +121,8 @@ export function parseArgs(argv) {
101
121
  command = token;
102
122
  else if (!subcommand)
103
123
  subcommand = token;
124
+ else
125
+ args.push(token);
104
126
  i += 1;
105
127
  continue;
106
128
  }
@@ -125,10 +147,10 @@ export function parseArgs(argv) {
125
147
  i += 2;
126
148
  }
127
149
  }
128
- return { command, subcommand, flags };
150
+ return { command, subcommand, args, flags };
129
151
  }
130
152
  async function main(argv) {
131
- const { command, subcommand, flags } = parseArgs(argv);
153
+ const { command, subcommand, args, flags } = parseArgs(argv);
132
154
  // Checked before the help/no-command branch: `prohost --version` carries no
133
155
  // command, which would otherwise print help and exit 1.
134
156
  if (flags.version) {
@@ -148,7 +170,7 @@ async function main(argv) {
148
170
  return 1;
149
171
  }
150
172
  if (command === 'agent') {
151
- return runAgent(subcommand, flags, printHelp);
173
+ return runAgent(subcommand, flags, printHelp, args);
152
174
  }
153
175
  if (command !== 'listen') {
154
176
  process.stderr.write(`Unknown command: ${command}\n\n`);
package/dist/version.d.ts CHANGED
@@ -7,6 +7,6 @@
7
7
  * `package.json`, so a release bump that forgets this file fails the suite
8
8
  * instead of shipping a `User-Agent` that lies about which build is calling.
9
9
  */
10
- export declare const CLI_VERSION = "0.5.0";
10
+ export declare const CLI_VERSION = "0.7.0";
11
11
  /** Sent on every HTTP request the CLI makes back into ProhostAI. */
12
- export declare const USER_AGENT = "prohost-cli/0.5.0";
12
+ export declare const USER_AGENT = "prohost-cli/0.7.0";
package/dist/version.js CHANGED
@@ -7,6 +7,6 @@
7
7
  * `package.json`, so a release bump that forgets this file fails the suite
8
8
  * instead of shipping a `User-Agent` that lies about which build is calling.
9
9
  */
10
- export const CLI_VERSION = '0.5.0';
10
+ export const CLI_VERSION = '0.7.0';
11
11
  /** Sent on every HTTP request the CLI makes back into ProhostAI. */
12
12
  export const USER_AGENT = `prohost-cli/${CLI_VERSION}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prohost/cli",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Run your own AI agent as a ProhostAI teammate, and stream your account's webhooks to your laptop.",
5
5
  "type": "module",
6
6
  "license": "MIT",