@anytowork/cli 0.12.27 → 0.12.28

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anytowork/cli",
3
- "version": "0.12.27",
3
+ "version": "0.12.28",
4
4
  "description": "AnytoWork 平台命令行工具——让任何能执行 shell 命令的 Agent 用一条命令接入 AnytoWork:登录、发单接单、管理能力。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,7 +41,7 @@ export async function runListenInstall(p) {
41
41
  } else {
42
42
  try {
43
43
  result = await (deps.installAutostart || installAutostart)({
44
- profile, configDir: env.ANYTOWORK_CONFIG_DIR || null, logFile: `${dir}/listener.log`,
44
+ profile, configDir: profile ? null : (env.ANYTOWORK_CONFIG_DIR || env.LINGER_CONFIG_DIR || null), logFile: `${dir}/listener.log`,
45
45
  platform: deps.platform, home: deps.home, runCmd: deps.runCmd, node: deps.node, entry: deps.entry, pathEnv: deps.pathEnv,
46
46
  startNow: deps.startNow,
47
47
  });
@@ -64,7 +64,7 @@ export async function runListenUninstall(p) {
64
64
  const { flags, credOpts, baseUrl, fetchImpl, log, deps = {} } = p;
65
65
  const env = deps.env || process.env;
66
66
  try {
67
- await (deps.uninstallAutostart || uninstallAutostart)({ profile: flags.profile || null, platform: deps.platform, home: deps.home, runCmd: deps.runCmd });
67
+ await (deps.uninstallAutostart || uninstallAutostart)({ profile: flags.profile || null, configDir: flags.profile ? null : (env.ANYTOWORK_CONFIG_DIR || env.LINGER_CONFIG_DIR || null), platform: deps.platform, home: deps.home, runCmd: deps.runCmd });
68
68
  } catch { /* nothing to undo */ }
69
69
  for (const f of [configPath(credOpts), cursorFile(credOpts), statePath(credOpts)]) { try { fs.unlinkSync(f); } catch { /* none */ } }
70
70
  try {
@@ -8,9 +8,16 @@
8
8
  import fs from 'node:fs';
9
9
  import os from 'node:os';
10
10
  import path from 'node:path';
11
+ import { createHash } from 'node:crypto';
11
12
  import { execFile } from 'node:child_process';
12
13
  import { fileURLToPath } from 'node:url';
13
14
 
15
+ /** Name part that tells identities apart on one computer: the profile, else a short hash of the config dir, else "default". */
16
+ export function slotOf(profile, configDir) {
17
+ if (profile) return profile;
18
+ if (configDir) return `dir-${createHash("sha1").update(String(configDir)).digest("hex").slice(0, 8)}`;
19
+ return null;
20
+ }
14
21
  export function labelOf(profile) { return `work.anytowork.listen.${profile || 'default'}`; }
15
22
  export function unitNameOf(profile) { return `anytowork-listen-${profile || 'default'}.service`; }
16
23
  export function taskNameOf(profile) { return `AnytoWork Listen ${profile || 'default'}`; }
@@ -79,13 +86,15 @@ function shSingle(s) { return `'${String(s).replace(/'/g, `'\\''`)}'`; }
79
86
  export function cronLine({ node, entry, profile, configDir }) {
80
87
  const cmd = [shSingle(node), shSingle(entry), ...runArgs(profile).map(shSingle)].join(' ');
81
88
  const envPart = configDir ? `ANYTOWORK_CONFIG_DIR=${shSingle(configDir)} ` : '';
82
- return `@reboot ${envPart}${cmd} >/dev/null 2>&1 ${cronMarkOf(profile)}`;
89
+ return `@reboot ${envPart}${cmd} >/dev/null 2>&1 ${cronMarkOf(slotOf(profile, configDir))}`;
83
90
  }
84
91
 
85
92
  /** Windows: the arguments of `schtasks /Create` (run at the user's logon, no administrator needed). */
86
- export function schtasksCreateArgs({ node, entry, profile }) {
87
- const tr = [`"${node}"`, `"${entry}"`, ...runArgs(profile)].join(' ');
88
- return ['/Create', '/TN', taskNameOf(profile), '/SC', 'ONLOGON', '/TR', tr, '/F'];
93
+ export function schtasksCreateArgs({ node, entry, profile, configDir }) {
94
+ const run = [`"${node}"`, `"${entry}"`, ...runArgs(profile)].join(" ");
95
+ // a task cannot carry environment variables, so a custom config dir is set by a small cmd wrapper
96
+ const tr = configDir ? `cmd /c "set ANYTOWORK_CONFIG_DIR=${configDir}&& ${run}"` : run;
97
+ return ["/Create", "/TN", taskNameOf(slotOf(profile, configDir)), "/SC", "ONLOGON", "/TR", tr, "/F"];
89
98
  }
90
99
 
91
100
  /** Default way to run a system command; resolves {code, stdout, stderr}, never rejects. */
@@ -117,11 +126,12 @@ export async function installAutostart(o) {
117
126
  const node = o.node || process.execPath;
118
127
  const entry = o.entry || defaultEntryPath();
119
128
  const pathEnv = o.pathEnv != null ? o.pathEnv : process.env.PATH;
129
+ const slot = slotOf(o.profile, o.configDir);
120
130
  const common = { node, entry, profile: o.profile || null, pathEnv, configDir: o.configDir || null };
121
131
  const notes = [];
122
132
 
123
133
  if (platform === 'darwin') {
124
- const label = labelOf(o.profile);
134
+ const label = labelOf(slot);
125
135
  const dir = path.join(home, 'Library', 'LaunchAgents');
126
136
  const file = path.join(dir, `${label}.plist`);
127
137
  fs.mkdirSync(dir, { recursive: true });
@@ -136,7 +146,7 @@ export async function installAutostart(o) {
136
146
  if (platform === 'win32') {
137
147
  const create = await runCmd('schtasks', schtasksCreateArgs(common));
138
148
  if (create.code !== 0) throw new Error(`schtasks: ${(create.stderr || create.stdout || '').trim().slice(0, 200)}`);
139
- await runCmd('schtasks', ['/Run', '/TN', taskNameOf(o.profile)]);
149
+ await runCmd('schtasks', ['/Run', '/TN', taskNameOf(slot)]);
140
150
  return { kind: 'schtasks', notes };
141
151
  }
142
152
 
@@ -144,17 +154,17 @@ export async function installAutostart(o) {
144
154
  const sd = await runCmd('systemctl', ['--user', '--version']);
145
155
  if (sd.code === 0 && !sd.missing) {
146
156
  const dir = path.join(home, '.config', 'systemd', 'user');
147
- const file = path.join(dir, unitNameOf(o.profile));
157
+ const file = path.join(dir, unitNameOf(slot));
148
158
  fs.mkdirSync(dir, { recursive: true });
149
159
  fs.writeFileSync(file, systemdUnit(common), { mode: 0o644 });
150
160
  await runCmd('systemctl', ['--user', 'daemon-reload']);
151
- const r = await runCmd('systemctl', ['--user', 'enable', '--now', unitNameOf(o.profile)]);
161
+ const r = await runCmd('systemctl', ['--user', 'enable', '--now', unitNameOf(slot)]);
152
162
  if (r.code === 0) { notes.push('linger'); return { kind: 'systemd', file, notes }; }
153
163
  // The user manager is not reachable (container, ssh without a session): fall through to cron.
154
164
  }
155
165
  const ct = await runCmd('crontab', ['-l']);
156
166
  if (!ct.missing) {
157
- const mark = cronMarkOf(o.profile);
167
+ const mark = cronMarkOf(slot);
158
168
  const kept = (ct.code === 0 ? ct.stdout : '').split('\n').filter((l) => l && !l.includes(mark));
159
169
  kept.push(cronLine(common));
160
170
  const w = await runCmd('crontab', ['-'], { input: `${kept.join('\n')}\n` });
@@ -168,30 +178,31 @@ export async function installAutostart(o) {
168
178
 
169
179
  /** Undo installAutostart; unknown or missing pieces are skipped. */
170
180
  export async function uninstallAutostart(o) {
181
+ const slot = slotOf(o.profile, o.configDir);
171
182
  const platform = o.platform || process.platform;
172
183
  const runCmd = o.runCmd || defaultRunCmd;
173
184
  const home = o.home || os.homedir();
174
185
  if (platform === 'darwin') {
175
- const label = labelOf(o.profile);
186
+ const label = labelOf(slot);
176
187
  const uid = String(o.uid != null ? o.uid : (process.getuid ? process.getuid() : 501));
177
188
  await runCmd('launchctl', ['bootout', `gui/${uid}/${label}`]);
178
189
  try { fs.unlinkSync(path.join(home, 'Library', 'LaunchAgents', `${label}.plist`)); } catch { /* none */ }
179
190
  return;
180
191
  }
181
192
  if (platform === 'win32') {
182
- await runCmd('schtasks', ['/End', '/TN', taskNameOf(o.profile)]);
183
- await runCmd('schtasks', ['/Delete', '/TN', taskNameOf(o.profile), '/F']);
193
+ await runCmd('schtasks', ['/End', '/TN', taskNameOf(slot)]);
194
+ await runCmd('schtasks', ['/Delete', '/TN', taskNameOf(slot), '/F']);
184
195
  return;
185
196
  }
186
- const unit = path.join(home, '.config', 'systemd', 'user', unitNameOf(o.profile));
197
+ const unit = path.join(home, '.config', 'systemd', 'user', unitNameOf(slot));
187
198
  if (fs.existsSync(unit)) {
188
- await runCmd('systemctl', ['--user', 'disable', '--now', unitNameOf(o.profile)]);
199
+ await runCmd('systemctl', ['--user', 'disable', '--now', unitNameOf(slot)]);
189
200
  try { fs.unlinkSync(unit); } catch { /* none */ }
190
201
  await runCmd('systemctl', ['--user', 'daemon-reload']);
191
202
  }
192
203
  const ct = await runCmd('crontab', ['-l']);
193
204
  if (!ct.missing && ct.code === 0) {
194
- const mark = cronMarkOf(o.profile);
205
+ const mark = cronMarkOf(slot);
195
206
  if (ct.stdout.includes(mark)) {
196
207
  const kept = ct.stdout.split('\n').filter((l) => l && !l.includes(mark));
197
208
  await runCmd('crontab', ['-'], { input: kept.length ? `${kept.join('\n')}\n` : '' });