@coralai/nps-cli 0.1.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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +459 -0
  3. package/dist/audit-log.d.ts +26 -0
  4. package/dist/audit-log.d.ts.map +1 -0
  5. package/dist/audit-log.js +120 -0
  6. package/dist/audit-log.js.map +1 -0
  7. package/dist/bindings/bindings-loader.d.ts +19 -0
  8. package/dist/bindings/bindings-loader.d.ts.map +1 -0
  9. package/dist/bindings/bindings-loader.js +84 -0
  10. package/dist/bindings/bindings-loader.js.map +1 -0
  11. package/dist/cli/agent-cmd.d.ts +2 -0
  12. package/dist/cli/agent-cmd.d.ts.map +1 -0
  13. package/dist/cli/agent-cmd.js +125 -0
  14. package/dist/cli/agent-cmd.js.map +1 -0
  15. package/dist/cli/bind-cmd.d.ts +2 -0
  16. package/dist/cli/bind-cmd.d.ts.map +1 -0
  17. package/dist/cli/bind-cmd.js +127 -0
  18. package/dist/cli/bind-cmd.js.map +1 -0
  19. package/dist/cli/daemon-cmd.d.ts +4 -0
  20. package/dist/cli/daemon-cmd.d.ts.map +1 -0
  21. package/dist/cli/daemon-cmd.js +137 -0
  22. package/dist/cli/daemon-cmd.js.map +1 -0
  23. package/dist/cli/index.d.ts +2 -0
  24. package/dist/cli/index.d.ts.map +1 -0
  25. package/dist/cli/index.js +62 -0
  26. package/dist/cli/index.js.map +1 -0
  27. package/dist/config/loader.d.ts +5 -0
  28. package/dist/config/loader.d.ts.map +1 -0
  29. package/dist/config/loader.js +54 -0
  30. package/dist/config/loader.js.map +1 -0
  31. package/dist/config/paths.d.ts +14 -0
  32. package/dist/config/paths.d.ts.map +1 -0
  33. package/dist/config/paths.js +27 -0
  34. package/dist/config/paths.js.map +1 -0
  35. package/dist/config/schema.d.ts +48 -0
  36. package/dist/config/schema.d.ts.map +1 -0
  37. package/dist/config/schema.js +52 -0
  38. package/dist/config/schema.js.map +1 -0
  39. package/dist/daemon/control-client.d.ts +2 -0
  40. package/dist/daemon/control-client.d.ts.map +1 -0
  41. package/dist/daemon/control-client.js +51 -0
  42. package/dist/daemon/control-client.js.map +1 -0
  43. package/dist/daemon/control-socket.d.ts +19 -0
  44. package/dist/daemon/control-socket.d.ts.map +1 -0
  45. package/dist/daemon/control-socket.js +192 -0
  46. package/dist/daemon/control-socket.js.map +1 -0
  47. package/dist/daemon/daemon.d.ts +20 -0
  48. package/dist/daemon/daemon.d.ts.map +1 -0
  49. package/dist/daemon/daemon.js +166 -0
  50. package/dist/daemon/daemon.js.map +1 -0
  51. package/dist/dispatch/dispatch-pipeline.d.ts +39 -0
  52. package/dist/dispatch/dispatch-pipeline.d.ts.map +1 -0
  53. package/dist/dispatch/dispatch-pipeline.js +219 -0
  54. package/dist/dispatch/dispatch-pipeline.js.map +1 -0
  55. package/dist/dispatch/slash-commands.d.ts +35 -0
  56. package/dist/dispatch/slash-commands.d.ts.map +1 -0
  57. package/dist/dispatch/slash-commands.js +67 -0
  58. package/dist/dispatch/slash-commands.js.map +1 -0
  59. package/dist/errors.d.ts +22 -0
  60. package/dist/errors.d.ts.map +1 -0
  61. package/dist/errors.js +17 -0
  62. package/dist/errors.js.map +1 -0
  63. package/dist/gateway/matrix-gateway.d.ts +24 -0
  64. package/dist/gateway/matrix-gateway.d.ts.map +1 -0
  65. package/dist/gateway/matrix-gateway.js +166 -0
  66. package/dist/gateway/matrix-gateway.js.map +1 -0
  67. package/dist/logger.d.ts +13 -0
  68. package/dist/logger.d.ts.map +1 -0
  69. package/dist/logger.js +19 -0
  70. package/dist/logger.js.map +1 -0
  71. package/dist/main.d.ts +3 -0
  72. package/dist/main.d.ts.map +1 -0
  73. package/dist/main.js +7 -0
  74. package/dist/main.js.map +1 -0
  75. package/dist/profile/profile-registry.d.ts +24 -0
  76. package/dist/profile/profile-registry.d.ts.map +1 -0
  77. package/dist/profile/profile-registry.js +114 -0
  78. package/dist/profile/profile-registry.js.map +1 -0
  79. package/dist/runtime-check.d.ts +18 -0
  80. package/dist/runtime-check.d.ts.map +1 -0
  81. package/dist/runtime-check.js +62 -0
  82. package/dist/runtime-check.js.map +1 -0
  83. package/dist/session/compat-hash.d.ts +21 -0
  84. package/dist/session/compat-hash.d.ts.map +1 -0
  85. package/dist/session/compat-hash.js +115 -0
  86. package/dist/session/compat-hash.js.map +1 -0
  87. package/dist/session/session-store.d.ts +29 -0
  88. package/dist/session/session-store.d.ts.map +1 -0
  89. package/dist/session/session-store.js +123 -0
  90. package/dist/session/session-store.js.map +1 -0
  91. package/dist/version.d.ts +2 -0
  92. package/dist/version.d.ts.map +1 -0
  93. package/dist/version.js +2 -0
  94. package/dist/version.js.map +1 -0
  95. package/package.json +59 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * M3 — BindingsLoader.
