@camerontaylor/paseo-cli 0.11.0-beta.3.fork.1 → 0.11.1-fork.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.
@@ -4,6 +4,7 @@ export function addAttachOptions(cmd) {
4
4
  .argument("<id>", "Agent ID (or prefix)");
5
5
  }
6
6
  import { connectToDaemon } from "../../utils/client.js";
7
+ import { waitForStop } from "../../utils/wait-for-stop.js";
7
8
  import { fetchProjectedTimelineItems, LIVE_HISTORY_FETCH_TIMEOUT_MS, } from "../../utils/timeline.js";
8
9
  /**
9
10
  * Format and print a timeline item to the terminal
@@ -123,29 +124,10 @@ export async function runAttachCommand(id, options, _command) {
123
124
  });
124
125
  await unsubscribe.ready;
125
126
  console.log(`Attached to agent ${resolvedId.substring(0, 7)}.`);
126
- // Handle Ctrl+C to detach gracefully
127
- let detached = false;
128
- const detach = () => {
129
- if (detached)
130
- return;
131
- detached = true;
132
- console.log("\n\nDetaching from agent...");
133
- unsubscribe();
134
- client
135
- .close()
136
- .then(() => {
137
- process.exit(0);
138
- })
139
- .catch(() => {
140
- process.exit(1);
141
- });
142
- };
143
- process.on("SIGINT", detach);
144
- process.on("SIGTERM", detach);
145
- // Keep the process alive
146
- await new Promise(() => {
147
- // Wait indefinitely until interrupted
148
- });
127
+ await waitForStop();
128
+ console.log("\n\nDetaching from agent...");
129
+ unsubscribe();
130
+ await client.close();
149
131
  }
150
132
  catch (err) {
151
133
  await client.close().catch(() => { });
@@ -11,6 +11,19 @@ export const deleteSchema = {
11
11
  idField: (item) => item.agentIds.join("\n"),
12
12
  columns: [{ header: "DELETED", field: "deletedCount" }],
13
13
  };
14
+ // History includes archived agents and agents whose provider is unavailable.
15
+ async function fetchHistoryAgents(client) {
16
+ const agents = [];
17
+ let cursor;
18
+ do {
19
+ const payload = await client.fetchAgentHistory({
20
+ page: { limit: 200, ...(cursor ? { cursor } : {}) },
21
+ });
22
+ agents.push(...payload.entries.map((entry) => entry.agent));
23
+ cursor = payload.pageInfo.nextCursor ?? undefined;
24
+ } while (cursor);
25
+ return agents;
26
+ }
14
27
  export async function runDeleteCommand(id, options, _command) {
15
28
  if (!id && !options.all && !options.cwd) {
16
29
  const error = {
@@ -22,20 +35,15 @@ export async function runDeleteCommand(id, options, _command) {
22
35
  }
23
36
  const client = await connectToDaemon({ target: options.daemonTarget });
24
37
  try {
25
- const fetchPayload = await client.fetchAgents({ filter: { includeArchived: true } });
26
- let agents = fetchPayload.entries.map((entry) => entry.agent);
27
38
  const deletedIds = [];
28
- if (options.all) {
29
- agents = agents.filter((a) => !a.archivedAt);
30
- }
31
- else if (options.cwd) {
32
- agents = agents.filter((a) => {
33
- if (a.archivedAt)
34
- return false;
35
- return isSameOrDescendantPath(options.cwd, a.cwd);
36
- });
39
+ let agents;
40
+ if (options.all || options.cwd) {
41
+ agents = await fetchHistoryAgents(client);
42
+ if (!options.all) {
43
+ agents = agents.filter((a) => isSameOrDescendantPath(options.cwd, a.cwd));
44
+ }
37
45
  }
38
- else if (id) {
46
+ else {
39
47
  const fetchResult = await client.fetchAgent({ agentId: id });
40
48
  if (!fetchResult) {
41
49
  const error = {
@@ -1,4 +1,5 @@
1
1
  import { connectToDaemon } from "../../utils/client.js";
2
+ import { waitForStop } from "../../utils/wait-for-stop.js";
2
3
  import { fetchProjectedTimelineItems, LIVE_HISTORY_FETCH_TIMEOUT_MS, } from "../../utils/timeline.js";
3
4
  import { curateAgentActivity } from "@camerontaylor/paseo-server/agent-activity";
4
5
  export function addLogsOptions(cmd) {
@@ -166,15 +167,8 @@ async function runFollowMode(client, agentId, options) {
166
167
  });
167
168
  await unsubscribe.ready;
168
169
  console.log(`\n--- Following logs (${tailLabel}; Ctrl+C to stop) ---\n`);
169
- // Wait for interrupt
170
- await new Promise((resolve) => {
171
- const cleanup = () => {
172
- unsubscribe();
173
- resolve();
174
- };
175
- process.on("SIGINT", cleanup);
176
- process.on("SIGTERM", cleanup);
177
- });
170
+ await waitForStop();
171
+ unsubscribe();
178
172
  await client.close();
179
173
  }
180
174
  //# sourceMappingURL=logs.js.map
@@ -59,7 +59,8 @@ export async function runRestartCommand(options, _command) {
59
59
  try {
60
60
  if (replacement.getLastServerInfoMessage()?.serverId !== serverId)
61
61
  throw new Error("Connected peer identity changed");
62
- const status = await replacement.getDaemonStatus({ timeout: Math.min(1000, remaining()) });
62
+ // Status probes every provider, so it can take seconds on a healthy worker.
63
+ const status = await replacement.getDaemonStatus({ timeout: remaining() });
63
64
  if (status.pid !== workerPid) {
64
65
  await checkSupervisor();
65
66
  return {
@@ -1,7 +1,9 @@
1
+ import path from "node:path";
2
+ import { expandUserPath } from "../../classify.js";
1
3
  import { createInterface } from "node:readline/promises";
2
4
  import { reviewPluginUpdates } from "./update.js";
3
5
  import { Command } from "commander";
4
- import { formatPluginSourceReference, formatPluginIdentity, } from "@camerontaylor/paseo-protocol/plugin-source-reference";
6
+ import { formatPluginSourceReference, parsePluginSourceReference, formatPluginIdentity, } from "@camerontaylor/paseo-protocol/plugin-source-reference";
5
7
  import { withOutput } from "../../output/index.js";
6
8
  import { addJsonAndDaemonHostOptions, addJsonOption } from "../../utils/command-options.js";
7
9
  import { scaffoldPluginDirectory } from "./scaffold.js";
@@ -73,7 +75,17 @@ export async function runPluginLogsCommand(pluginId, options, _command) {
73
75
  }
74
76
  export async function runPluginInstallCommand(source, options, _command) {
75
77
  process.stderr.write("Trusting plugin code: server code and preparation commands run unsandboxed on the daemon host; client code runs inside Paseo. Dependencies and future updates are part of the codebase you trust.\n");
76
- const sourceReference = formatPluginSourceReference(source, options.path);
78
+ const reference = parsePluginSourceReference(source);
79
+ let resolvedSource = source;
80
+ if (reference.kind === "directory") {
81
+ const directory = expandUserPath(reference.source);
82
+ // An absolute path can target a daemon on a different operating system.
83
+ const absoluteDirectory = path.posix.isAbsolute(directory) || path.win32.isAbsolute(directory)
84
+ ? directory
85
+ : path.resolve(directory);
86
+ resolvedSource = formatPluginSourceReference(absoluteDirectory, reference.pluginPath);
87
+ }
88
+ const sourceReference = formatPluginSourceReference(resolvedSource, options.path);
77
89
  const data = await withPluginSourceClient(options.daemonTarget, (client) => client.installPluginSource({
78
90
  source: sourceReference,
79
91
  ...(options.id ? { id: options.id } : {}),
@@ -131,8 +143,8 @@ export function createPluginCommand() {
131
143
  addJsonAndDaemonHostOptions(plugin
132
144
  .command("install")
133
145
  .alias("add")
134
- .description("Trust and install a plugin from a directory, Git repository, or npm package")
135
- .argument("<source>", "Host directory, Git or npm source, optionally followed by :plugin/path")
146
+ .description("Trust and install a plugin from a registry, directory, Git repository, or npm package")
147
+ .argument("<source>", "Registry owner/slug or host/owner/slug, host directory, explicit git:owner/repo or Git URL, or npm: source")
136
148
  .option("--id <id>", "Runtime plugin ID (defaults to paseo-plugin.json id)")
137
149
  .option("--ref <ref>", "Git branch, tag, or commit")
138
150
  .option("--path <path>", "Legacy form of the :plugin/path source suffix")).action(withOutput(runPluginInstallCommand));
@@ -2,6 +2,33 @@ import { mkdir, readdir, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { PluginIdSchema } from "@camerontaylor/paseo-protocol/messages";
4
4
  import { resolveCliVersion } from "../../version.js";
5
+ const OVERVIEW = `<!--
6
+ OVERVIEW.md is required to list your plugin in the registry. Keep it beside
7
+ paseo-plugin.json in your repository at the pinned commit and in your npm package.
8
+ Replace this comment with useful facts before publishing.
9
+
10
+ This overview is for people choosing a plugin in Paseo, on the plugin page under
11
+ its install command. README assumes a GitHub audience and includes technical
12
+ details, installation instructions, and badges. Long, AI-generated READMEs leave
13
+ people reading past that material to understand what the plugin does.
14
+
15
+ Write these parts in order, including only what helps someone decide to install:
16
+
17
+ 1. Describe what your plugin is and does in plain terms first.
18
+ 2. Explain how it works only when it is not obvious.
19
+ 3. Explain setup when needed: settings, accounts, tokens, providers, tools, or
20
+ other plugins. Include applicable daemon version and operating system
21
+ requirements. Setup guidance is allowed; installation instructions are not.
22
+ 4. Explain capabilities and settings worth understanding, what each option does,
23
+ what the plugin reads or sends and where, permissions, and known limits.
24
+
25
+ Length follows complexity; a theme needs one paragraph. Choose headings only
26
+ when they help. Use sentence case and plain factual language, with no em dashes.
27
+ Omit installation commands, badges, changelog, contributing or license sections,
28
+ marketing, and unsupported claims. Avoid implementation filler such as empty
29
+ cleanup functions, theme-token field lists, or lists of absent features.
30
+ -->
31
+ `;
5
32
  const TSCONFIG = {
6
33
  compilerOptions: {
7
34
  target: "ES2020",
@@ -138,6 +165,7 @@ export async function scaffoldPluginDirectory(targetDirectory, requestedId) {
138
165
  version: "0.0.0",
139
166
  files: [
140
167
  "paseo-plugin.json",
168
+ "OVERVIEW.md",
141
169
  "index.client.ts",
142
170
  "index.client.tsx",
143
171
  "index.server.ts",
@@ -160,8 +188,14 @@ export async function scaffoldPluginDirectory(targetDirectory, requestedId) {
160
188
  const files = new Map([
161
189
  [
162
190
  "paseo-plugin.json",
163
- `${JSON.stringify({ id, requirements: { paseo: `>=${version}` } }, null, 2)}\n`,
191
+ `${JSON.stringify({
192
+ id,
193
+ $comment: 'Add "name": "My plugin" to set a display name. Add "icon": "assets/icon.png" for a PNG inside the package. Add image or video paths (relative to this manifest) or HTTPS URLs to media. Include local assets in package.json files.',
194
+ media: [],
195
+ requirements: { paseo: `>=${version}` },
196
+ }, null, 2)}\n`,
164
197
  ],
198
+ ["OVERVIEW.md", OVERVIEW],
165
199
  ["package.json", `${JSON.stringify(packageJson, null, 2)}\n`],
166
200
  ["tsconfig.json", `${JSON.stringify(TSCONFIG, null, 2)}\n`],
167
201
  ["index.client.tsx", CLIENT_ENTRY],
@@ -12,10 +12,8 @@ export interface ScheduleUpdateOptions extends ScheduleCommandOptions {
12
12
  model?: string;
13
13
  mode?: string;
14
14
  cwd?: string;
15
- maxRuns?: string;
16
- noMaxRuns?: boolean;
17
- expiresIn?: string;
18
- noExpiresIn?: boolean;
15
+ maxRuns?: string | false;
16
+ expiresIn?: string | false;
19
17
  }
20
18
  export declare function runUpdateCommand(id: string, options: ScheduleUpdateOptions, _command: Command): Promise<ListResult<ScheduleInspectRow>>;
21
19
  //# sourceMappingURL=update.d.ts.map
@@ -12,10 +12,10 @@ export async function runUpdateCommand(id, options, _command) {
12
12
  model: options.model,
13
13
  mode: options.mode,
14
14
  cwd: options.cwd,
15
- maxRuns: options.maxRuns,
16
- expiresIn: options.expiresIn,
17
- clearMaxRuns: options.noMaxRuns,
18
- clearExpires: options.noExpiresIn,
15
+ maxRuns: options.maxRuns === false ? undefined : options.maxRuns,
16
+ expiresIn: options.expiresIn === false ? undefined : options.expiresIn,
17
+ clearMaxRuns: options.maxRuns === false,
18
+ clearExpires: options.expiresIn === false,
19
19
  });
20
20
  const { client } = await connectScheduleClient(options.daemonTarget);
21
21
  try {
package/dist/run.js CHANGED
@@ -24,7 +24,19 @@ export function createCliParseArgv(input) {
24
24
  }
25
25
  return [...nodeArgv, ...cliArgv];
26
26
  }
27
+ // The program that started the CLI can close its end of stdout before the output
28
+ // is written, as `paseo ls | head -1` or a launcher that discards output does.
29
+ // Drop the output nobody reads and let the command finish, instead of surfacing
30
+ // the failed write as an uncaught error.
31
+ function ignoreClosedStdout(error) {
32
+ if (error.code !== "EPIPE") {
33
+ throw error;
34
+ }
35
+ }
27
36
  export async function runCli(argv, options = {}) {
37
+ if (!process.stdout.listeners("error").includes(ignoreClosedStdout)) {
38
+ process.stdout.on("error", ignoreClosedStdout);
39
+ }
28
40
  const parseArgv = createCliParseArgv({
29
41
  argv,
30
42
  cwd: options.cwd ?? process.cwd(),
@@ -0,0 +1,2 @@
1
+ export declare function waitForStop(): Promise<void>;
2
+ //# sourceMappingURL=wait-for-stop.d.ts.map
@@ -0,0 +1,24 @@
1
+ // Commands that stream until stopped (`paseo agent logs --follow`, `paseo agent
2
+ // attach`) stop on Ctrl+C, a termination signal, or when the program reading
3
+ // their output closes its end, as `| head` does. A closed reader shows up as
4
+ // EPIPE on the next write to stdout, so the command stops at its next line of
5
+ // output.
6
+ export function waitForStop() {
7
+ return new Promise((resolve) => {
8
+ const stop = () => {
9
+ process.off("SIGINT", stop);
10
+ process.off("SIGTERM", stop);
11
+ process.stdout.off("error", stopOnClosedStdout);
12
+ resolve();
13
+ };
14
+ const stopOnClosedStdout = (error) => {
15
+ if (error.code === "EPIPE") {
16
+ stop();
17
+ }
18
+ };
19
+ process.on("SIGINT", stop);
20
+ process.on("SIGTERM", stop);
21
+ process.stdout.on("error", stopOnClosedStdout);
22
+ });
23
+ }
24
+ //# sourceMappingURL=wait-for-stop.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camerontaylor/paseo-cli",
3
- "version": "0.11.0-beta.3.fork.1",
3
+ "version": "0.11.1-fork.1",
4
4
  "description": "Paseo CLI - control your AI coding agents from the command line",
5
5
  "bin": {
6
6
  "paseo": "bin/paseo"
@@ -28,9 +28,9 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@clack/prompts": "^1.0.0",
31
- "@camerontaylor/paseo-client": "0.11.0-beta.3.fork.1",
32
- "@camerontaylor/paseo-protocol": "0.11.0-beta.3.fork.1",
33
- "@camerontaylor/paseo-server": "0.11.0-beta.3.fork.1",
31
+ "@camerontaylor/paseo-client": "0.11.1-fork.1",
32
+ "@camerontaylor/paseo-protocol": "0.11.1-fork.1",
33
+ "@camerontaylor/paseo-server": "0.11.1-fork.1",
34
34
  "chalk": "^5.3.0",
35
35
  "commander": "^12.0.0",
36
36
  "mime-types": "^2.1.35",