3
+ *
4
+ * Read-only loader of ~/.nps/bindings.yaml. CLI writes (daemon doesn't),
5
+ * we watch for changes via chokidar and reload.
6
+ *
7
+ * Per CEO plan rev 5 atomic-read protocol: tolerant of EBUSY/transient
8
+ * mid-rename reads — retry up to 3 times with 50ms backoff.
9
+ */
10
+ import { existsSync } from 'node:fs';
11
+ import { EventEmitter } from 'node:events';
12
+ import chokidar from 'chokidar';
13
+ import { getLogger } from '../logger.js';
14
+ import { loadBindings } from '../config/loader.js';
15
+ export class BindingsLoader extends EventEmitter {
16
+ path;
17
+ logger;
18
+ bindings = { matrix: {} };
19
+ watcher;
20
+ constructor(path, logger) {
21
+ super();
22
+ this.path = path;
23
+ this.logger = logger ?? getLogger('bindings-loader');
24
+ }
25
+ async start() {
26
+ this.reload();
27
+ this.watcher = chokidar.watch(this.path, {
28
+ ignoreInitial: true,
29
+ persistent: true,
30
+ });
31
+ this.watcher.on('change', () => {
32
+ this.logger.debug('bindings.yaml changed — reloading');
33
+ this.reload();
34
+ this.emit('reloaded', this.bindings);
35
+ });
36
+ this.watcher.on('add', () => {
37
+ this.reload();
38
+ this.emit('reloaded', this.bindings);
39
+ });
40
+ }
41
+ async stop() {
42
+ if (this.watcher) {
43
+ await this.watcher.close();
44
+ this.watcher = undefined;
45
+ }
46
+ }
47
+ reload() {
48
+ if (!existsSync(this.path)) {
49
+ this.bindings = { matrix: {} };
50
+ return;
51
+ }
52
+ // Retry on EBUSY (CLI is rename(2)-ing; small window)
53
+ for (let attempt = 0; attempt < 3; attempt++) {
54
+ try {
55
+ this.bindings = loadBindings(this.path);
56
+ return;
57
+ }
58
+ catch (e) {
59
+ const msg = e instanceof Error ? e.message : String(e);
60
+ if (msg.includes('EBUSY') || msg.includes('ENOENT')) {
61
+ // Pause + retry
62
+ const wait = (attempt + 1) * 50;
63
+ this.logger.debug({ attempt, wait }, 'transient read error, retrying');
64
+ // synchronous sleep — we're called rarely
65
+ const start = Date.now();
66
+ while (Date.now() - start < wait) { /* spin briefly */ }
67
+ continue;
68
+ }
69
+ this.logger.error({ err: msg }, 'bindings parse failed, keeping previous in-memory state');
70
+ return;
71
+ }
72
+ }
73
+ this.logger.warn('bindings reload exhausted retries; keeping previous state');
74
+ }
75
+ /** Look up profile name for a Matrix channel id; undefined if unbound. */
76
+ lookupMatrix(channelId) {
77
+ return this.bindings.matrix?.[channelId];
78
+ }
79
+ /** Snapshot of current bindings (read-only). */
80
+ snapshot() {
81
+ return JSON.parse(JSON.stringify(this.bindings));
82
+ }
83
+ }
84
+ //# sourceMappingURL=bindings-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bindings-loader.js","sourceRoot":"","sources":["../../src/bindings/bindings-loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runAgentCmd(args: string[]): Promise<number>;
2
+ //# sourceMappingURL=agent-cmd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-cmd.d.ts","sourceRoot":"","sources":["../../src/cli/agent-cmd.ts"],"names":[],"mappings":"AASA,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAWjE"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * `nps agent` subcommand: list / init / edit / invoke.
3
+ */
4
+ import { existsSync, mkdirSync, readdirSync, writeFileSync, statSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { ClaudeCodeAgent } from '@coralai/claude-code-agent';
7
+ import { PATHS } from '../config/paths.js';
8
+ import { loadProfileConfig } from '../config/loader.js';
9
+ export async function runAgentCmd(args) {
10
+ const sub = args[0] ?? '';
11
+ switch (sub) {
12
+ case 'list': return cmdList();
13
+ case 'init': return cmdInit(args[1]);
14
+ case 'edit': return cmdEdit(args[1]);
15
+ case 'invoke': return await cmdInvoke(args[1], args.slice(2).join(' '));
16
+ default:
17
+ console.error('nps agent: subcommand required (list|init|edit|invoke)');
18
+ return 1;
19
+ }
20
+ }
21
+ function cmdList() {
22
+ if (!existsSync(PATHS.agentsDir)) {
23
+ console.log('(no agents directory at ' + PATHS.agentsDir + ')');
24
+ return 0;
25
+ }
26
+ const entries = readdirSync(PATHS.agentsDir).filter(n => {
27
+ const p = join(PATHS.agentsDir, n);
28
+ return statSync(p).isDirectory() && existsSync(join(p, 'nps.yaml'));
29
+ });
30
+ if (entries.length === 0) {
31
+ console.log('(no agent profiles found)');
32
+ return 0;
33
+ }
34
+ for (const name of entries) {
35
+ try {
36
+ const cfg = loadProfileConfig(join(PATHS.agentsDir, name, 'nps.yaml'));
37
+ console.log(` ${name.padEnd(20)} workspace=${cfg.workspace}`);
38
+ }
39
+ catch (e) {
40
+ console.log(` ${name.padEnd(20)} ⚠ invalid (${e instanceof Error ? e.message : e})`);
41
+ }
42
+ }
43
+ return 0;
44
+ }
45
+ function cmdInit(name) {
46
+ if (!name) {
47
+ console.error('nps agent init: name required');
48
+ return 1;
49
+ }
50
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
51
+ console.error('nps agent init: name must be [a-zA-Z0-9_-]+');
52
+ return 1;
53
+ }
54
+ const dir = join(PATHS.agentsDir, name);
55
+ if (existsSync(dir)) {
56
+ console.error(`profile already exists: ${dir}`);
57
+ return 1;
58
+ }
59
+ mkdirSync(dir, { recursive: true });
60
+ mkdirSync(join(dir, '.claude', 'skills'), { recursive: true });
61
+ writeFileSync(join(dir, 'nps.yaml'), `# nps profile: ${name}
62
+ workspace: ${process.cwd()}
63
+ permissionMode: auto
64
+ env: {}
65
+ `);
66
+ writeFileSync(join(dir, 'CLAUDE.md'), `# ${name}
67
+
68
+ You are the "${name}" assistant. Describe your persona and behaviors here.
69
+
70
+ Workspace is provided via the dispatch prompt header — read/write files there.
71
+ `);
72
+ console.log(`✅ created profile: ${dir}`);
73
+ console.log(`Edit ${join(dir, 'nps.yaml')} and ${join(dir, 'CLAUDE.md')} to customize.`);
74
+ return 0;
75
+ }
76
+ function cmdEdit(name) {
77
+ if (!name) {
78
+ console.error('nps agent edit: name required');
79
+ return 1;
80
+ }
81
+ const dir = join(PATHS.agentsDir, name);
82
+ if (!existsSync(dir)) {
83
+ console.error(`profile not found: ${dir}`);
84
+ return 1;
85
+ }
86
+ const editor = process.env['EDITOR'] || 'vi';
87
+ console.log(`Opening profile in ${editor}: ${dir}/nps.yaml`);
88
+ // We can't actually fork editor here without losing CLI flow; print hint instead
89
+ console.log(`Run: ${editor} ${join(dir, 'nps.yaml')}`);
90
+ return 0;
91
+ }
92
+ async function cmdInvoke(name, message) {
93
+ if (!name) {
94
+ console.error('nps agent invoke: name required');
95
+ return 1;
96
+ }
97
+ if (!message) {
98
+ console.error('nps agent invoke: message required');
99
+ return 1;
100
+ }
101
+ const dir = join(PATHS.agentsDir, name);
102
+ if (!existsSync(dir)) {
103
+ console.error(`profile not found: ${dir}`);
104
+ return 1;
105
+ }
106
+ const cfg = loadProfileConfig(join(dir, 'nps.yaml'));
107
+ console.log(`[invoke] profile=${name} workspace=${cfg.workspace}`);
108
+ const agent = new ClaudeCodeAgent({
109
+ cwd: dir,
110
+ env: cfg.env,
111
+ permissionMode: cfg.permissionMode,
112
+ });
113
+ await agent.start();
114
+ const prompt = cfg.workspace
115
+ ? `[Your task workspace is at: ${cfg.workspace}]\n\n${message}`
116
+ : message;
117
+ const handle = agent.prompt(prompt);
118
+ const result = await handle.result;
119
+ console.log('---');
120
+ console.log(result.text);
121
+ console.log('---');
122
+ await agent.stop();
123
+ return 0;
124
+ }
125
+ //# sourceMappingURL=agent-cmd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-cmd.js","sourceRoot":"","sources":["../../src/cli/agent-cmd.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runBindCmd(args: string[]): Promise<number>;
2
+ //# sourceMappingURL=bind-cmd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bind-cmd.d.ts","sourceRoot":"","sources":["../../src/cli/bind-cmd.ts"],"names":[],"mappings":"AAcA,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAUhE"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * `nps bind` subcommand: add / remove / list.
3
+ *
4
+ * Uses a file-lock + atomic rename pattern to avoid races with the daemon
5
+ * reader and concurrent CLI invocations.
6
+ */
7
+ import { existsSync, openSync, closeSync, readFileSync, writeFileSync, renameSync, unlinkSync, chmodSync, mkdirSync } from 'node:fs';
8
+ import { dirname } from 'node:path';
9
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
10
+ import { PATHS } from '../config/paths.js';
11
+ const LOCK_RETRY_MS = 50;
12
+ const LOCK_MAX_RETRIES = 60; // 3 seconds total
13
+ export async function runBindCmd(args) {
14
+ const sub = args[0] ?? '';
15
+ switch (sub) {
16
+ case 'add': return cmdAdd(args[1], args[2]);
17
+ case 'remove': return cmdRemove(args[1]);
18
+ case 'list': return cmdList();
19
+ default:
20
+ console.error('nps bind: subcommand required (add|remove|list)');
21
+ return 1;
22
+ }
23
+ }
24
+ async function acquireLock(lockPath) {
25
+ for (let i = 0; i < LOCK_MAX_RETRIES; i++) {
26
+ try {
27
+ const fd = openSync(lockPath, 'wx', 0o600);
28
+ writeFileSync(lockPath, `${process.pid}\n`);
29
+ closeSync(fd);
30
+ return;
31
+ }
32
+ catch (e) {
33
+ const msg = e instanceof Error ? e.message : String(e);
34
+ if (msg.includes('EEXIST')) {
35
+ await new Promise(r => setTimeout(r, LOCK_RETRY_MS));
36
+ continue;
37
+ }
38
+ throw e;
39
+ }
40
+ }
41
+ throw new Error(`could not acquire bindings lock after ${LOCK_MAX_RETRIES * LOCK_RETRY_MS}ms`);
42
+ }
43
+ function releaseLock(lockPath) {
44
+ try {
45
+ unlinkSync(lockPath);
46
+ }
47
+ catch { /* ignore */ }
48
+ }
49
+ function readBindings() {
50
+ if (!existsSync(PATHS.bindingsFile)) {
51
+ return { matrix: {} };
52
+ }
53
+ const raw = readFileSync(PATHS.bindingsFile, 'utf8');
54
+ const parsed = parseYaml(raw);
55
+ if (!parsed)
56
+ return { matrix: {} };
57
+ return {
58
+ matrix: parsed.matrix ?? {},
59
+ };
60
+ }
61
+ function writeBindings(bindings) {
62
+ mkdirSync(dirname(PATHS.bindingsFile), { recursive: true });
63
+ const tmp = PATHS.bindingsFile + '.tmp';
64
+ const yaml = stringifyYaml(bindings, { sortMapEntries: true, lineWidth: 0 });
65
+ writeFileSync(tmp, yaml, { mode: 0o600 });
66
+ renameSync(tmp, PATHS.bindingsFile);
67
+ try {
68
+ chmodSync(PATHS.bindingsFile, 0o600);
69
+ }
70
+ catch { /* ignore */ }
71
+ }
72
+ async function cmdAdd(channelId, profile) {
73
+ if (!channelId || !profile) {
74
+ console.error('nps bind add: usage: nps bind add <channel-id> <profile>');
75
+ return 1;
76
+ }
77
+ if (!/^[a-zA-Z0-9_-]+$/.test(profile)) {
78
+ console.error('nps bind add: profile must be [a-zA-Z0-9_-]+');
79
+ return 1;
80
+ }
81
+ await acquireLock(PATHS.bindingsLock);
82
+ try {
83
+ const b = readBindings();
84
+ b.matrix[channelId] = profile;
85
+ writeBindings(b);
86
+ console.log(`✅ bound ${channelId} → ${profile}`);
87
+ }
88
+ finally {
89
+ releaseLock(PATHS.bindingsLock);
90
+ }
91
+ return 0;
92
+ }
93
+ async function cmdRemove(channelId) {
94
+ if (!channelId) {
95
+ console.error('nps bind remove: channel id required');
96
+ return 1;
97
+ }
98
+ await acquireLock(PATHS.bindingsLock);
99
+ try {
100
+ const b = readBindings();
101
+ if (!(channelId in b.matrix)) {
102
+ console.log(`(no binding for ${channelId})`);
103
+ return 0;
104
+ }
105
+ delete b.matrix[channelId];
106
+ writeBindings(b);
107
+ console.log(`✅ removed binding for ${channelId}`);
108
+ }
109
+ finally {
110
+ releaseLock(PATHS.bindingsLock);
111
+ }
112
+ return 0;
113
+ }
114
+ function cmdList() {
115
+ const b = readBindings();
116
+ const keys = Object.keys(b.matrix);
117
+ if (keys.length === 0) {
118
+ console.log('(no bindings)');
119
+ return 0;
120
+ }
121
+ console.log('Matrix bindings:');
122
+ for (const k of keys.sort()) {
123
+ console.log(` ${k} → ${b.matrix[k]}`);
124
+ }
125
+ return 0;
126
+ }
127
+ //# sourceMappingURL=bind-cmd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bind-cmd.js","sourceRoot":"","sources":["../../src/cli/bind-cmd.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { stopDaemon } from '../daemon/daemon.js';
2
+ export declare function runDaemonCmd(args: string[]): Promise<number>;
3
+ export { stopDaemon };
4
+ //# sourceMappingURL=daemon-cmd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-cmd.d.ts","sourceRoot":"","sources":["../../src/cli/daemon-cmd.ts"],"names":[],"mappings":"AAUA,OAAO,EAAe,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAE9D,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAWlE;AAiHD,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * `nps daemon` subcommand.
3
+ */
4
+ import { existsSync, writeFileSync, mkdirSync, chmodSync } from 'node:fs';
5
+ import { homedir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { dirname } from 'node:path';
9
+ import { PATHS } from '../config/paths.js';
10
+ import { rpcCall } from '../daemon/control-client.js';
11
+ import { startDaemon, stopDaemon } from '../daemon/daemon.js';
12
+ export async function runDaemonCmd(args) {
13
+ const sub = args[0] ?? '';
14
+ switch (sub) {
15
+ case 'start': return await cmdStart();
16
+ case 'stop': return await cmdStop();
17
+ case 'status': return await cmdStatus();
18
+ case 'install-service': return cmdInstallService();
19
+ default:
20
+ console.error('nps daemon: subcommand required (start|stop|status|install-service)');
21
+ return 1;
22
+ }
23
+ }
24
+ async function cmdStart() {
25
+ await startDaemon();
26
+ // Block forever until SIGINT/SIGTERM
27
+ await new Promise(() => { });
28
+ return 0;
29
+ }
30
+ async function cmdStop() {
31
+ try {
32
+ await rpcCall(PATHS.daemonSocket, 'stop', undefined, 5_000);
33
+ console.log('nps daemon: stop requested');
34
+ return 0;
35
+ }
36
+ catch (e) {
37
+ console.error(`nps daemon stop: ${e instanceof Error ? e.message : e}`);
38
+ return 1;
39
+ }
40
+ }
41
+ async function cmdStatus() {
42
+ try {
43
+ const health = await rpcCall(PATHS.daemonSocket, 'health', undefined, 3_000);
44
+ console.log(JSON.stringify(health, null, 2));
45
+ return 0;
46
+ }
47
+ catch (e) {
48
+ console.error(`nps daemon status: ${e instanceof Error ? e.message : e}`);
49
+ return 1;
50
+ }
51
+ }
52
+ function cmdInstallService() {
53
+ const home = homedir();
54
+ const unitDir = join(home, '.config', 'systemd', 'user');
55
+ mkdirSync(unitDir, { recursive: true });
56
+ const unitPath = join(unitDir, 'nps.service');
57
+ // Resolve script path from import.meta when present (post-build).
58
+ let scriptArg = '';
59
+ try {
60
+ const here = dirname(fileURLToPath(import.meta.url));
61
+ const pkgRoot = join(here, '..', '..');
62
+ const distMain = join(pkgRoot, 'dist', 'main.js');
63
+ if (existsSync(distMain)) {
64
+ scriptArg = distMain;
65
+ }
66
+ }
67
+ catch { /* fall through */ }
68
+ // Generate a wrapper script so we can `source ~/.coral/env` (handles
69
+ // `export KEY=value` lines which systemd's EnvironmentFile cannot).
70
+ // Wrapper also captures current PATH so claude + claude-agent-acp resolve
71
+ // (systemd's user service PATH defaults to /usr/bin:/usr/local/bin which
72
+ // doesn't include ~/.local/bin or ~/.npm-global/bin).
73
+ const wrapperPath = join(home, '.nps', 'run-daemon.sh');
74
+ mkdirSync(dirname(wrapperPath), { recursive: true });
75
+ const inheritedPath = process.env['PATH'] ?? '/usr/local/bin:/usr/bin:/bin';
76
+ const wrapper = `#!/bin/bash
77
+ # Generated by \`nps daemon install-service\` — do not edit by hand; rerun the command instead.
78
+ # Inherits PATH from install-time shell so claude + claude-agent-acp resolve.
79
+ export PATH="${inheritedPath}"
80
+
81
+ # Source ~/.coral/env if present (supports \`export KEY=value\` lines).
82
+ if [ -f "$HOME/.coral/env" ]; then
83
+ set -a
84
+ # shellcheck disable=SC1091
85
+ source "$HOME/.coral/env"
86
+ set +a
87
+ fi
88
+
89
+ exec ${scriptArg ? `${process.execPath} ${scriptArg}` : '/usr/bin/env nps'} daemon start
90
+ `;
91
+ writeFileSync(wrapperPath, wrapper);
92
+ try {
93
+ chmodSync(wrapperPath, 0o700);
94
+ }
95
+ catch { /* ignore */ }
96
+ const unit = `[Unit]
97
+ Description=nps-cli — local Claude Code agent daemon
98
+ After=network-online.target
99
+
100
+ [Service]
101
+ Type=simple
102
+ ExecStart=${wrapperPath}
103
+ Restart=on-failure
104
+ RestartSec=5
105
+ Environment=NODE_ENV=production
106
+
107
+ [Install]
108
+ WantedBy=default.target
109
+ `;
110
+ writeFileSync(unitPath, unit);
111
+ try {
112
+ chmodSync(unitPath, 0o644);
113
+ }
114
+ catch { /* ignore */ }
115
+ console.log(`✅ wrote wrapper: ${wrapperPath}`);
116
+ console.log(`✅ wrote systemd unit: ${unitPath}`);
117
+ console.log();
118
+ console.log('Wrapper sources ~/.coral/env (handles `export` lines + tokens) and');
119
+ console.log('inherits PATH from this shell so claude + claude-agent-acp resolve.');
120
+ console.log();
121
+ console.log('To enable + start:');
122
+ console.log(' systemctl --user daemon-reload');
123
+ console.log(' systemctl --user enable --now nps.service');
124
+ console.log();
125
+ console.log('For boot persistence (survives logout):');
126
+ console.log(' loginctl enable-linger $USER');
127
+ console.log();
128
+ console.log('To stop:');
129
+ console.log(' systemctl --user stop nps.service');
130
+ console.log();
131
+ console.log('To inspect logs:');
132
+ console.log(' journalctl --user -u nps.service -f');
133
+ return 0;
134
+ }
135
+ // Re-export stopDaemon so tests can use it
136
+ export { stopDaemon };
137
+ //# sourceMappingURL=daemon-cmd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-cmd.js","sourceRoot":"","sources":["../../src/cli/daemon-cmd.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runCli(argv: string[]): Promise<number>;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAmCA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA0B5D"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * M7 — `nps` CLI router.
3
+ *
4
+ * Subcommands:
5
+ * nps daemon { start | stop | status | install-service }
6
+ * nps agent { list | init <name> | edit <name> | invoke <name> '<msg>' }
7
+ * nps bind { add <channel-id> <profile> | remove <channel-id> | list }
8
+ *
9
+ * nps --version
10
+ * nps --help
11
+ */
12
+ import { NPS_VERSION } from '../version.js';
13
+ import { runAgentCmd } from './agent-cmd.js';
14
+ import { runDaemonCmd } from './daemon-cmd.js';
15
+ import { runBindCmd } from './bind-cmd.js';
16
+ function printUsage() {
17
+ console.log(`nps v${NPS_VERSION}
18
+
19
+ Usage:
20
+ nps daemon { start | stop | status | install-service }
21
+ nps agent { list | init <name> | edit <name> | invoke <name> '<msg>' }
22
+ nps bind { add <channel-id> <profile> | remove <channel-id> | list }
23
+ nps --version
24
+ nps --help
25
+
26
+ Files (resolved relative to NPS_HOME, default ~/.nps):
27
+ config.yaml global daemon + IM credentials
28
+ bindings.yaml channel → profile map (CLI-managed only)
29
+ agents/<name>/ profile directories (nps.yaml + CLAUDE.md + .claude/skills/)
30
+ state/<channel>.json per-channel session state
31
+ audit.jsonl audit log
32
+ `);
33
+ }
34
+ export async function runCli(argv) {
35
+ const args = argv.slice(2);
36
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
37
+ printUsage();
38
+ return 0;
39
+ }
40
+ if (args[0] === '--version' || args[0] === '-v') {
41
+ console.log(NPS_VERSION);
42
+ return 0;
43
+ }
44
+ const sub = args[0];
45
+ const rest = args.slice(1);
46
+ try {
47
+ switch (sub) {
48
+ case 'daemon': return await runDaemonCmd(rest);
49
+ case 'agent': return await runAgentCmd(rest);
50
+ case 'bind': return await runBindCmd(rest);
51
+ default:
52
+ console.error(`nps: unknown subcommand '${sub}'`);
53
+ printUsage();
54
+ return 1;
55
+ }
56
+ }
57
+ catch (e) {
58
+ console.error(`nps: ${e instanceof Error ? e.message : String(e)}`);
59
+ return 1;
60
+ }
61
+ }
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,SAAS,UAAU;IACjB,OAAO,CAAC,GAAG,CAAC,QAAQ,WAAW;;;;;;;;;;;;;;;CAehC,CAAC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAc;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAClE,UAAU,EAAE,CAAC;QACb,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACzB,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAW,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC;QACH,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,QAAQ,CAAC,CAAC,OAAO,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;YAC/C,KAAK,OAAO,CAAC,CAAE,OAAO,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;YAC9C,KAAK,MAAM,CAAC,CAAG,OAAO,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;YAC7C;gBACE,OAAO,CAAC,KAAK,CAAC,4BAA4B,GAAG,GAAG,CAAC,CAAC;gBAClD,UAAU,EAAE,CAAC;gBACb,OAAO,CAAC,CAAC;QACb,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACpE,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { type GlobalConfig, type ProfileConfig, type BindingsFile } from './schema.js';
2
+ export declare function loadGlobalConfig(path: string): GlobalConfig;
3
+ export declare function loadProfileConfig(path: string): ProfileConfig;
4
+ export declare function loadBindings(path: string): BindingsFile;
5
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAMA,OAAO,EAKL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AAErB,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAgB3D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAgB7D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAgBvD"}