@soleri/core 9.15.0 → 9.16.7
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/data/flows/deliver.flow.yaml +11 -0
- package/data/flows/design.flow.yaml +4 -14
- package/data/flows/enhance.flow.yaml +10 -0
- package/data/flows/explore.flow.yaml +16 -0
- package/data/flows/fix.flow.yaml +1 -1
- package/data/flows/review.flow.yaml +13 -4
- package/dist/capabilities/chain-mapping.d.ts.map +1 -1
- package/dist/capabilities/chain-mapping.js +5 -4
- package/dist/capabilities/chain-mapping.js.map +1 -1
- package/dist/capabilities/registry.d.ts +6 -0
- package/dist/capabilities/registry.d.ts.map +1 -1
- package/dist/capabilities/registry.js +3 -2
- package/dist/capabilities/registry.js.map +1 -1
- package/dist/context/context-engine.js +1 -1
- package/dist/context/context-engine.js.map +1 -1
- package/dist/engine/core-ops.d.ts.map +1 -1
- package/dist/engine/core-ops.js +38 -1
- package/dist/engine/core-ops.js.map +1 -1
- package/dist/flows/epilogue.d.ts +5 -1
- package/dist/flows/epilogue.d.ts.map +1 -1
- package/dist/flows/epilogue.js +11 -3
- package/dist/flows/epilogue.js.map +1 -1
- package/dist/flows/executor.d.ts.map +1 -1
- package/dist/flows/executor.js +13 -5
- package/dist/flows/executor.js.map +1 -1
- package/dist/flows/index.d.ts +1 -2
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js +1 -0
- package/dist/flows/index.js.map +1 -1
- package/dist/flows/plan-builder.d.ts +17 -1
- package/dist/flows/plan-builder.d.ts.map +1 -1
- package/dist/flows/plan-builder.js +67 -6
- package/dist/flows/plan-builder.js.map +1 -1
- package/dist/flows/probes.d.ts +1 -1
- package/dist/flows/probes.d.ts.map +1 -1
- package/dist/flows/probes.js +15 -3
- package/dist/flows/probes.js.map +1 -1
- package/dist/flows/types.d.ts +31 -4
- package/dist/flows/types.d.ts.map +1 -1
- package/dist/flows/types.js +6 -1
- package/dist/flows/types.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/packs/pack-installer.d.ts.map +1 -1
- package/dist/packs/pack-installer.js +28 -2
- package/dist/packs/pack-installer.js.map +1 -1
- package/dist/planning/planner-types.d.ts +2 -0
- package/dist/planning/planner-types.d.ts.map +1 -1
- package/dist/planning/planner.d.ts +1 -0
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +7 -0
- package/dist/planning/planner.js.map +1 -1
- package/dist/playbooks/playbook-executor.d.ts +10 -1
- package/dist/playbooks/playbook-executor.d.ts.map +1 -1
- package/dist/playbooks/playbook-executor.js +8 -2
- package/dist/playbooks/playbook-executor.js.map +1 -1
- package/dist/playbooks/playbook-types.d.ts +8 -0
- package/dist/playbooks/playbook-types.d.ts.map +1 -1
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
- package/dist/runtime/admin-extra-ops.js +30 -0
- package/dist/runtime/admin-extra-ops.js.map +1 -1
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +60 -21
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/admin-setup-ops.d.ts +11 -0
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
- package/dist/runtime/admin-setup-ops.js +87 -17
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/capture-ops.d.ts.map +1 -1
- package/dist/runtime/capture-ops.js +38 -12
- package/dist/runtime/capture-ops.js.map +1 -1
- package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
- package/dist/runtime/facades/brain-facade.js +16 -4
- package/dist/runtime/facades/brain-facade.js.map +1 -1
- package/dist/runtime/facades/context-facade.d.ts.map +1 -1
- package/dist/runtime/facades/context-facade.js +9 -3
- package/dist/runtime/facades/context-facade.js.map +1 -1
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +20 -7
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.js +12 -0
- package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +113 -4
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
- package/dist/runtime/facades/vault-facade.js +24 -3
- package/dist/runtime/facades/vault-facade.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts +21 -0
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +132 -38
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/schema-helpers.d.ts.map +1 -1
- package/dist/runtime/schema-helpers.js +4 -0
- package/dist/runtime/schema-helpers.js.map +1 -1
- package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
- package/dist/runtime/vault-linking-ops.js +16 -3
- package/dist/runtime/vault-linking-ops.js.map +1 -1
- package/dist/scheduler/cron-validator.d.ts +15 -0
- package/dist/scheduler/cron-validator.d.ts.map +1 -0
- package/dist/scheduler/cron-validator.js +93 -0
- package/dist/scheduler/cron-validator.js.map +1 -0
- package/dist/scheduler/platform-linux.d.ts +14 -0
- package/dist/scheduler/platform-linux.d.ts.map +1 -0
- package/dist/scheduler/platform-linux.js +107 -0
- package/dist/scheduler/platform-linux.js.map +1 -0
- package/dist/scheduler/platform-macos.d.ts +15 -0
- package/dist/scheduler/platform-macos.d.ts.map +1 -0
- package/dist/scheduler/platform-macos.js +131 -0
- package/dist/scheduler/platform-macos.js.map +1 -0
- package/dist/scheduler/scheduler-ops.d.ts +14 -0
- package/dist/scheduler/scheduler-ops.d.ts.map +1 -0
- package/dist/scheduler/scheduler-ops.js +77 -0
- package/dist/scheduler/scheduler-ops.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +55 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/scheduler.js +144 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/scheduler/types.d.ts +48 -0
- package/dist/scheduler/types.d.ts.map +1 -0
- package/dist/scheduler/types.js +6 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/skills/sync-skills.d.ts +11 -0
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +132 -38
- package/dist/skills/sync-skills.js.map +1 -1
- package/dist/utils/worktree-reaper.d.ts +38 -0
- package/dist/utils/worktree-reaper.d.ts.map +1 -0
- package/dist/utils/worktree-reaper.js +85 -0
- package/dist/utils/worktree-reaper.js.map +1 -0
- package/dist/vault/scope-detector.d.ts.map +1 -1
- package/dist/vault/scope-detector.js +37 -4
- package/dist/vault/scope-detector.js.map +1 -1
- package/dist/vault/vault-entries.d.ts.map +1 -1
- package/dist/vault/vault-entries.js +3 -1
- package/dist/vault/vault-entries.js.map +1 -1
- package/package.json +1 -1
- package/src/agency/agency-manager.test.ts +4 -4
- package/src/agency/default-rules.test.ts +0 -13
- package/src/brain/brain-intelligence.test.ts +0 -5
- package/src/brain/second-brain-features.test.ts +2 -14
- package/src/capabilities/chain-mapping.test.ts +1 -6
- package/src/capabilities/chain-mapping.ts +6 -4
- package/src/capabilities/registry.test.ts +1 -1
- package/src/capabilities/registry.ts +9 -2
- package/src/chat/agent-loop.test.ts +1 -1
- package/src/chat/chat-enhanced.test.ts +0 -8
- package/src/claudemd/compose.test.ts +0 -5
- package/src/context/context-engine.test.ts +0 -1
- package/src/context/context-engine.ts +1 -1
- package/src/control/intent-router.test.ts +2 -2
- package/src/curator/tag-manager.test.ts +0 -4
- package/src/domain-packs/types.test.ts +0 -5
- package/src/dream/dream.test.ts +0 -7
- package/src/enforcement/registry.test.ts +2 -2
- package/src/engine/core-ops.test.ts +4 -22
- package/src/engine/core-ops.ts +36 -1
- package/src/engine/module-manifest.test.ts +1 -31
- package/src/engine/register-engine.test.ts +3 -33
- package/src/errors/retry.test.ts +3 -1
- package/src/flows/chain-runner.test.ts +0 -6
- package/src/flows/context-router.test.ts +3 -3
- package/src/flows/epilogue.test.ts +40 -2
- package/src/flows/epilogue.ts +11 -2
- package/src/flows/executor.test.ts +48 -2
- package/src/flows/executor.ts +15 -5
- package/src/flows/index.ts +1 -3
- package/src/flows/plan-builder.test.ts +201 -0
- package/src/flows/plan-builder.ts +81 -5
- package/src/flows/probes.ts +17 -3
- package/src/flows/types.ts +31 -2
- package/src/health/health-registry.test.ts +3 -1
- package/src/index.ts +17 -0
- package/src/intake/dedup-gate.test.ts +2 -6
- package/src/intake/text-ingester.test.ts +3 -4
- package/src/llm/llm-client.test.ts +1 -1
- package/src/llm/utils.test.ts +1 -1
- package/src/migrations/migration-runner.test.ts +0 -1
- package/src/operator/operator-context-store.test.ts +0 -13
- package/src/operator/operator-profile.test.ts +2 -20
- package/src/packs/pack-installer.ts +28 -2
- package/src/packs/pack-system.test.ts +2 -2
- package/src/persona/defaults.test.ts +19 -19
- package/src/planning/gap-passes.test.ts +0 -46
- package/src/planning/gap-patterns.test.ts +0 -42
- package/src/planning/goal-ancestry.test.ts +3 -1
- package/src/planning/plan-lifecycle.test.ts +15 -7
- package/src/planning/planner-types.ts +2 -0
- package/src/planning/planner.ts +8 -0
- package/src/planning/reconciliation-engine.test.ts +3 -10
- package/src/planning/task-complexity-assessor.test.ts +0 -5
- package/src/planning/task-verifier.test.ts +3 -1
- package/src/playbooks/generic/generic-playbooks.test.ts +0 -28
- package/src/playbooks/index.test.ts +0 -55
- package/src/playbooks/playbook-executor.test.ts +76 -0
- package/src/playbooks/playbook-executor.ts +24 -3
- package/src/playbooks/playbook-types.ts +8 -0
- package/src/plugins/plugin-registry.test.ts +6 -2
- package/src/project/project-registry.test.ts +2 -0
- package/src/queue/async-infrastructure.test.ts +6 -4
- package/src/queue/job-queue.test.ts +13 -7
- package/src/runtime/admin-extra-ops.test.ts +35 -30
- package/src/runtime/admin-extra-ops.ts +30 -0
- package/src/runtime/admin-ops.test.ts +0 -4
- package/src/runtime/admin-ops.ts +63 -21
- package/src/runtime/admin-setup-ops.test.ts +185 -13
- package/src/runtime/admin-setup-ops.ts +86 -16
- package/src/runtime/archive-ops.test.ts +0 -28
- package/src/runtime/branching-ops.test.ts +0 -17
- package/src/runtime/capture-ops.test.ts +41 -16
- package/src/runtime/capture-ops.ts +78 -46
- package/src/runtime/chain-ops.test.ts +0 -21
- package/src/runtime/facades/admin-facade.test.ts +0 -34
- package/src/runtime/facades/agency-facade.test.ts +0 -39
- package/src/runtime/facades/archive-facade.test.ts +0 -43
- package/src/runtime/facades/brain-facade.test.ts +8 -99
- package/src/runtime/facades/brain-facade.ts +29 -12
- package/src/runtime/facades/branching-facade.test.ts +30 -17
- package/src/runtime/facades/chat-facade.test.ts +0 -91
- package/src/runtime/facades/chat-service-ops.test.ts +0 -24
- package/src/runtime/facades/chat-session-ops.test.ts +0 -12
- package/src/runtime/facades/chat-transport-ops.test.ts +0 -23
- package/src/runtime/facades/context-facade.test.ts +0 -17
- package/src/runtime/facades/context-facade.ts +11 -4
- package/src/runtime/facades/control-facade.test.ts +0 -30
- package/src/runtime/facades/curator-facade.test.ts +0 -33
- package/src/runtime/facades/intake-facade.test.ts +0 -33
- package/src/runtime/facades/links-facade.test.ts +0 -37
- package/src/runtime/facades/loop-facade.test.ts +0 -26
- package/src/runtime/facades/memory-facade.test.ts +0 -18
- package/src/runtime/facades/memory-facade.ts +27 -11
- package/src/runtime/facades/operator-facade.test.ts +0 -31
- package/src/runtime/facades/orchestrate-facade.test.ts +0 -21
- package/src/runtime/facades/orchestrate-facade.ts +12 -0
- package/src/runtime/facades/plan-facade.test.ts +7 -32
- package/src/runtime/facades/plan-facade.ts +137 -4
- package/src/runtime/facades/review-facade.test.ts +1 -49
- package/src/runtime/facades/sync-facade.test.ts +24 -41
- package/src/runtime/facades/tier-facade.test.ts +30 -22
- package/src/runtime/facades/vault-facade.test.ts +0 -41
- package/src/runtime/facades/vault-facade.ts +26 -3
- package/src/runtime/grading-ops.test.ts +0 -27
- package/src/runtime/intake-ops.test.ts +0 -19
- package/src/runtime/loop-ops.test.ts +0 -48
- package/src/runtime/memory-cross-project-ops.test.ts +0 -14
- package/src/runtime/memory-extra-ops.test.ts +4 -8
- package/src/runtime/orchestrate-ops.test.ts +238 -19
- package/src/runtime/orchestrate-ops.ts +166 -41
- package/src/runtime/pack-ops.test.ts +0 -26
- package/src/runtime/planning-extra-ops.test.ts +2 -14
- package/src/runtime/playbook-ops-execution.test.ts +9 -20
- package/src/runtime/playbook-ops.test.ts +4 -67
- package/src/runtime/review-ops.test.ts +0 -15
- package/src/runtime/schema-helpers.ts +4 -0
- package/src/runtime/sync-ops.test.ts +0 -18
- package/src/runtime/tier-ops.test.ts +0 -21
- package/src/runtime/vault-extra-ops.test.ts +0 -12
- package/src/runtime/vault-linking-ops.test.ts +0 -4
- package/src/runtime/vault-linking-ops.ts +26 -8
- package/src/runtime/vault-sharing-ops.test.ts +0 -9
- package/src/scheduler/cron-validator.ts +101 -0
- package/src/scheduler/platform-linux.ts +122 -0
- package/src/scheduler/platform-macos.ts +150 -0
- package/src/scheduler/scheduler-ops.ts +77 -0
- package/src/scheduler/scheduler.test.ts +247 -0
- package/src/scheduler/scheduler.ts +174 -0
- package/src/scheduler/types.ts +52 -0
- package/src/skills/__tests__/sync-skills.test.ts +6 -17
- package/src/skills/global-claude-md.test.ts +113 -0
- package/src/skills/sync-skills.ts +143 -35
- package/src/skills/validate-skills.test.ts +12 -11
- package/src/telemetry/telemetry.test.ts +1 -0
- package/src/transport/http-server.test.ts +3 -0
- package/src/transport/session-manager.test.ts +3 -1
- package/src/transport/token-auth.test.ts +6 -9
- package/src/transport/ws-server.test.ts +10 -2
- package/src/utils/worktree-reaper.ts +113 -0
- package/src/vault/__tests__/vault-characterization.test.ts +0 -108
- package/src/vault/linking.test.ts +0 -2
- package/src/vault/playbook.test.ts +4 -1
- package/src/vault/scope-detector.test.ts +3 -1
- package/src/vault/scope-detector.ts +42 -4
- package/src/vault/vault-connect.test.ts +1 -1
- package/src/vault/vault-entries.ts +3 -1
- package/src/vault/vault.test.ts +23 -8
|
@@ -134,18 +134,6 @@ describe('createPlanningExtraOps', () => {
|
|
|
134
134
|
ops = createPlanningExtraOps(runtime);
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
-
it('returns expected op count', () => {
|
|
138
|
-
expect(ops.length).toBeGreaterThanOrEqual(22);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('all ops have required fields', () => {
|
|
142
|
-
for (const op of ops) {
|
|
143
|
-
expect(op.name).toBeTruthy();
|
|
144
|
-
expect(op.handler).toBeDefined();
|
|
145
|
-
expect(['read', 'write', 'admin']).toContain(op.auth);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
137
|
describe('plan_iterate', () => {
|
|
150
138
|
it('iterates a draft plan', async () => {
|
|
151
139
|
const result = (await findOp(ops, 'plan_iterate').handler({
|
|
@@ -460,7 +448,7 @@ describe('createPlanningExtraOps', () => {
|
|
|
460
448
|
intent: 'BUILD',
|
|
461
449
|
})) as Record<string, unknown>;
|
|
462
450
|
expect(result.matched).toBe(true);
|
|
463
|
-
expect(result.sections).
|
|
451
|
+
expect(result.sections).toEqual(['design', 'scope', 'tokens']); // generic + domain brainstorm sections
|
|
464
452
|
});
|
|
465
453
|
|
|
466
454
|
it('returns not-matched when no playbook fits', async () => {
|
|
@@ -498,7 +486,7 @@ describe('createPlanningExtraOps', () => {
|
|
|
498
486
|
planId: 'plan-1',
|
|
499
487
|
})) as Record<string, unknown>;
|
|
500
488
|
expect(result.planId).toBe('plan-1');
|
|
501
|
-
expect(result.taskMetrics).
|
|
489
|
+
expect(result.taskMetrics).toHaveLength(1); // mock plan has exactly 1 task
|
|
502
490
|
});
|
|
503
491
|
|
|
504
492
|
it('returns error for missing plan', async () => {
|
|
@@ -26,15 +26,6 @@ describe('playbook execution ops', () => {
|
|
|
26
26
|
ops = createPlaybookOps(runtime);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
it('should return 8 ops total', () => {
|
|
30
|
-
setup();
|
|
31
|
-
expect(ops).toHaveLength(8);
|
|
32
|
-
const names = ops.map((o) => o.name);
|
|
33
|
-
expect(names).toContain('playbook_start');
|
|
34
|
-
expect(names).toContain('playbook_step');
|
|
35
|
-
expect(names).toContain('playbook_complete');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
29
|
// ─── playbook_start ─────────────────────────────────────────────
|
|
39
30
|
|
|
40
31
|
describe('playbook_start', () => {
|
|
@@ -46,7 +37,7 @@ describe('playbook execution ops', () => {
|
|
|
46
37
|
|
|
47
38
|
expect(result.sessionId).toMatch(/^pbk-/);
|
|
48
39
|
expect(result.label).toBe('Test-Driven Development');
|
|
49
|
-
expect(result.totalSteps).
|
|
40
|
+
expect(result.totalSteps).toBe(4); // TDD playbook has 4 steps: RED, GREEN, REFACTOR, VERIFY
|
|
50
41
|
});
|
|
51
42
|
|
|
52
43
|
it('should start by intent auto-match', async () => {
|
|
@@ -57,7 +48,7 @@ describe('playbook execution ops', () => {
|
|
|
57
48
|
})) as { sessionId: string; label: string };
|
|
58
49
|
|
|
59
50
|
expect(result.sessionId).toMatch(/^pbk-/);
|
|
60
|
-
expect(result.label).
|
|
51
|
+
expect(result.label).toBe('Test-Driven Development'); // BUILD + "tests" triggers TDD playbook
|
|
61
52
|
});
|
|
62
53
|
|
|
63
54
|
it('should return error for unknown playbookId', async () => {
|
|
@@ -69,21 +60,19 @@ describe('playbook execution ops', () => {
|
|
|
69
60
|
expect(result.error).toContain('not found');
|
|
70
61
|
});
|
|
71
62
|
|
|
72
|
-
it('should
|
|
63
|
+
it('should start Verification playbook for DELIVER intent', async () => {
|
|
73
64
|
setup();
|
|
74
65
|
const result = (await findOp('playbook_start').handler({
|
|
75
66
|
intent: 'DELIVER',
|
|
76
67
|
text: 'something very obscure with no keyword matches',
|
|
77
|
-
})) as {
|
|
68
|
+
})) as { sessionId: string; label: string };
|
|
78
69
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
expect(result.available.length).toBeGreaterThan(0);
|
|
83
|
-
}
|
|
70
|
+
// DELIVER intent always matches Verification Before Completion
|
|
71
|
+
expect(result.sessionId).toMatch(/^pbk-/);
|
|
72
|
+
expect(result.label).toBe('Verification Before Completion');
|
|
84
73
|
});
|
|
85
74
|
|
|
86
|
-
it('should return error with no params', async () => {
|
|
75
|
+
it('should return error with all 7 available playbooks listed when no params', async () => {
|
|
87
76
|
setup();
|
|
88
77
|
const result = (await findOp('playbook_start').handler({})) as {
|
|
89
78
|
error: string;
|
|
@@ -91,7 +80,7 @@ describe('playbook execution ops', () => {
|
|
|
91
80
|
};
|
|
92
81
|
|
|
93
82
|
expect(result.error).toContain('Provide');
|
|
94
|
-
expect(result.available
|
|
83
|
+
expect(result.available).toHaveLength(7); // 7 built-in playbooks
|
|
95
84
|
});
|
|
96
85
|
});
|
|
97
86
|
|
|
@@ -183,25 +183,16 @@ describe('playbook-ops', () => {
|
|
|
183
183
|
});
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
-
describe('playbook_seed', () => {
|
|
187
|
-
it('seeds default playbooks', async () => {
|
|
188
|
-
const { ops } = setup();
|
|
189
|
-
const res = await executeOp(ops, 'playbook_seed');
|
|
190
|
-
expect(res.success).toBe(true);
|
|
191
|
-
// seedDefaultPlaybooks returns { seeded, skipped, errors }
|
|
192
|
-
const data = res.data as { seeded: number; skipped: number };
|
|
193
|
-
expect(typeof data.seeded).toBe('number');
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
186
|
describe('playbook_start', () => {
|
|
198
187
|
it('returns error when neither playbookId nor intent provided', async () => {
|
|
199
188
|
const { ops } = setup();
|
|
200
189
|
const res = await executeOp(ops, 'playbook_start', {});
|
|
201
190
|
expect(res.success).toBe(true);
|
|
202
|
-
const data = res.data as { error: string; available:
|
|
191
|
+
const data = res.data as { error: string; available: Array<{ id: string; title: string }> };
|
|
203
192
|
expect(data.error).toContain('Provide playbookId or intent');
|
|
204
|
-
expect(data.available).
|
|
193
|
+
expect(data.available).toHaveLength(7); // 7 built-in playbooks
|
|
194
|
+
expect(data.available[0]).toHaveProperty('id');
|
|
195
|
+
expect(data.available[0]).toHaveProperty('title');
|
|
205
196
|
});
|
|
206
197
|
|
|
207
198
|
it('returns error for unknown playbookId', async () => {
|
|
@@ -228,58 +219,4 @@ describe('playbook-ops', () => {
|
|
|
228
219
|
// Either starts a matched playbook or returns "no matching playbook"
|
|
229
220
|
});
|
|
230
221
|
});
|
|
231
|
-
|
|
232
|
-
describe('playbook_step', () => {
|
|
233
|
-
it('delegates to executor step', async () => {
|
|
234
|
-
const { ops, playbookExecutor } = setup();
|
|
235
|
-
const res = await executeOp(ops, 'playbook_step', {
|
|
236
|
-
sessionId: 'test-session',
|
|
237
|
-
output: 'Did the thing',
|
|
238
|
-
});
|
|
239
|
-
expect(res.success).toBe(true);
|
|
240
|
-
expect(playbookExecutor.step).toHaveBeenCalledWith('test-session', {
|
|
241
|
-
output: 'Did the thing',
|
|
242
|
-
skip: undefined,
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it('supports skip option', async () => {
|
|
247
|
-
const { ops, playbookExecutor } = setup();
|
|
248
|
-
await executeOp(ops, 'playbook_step', { sessionId: 's1', skip: true });
|
|
249
|
-
expect(playbookExecutor.step).toHaveBeenCalledWith('s1', { output: undefined, skip: true });
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
describe('playbook_complete', () => {
|
|
254
|
-
it('delegates to executor complete', async () => {
|
|
255
|
-
const { ops, playbookExecutor } = setup();
|
|
256
|
-
const res = await executeOp(ops, 'playbook_complete', { sessionId: 's1' });
|
|
257
|
-
expect(res.success).toBe(true);
|
|
258
|
-
expect(playbookExecutor.complete).toHaveBeenCalledWith('s1', {
|
|
259
|
-
abort: undefined,
|
|
260
|
-
gateResults: undefined,
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it('supports abort option', async () => {
|
|
265
|
-
const { ops, playbookExecutor } = setup();
|
|
266
|
-
await executeOp(ops, 'playbook_complete', { sessionId: 's1', abort: true });
|
|
267
|
-
expect(playbookExecutor.complete).toHaveBeenCalledWith('s1', {
|
|
268
|
-
abort: true,
|
|
269
|
-
gateResults: undefined,
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('passes gate results', async () => {
|
|
274
|
-
const { ops, playbookExecutor } = setup();
|
|
275
|
-
await executeOp(ops, 'playbook_complete', {
|
|
276
|
-
sessionId: 's1',
|
|
277
|
-
gateResults: { 'tests-pass': true, 'lint-clean': false },
|
|
278
|
-
});
|
|
279
|
-
expect(playbookExecutor.complete).toHaveBeenCalledWith('s1', {
|
|
280
|
-
abort: undefined,
|
|
281
|
-
gateResults: { 'tests-pass': true, 'lint-clean': false },
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
222
|
});
|
|
@@ -39,21 +39,6 @@ describe('createReviewOps', () => {
|
|
|
39
39
|
ops = createReviewOps(rt);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
it('returns 5 ops', () => {
|
|
43
|
-
expect(ops.length).toBe(5);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('has the expected op names', () => {
|
|
47
|
-
const names = ops.map((o) => o.name);
|
|
48
|
-
expect(names).toEqual([
|
|
49
|
-
'vault_submit_review',
|
|
50
|
-
'vault_approve',
|
|
51
|
-
'vault_reject',
|
|
52
|
-
'vault_pending_reviews',
|
|
53
|
-
'vault_review_stats',
|
|
54
|
-
]);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
42
|
// ─── vault_submit_review ──────────────────────────────────────
|
|
58
43
|
|
|
59
44
|
describe('vault_submit_review', () => {
|
|
@@ -14,6 +14,10 @@ export function coerceArray<T extends z.ZodTypeAny>(itemSchema: T) {
|
|
|
14
14
|
/* fall through to let Zod reject */
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
// Wrap a bare object in an array so callers can omit the array wrapper
|
|
18
|
+
if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
|
|
19
|
+
return [val];
|
|
20
|
+
}
|
|
17
21
|
return val;
|
|
18
22
|
}, z.array(itemSchema));
|
|
19
23
|
}
|
|
@@ -72,24 +72,6 @@ describe('createSyncOps', () => {
|
|
|
72
72
|
ops = createSyncOps(rt);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
it('returns 8 ops', () => {
|
|
76
|
-
expect(ops.length).toBe(8);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('has the expected op names', () => {
|
|
80
|
-
const names = ops.map((o) => o.name);
|
|
81
|
-
expect(names).toEqual([
|
|
82
|
-
'vault_git_push',
|
|
83
|
-
'vault_git_pull',
|
|
84
|
-
'vault_git_sync',
|
|
85
|
-
'obsidian_export',
|
|
86
|
-
'obsidian_import',
|
|
87
|
-
'obsidian_sync',
|
|
88
|
-
'vault_export_pack',
|
|
89
|
-
'vault_import_pack',
|
|
90
|
-
]);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
75
|
// ─── vault_git_push ───────────────────────────────────────────
|
|
94
76
|
|
|
95
77
|
describe('vault_git_push', () => {
|
|
@@ -37,27 +37,6 @@ describe('tier-ops', () => {
|
|
|
37
37
|
ops = captureOps(createTierOps(runtime));
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
it('registers all 7 tier/source ops', () => {
|
|
41
|
-
expect(ops.size).toBe(7);
|
|
42
|
-
expect(ops.has('vault_connect')).toBe(true);
|
|
43
|
-
expect(ops.has('vault_disconnect')).toBe(true);
|
|
44
|
-
expect(ops.has('vault_tiers')).toBe(true);
|
|
45
|
-
expect(ops.has('vault_search_all')).toBe(true);
|
|
46
|
-
expect(ops.has('vault_connect_source')).toBe(true);
|
|
47
|
-
expect(ops.has('vault_disconnect_source')).toBe(true);
|
|
48
|
-
expect(ops.has('vault_list_sources')).toBe(true);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('has correct auth levels', () => {
|
|
52
|
-
expect(ops.get('vault_connect')!.auth).toBe('admin');
|
|
53
|
-
expect(ops.get('vault_disconnect')!.auth).toBe('admin');
|
|
54
|
-
expect(ops.get('vault_tiers')!.auth).toBe('read');
|
|
55
|
-
expect(ops.get('vault_search_all')!.auth).toBe('read');
|
|
56
|
-
expect(ops.get('vault_connect_source')!.auth).toBe('admin');
|
|
57
|
-
expect(ops.get('vault_disconnect_source')!.auth).toBe('admin');
|
|
58
|
-
expect(ops.get('vault_list_sources')!.auth).toBe('read');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
40
|
// ─── Multi-vault ops ──────────────────────────────────────────────
|
|
62
41
|
|
|
63
42
|
describe('vault_connect', () => {
|
|
@@ -77,18 +77,6 @@ describe('createVaultExtraOps', () => {
|
|
|
77
77
|
ops = createVaultExtraOps(runtime);
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
it('returns 13 ops', () => {
|
|
81
|
-
expect(ops).toHaveLength(13);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('all ops have required fields', () => {
|
|
85
|
-
for (const op of ops) {
|
|
86
|
-
expect(op.name).toBeTruthy();
|
|
87
|
-
expect(op.handler).toBeDefined();
|
|
88
|
-
expect(['read', 'write', 'admin']).toContain(op.auth);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
80
|
describe('vault_get', () => {
|
|
93
81
|
it('returns entry by ID', async () => {
|
|
94
82
|
const result = (await findOp(ops, 'vault_get').handler({ id: 'entry-1' })) as Record<
|
|
@@ -52,10 +52,6 @@ describe('createVaultLinkingOps', () => {
|
|
|
52
52
|
ops = createVaultLinkingOps(rt);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
it('returns 9 ops', () => {
|
|
56
|
-
expect(ops.length).toBe(9);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
55
|
// ─── link_entries ─────────────────────────────────────────────
|
|
60
56
|
|
|
61
57
|
describe('link_entries', () => {
|
|
@@ -32,14 +32,32 @@ export function createVaultLinkingOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
32
32
|
name: 'link_entries',
|
|
33
33
|
description: 'Create a typed link between two vault entries (Zettelkasten)',
|
|
34
34
|
auth: 'write',
|
|
35
|
-
schema: z.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
schema: z.preprocess(
|
|
36
|
+
(val) => {
|
|
37
|
+
if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
|
|
38
|
+
const obj = val as Record<string, unknown>;
|
|
39
|
+
// Accept "relationship" as alias for "linkType"
|
|
40
|
+
if (obj.relationship !== undefined && obj.linkType === undefined) {
|
|
41
|
+
return { ...obj, linkType: obj.relationship };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return val;
|
|
45
|
+
},
|
|
46
|
+
z.object({
|
|
47
|
+
sourceId: z.string().describe('REQUIRED: Source entry ID'),
|
|
48
|
+
targetId: z.string().describe('REQUIRED: Target entry ID'),
|
|
49
|
+
linkType: z
|
|
50
|
+
.enum(['supports', 'contradicts', 'extends', 'sequences'])
|
|
51
|
+
.describe(
|
|
52
|
+
'REQUIRED: Relationship type — supports | contradicts | extends | sequences. Alias: relationship',
|
|
53
|
+
),
|
|
54
|
+
relationship: z
|
|
55
|
+
.enum(['supports', 'contradicts', 'extends', 'sequences'])
|
|
56
|
+
.optional()
|
|
57
|
+
.describe('Alias for linkType'),
|
|
58
|
+
note: z.string().optional().describe('Optional context for the link'),
|
|
59
|
+
}),
|
|
60
|
+
),
|
|
43
61
|
handler: async (params) => {
|
|
44
62
|
const sourceId = params.sourceId as string;
|
|
45
63
|
const targetId = params.targetId as string;
|
|
@@ -54,15 +54,6 @@ describe('createVaultSharingOps', () => {
|
|
|
54
54
|
ops = createVaultSharingOps(rt);
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
it('returns 3 scope ops', () => {
|
|
58
|
-
expect(ops.length).toBe(3);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('has the expected op names', () => {
|
|
62
|
-
const names = ops.map((o) => o.name);
|
|
63
|
-
expect(names).toEqual(['vault_detect_scope', 'vault_set_scope', 'vault_list_by_scope']);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
57
|
// ─── vault_detect_scope ───────────────────────────────────────
|
|
67
58
|
|
|
68
59
|
describe('vault_detect_scope', () => {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal cron expression validator.
|
|
3
|
+
* Enforces minimum 1-hour interval to prevent runaway automation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validate a cron expression (5-field: minute hour day month weekday).
|
|
8
|
+
* Returns null if valid, or an error message if invalid.
|
|
9
|
+
*/
|
|
10
|
+
export function validateCron(expression: string): string | null {
|
|
11
|
+
const parts = expression.trim().split(/\s+/);
|
|
12
|
+
if (parts.length !== 5) {
|
|
13
|
+
return 'Cron expression must have exactly 5 fields: minute hour day month weekday';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const [minute, hour] = parts;
|
|
17
|
+
|
|
18
|
+
// Enforce minimum 1-hour interval: minute field must be a single fixed value (no */1 style)
|
|
19
|
+
if (minute === '*' || minute.startsWith('*/')) {
|
|
20
|
+
return 'Minimum scheduling interval is 1 hour. Minute field cannot be * or */N';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Hour field: validate basic format (number, range, or list — not */1 which would be every hour on the minute)
|
|
24
|
+
if (hour !== '*' && !isValidField(hour, 0, 23)) {
|
|
25
|
+
return `Invalid hour field: "${hour}"`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!isValidField(parts[0], 0, 59)) return `Invalid minute field: "${parts[0]}"`;
|
|
29
|
+
if (!isValidField(parts[2], 1, 31)) return `Invalid day field: "${parts[2]}"`;
|
|
30
|
+
if (!isValidField(parts[3], 1, 12)) return `Invalid month field: "${parts[3]}"`;
|
|
31
|
+
if (!isValidField(parts[4], 0, 7)) return `Invalid weekday field: "${parts[4]}"`;
|
|
32
|
+
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isValidField(field: string, min: number, max: number): boolean {
|
|
37
|
+
if (field === '*') return true;
|
|
38
|
+
|
|
39
|
+
// Handle step values: */N or N-M/S
|
|
40
|
+
if (field.includes('/')) {
|
|
41
|
+
const [range, step] = field.split('/');
|
|
42
|
+
const stepNum = Number(step);
|
|
43
|
+
if (isNaN(stepNum) || stepNum < 1) return false;
|
|
44
|
+
if (range !== '*' && !isValidRange(range, min, max)) return false;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Handle comma-separated lists
|
|
49
|
+
if (field.includes(',')) {
|
|
50
|
+
return field.split(',').every((v) => isValidValue(v.trim(), min, max));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Handle ranges
|
|
54
|
+
if (field.includes('-')) {
|
|
55
|
+
return isValidRange(field, min, max);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return isValidValue(field, min, max);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isValidRange(range: string, min: number, max: number): boolean {
|
|
62
|
+
const [start, end] = range.split('-');
|
|
63
|
+
const s = Number(start);
|
|
64
|
+
const e = Number(end);
|
|
65
|
+
return !isNaN(s) && !isNaN(e) && s >= min && e <= max && s <= e;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isValidValue(value: string, min: number, max: number): boolean {
|
|
69
|
+
const n = Number(value);
|
|
70
|
+
return !isNaN(n) && n >= min && n <= max;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Estimate the minimum interval in hours from a cron expression.
|
|
75
|
+
* Returns Infinity if the expression is invalid.
|
|
76
|
+
*/
|
|
77
|
+
export function estimateMinIntervalHours(expression: string): number {
|
|
78
|
+
const parts = expression.trim().split(/\s+/);
|
|
79
|
+
if (parts.length !== 5) return Infinity;
|
|
80
|
+
|
|
81
|
+
const hour = parts[1];
|
|
82
|
+
|
|
83
|
+
// */N style = every N hours
|
|
84
|
+
if (hour.startsWith('*/')) {
|
|
85
|
+
return Number(hour.slice(2));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Comma-separated: e.g. "2,4,6" = every 2 hours
|
|
89
|
+
if (hour.includes(',')) {
|
|
90
|
+
const values = hour
|
|
91
|
+
.split(',')
|
|
92
|
+
.map(Number)
|
|
93
|
+
.sort((a, b) => a - b);
|
|
94
|
+
if (values.length < 2) return 24;
|
|
95
|
+
const gaps = values.slice(1).map((v, i) => v - values[i]);
|
|
96
|
+
return Math.min(...gaps);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Single value or range: at least once per day
|
|
100
|
+
return 24;
|
|
101
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linux platform adapter — uses systemd user timers for scheduling.
|
|
3
|
+
*
|
|
4
|
+
* Creates ~/.config/systemd/user/{name}.timer and {name}.service units.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, writeFileSync, rmSync, mkdirSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { execFileSync } from 'node:child_process';
|
|
11
|
+
import type { PlatformAdapter, ScheduledTask } from './types.js';
|
|
12
|
+
|
|
13
|
+
const SYSTEMD_USER_DIR = join(homedir(), '.config', 'systemd', 'user');
|
|
14
|
+
|
|
15
|
+
function unitPath(name: string, ext: 'service' | 'timer'): string {
|
|
16
|
+
return join(SYSTEMD_USER_DIR, `soleri-${name}.${ext}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Convert a 5-field cron to systemd OnCalendar format (simplified). */
|
|
20
|
+
function cronToOnCalendar(cron: string): string {
|
|
21
|
+
const [minute, hour, day, month, weekday] = cron.split(/\s+/);
|
|
22
|
+
|
|
23
|
+
// Simplified: only handle fixed values and */N patterns
|
|
24
|
+
const d = day === '*' ? '*' : day;
|
|
25
|
+
const M = month === '*' ? '*' : month;
|
|
26
|
+
const dow = weekday === '*' ? '*' : weekday;
|
|
27
|
+
const h = hour === '*' ? '*' : hour.startsWith('*/') ? `*/${hour.slice(2)}` : hour;
|
|
28
|
+
const m = minute === '*' ? '*' : minute;
|
|
29
|
+
|
|
30
|
+
return `${dow === '*' ? '' : dowName(dow)}*-${M}-${d} ${h}:${m}:00`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function dowName(dow: string): string {
|
|
34
|
+
const names = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
35
|
+
const n = Number(dow);
|
|
36
|
+
return isNaN(n) ? dow : (names[n % 7] ?? '*');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildService(task: ScheduledTask, logPath: string): string {
|
|
40
|
+
return `[Unit]
|
|
41
|
+
Description=Soleri scheduled task: ${task.name}
|
|
42
|
+
|
|
43
|
+
[Service]
|
|
44
|
+
Type=oneshot
|
|
45
|
+
ExecStart=/usr/local/bin/claude -p "${task.prompt.replace(/"/g, '\\"')}" --project-dir ${task.projectPath}
|
|
46
|
+
WorkingDirectory=${task.projectPath}
|
|
47
|
+
StandardOutput=append:${logPath}.log
|
|
48
|
+
StandardError=append:${logPath}.err
|
|
49
|
+
TimeoutStartSec=600
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildTimer(task: ScheduledTask): string {
|
|
54
|
+
return `[Unit]
|
|
55
|
+
Description=Soleri timer for task: ${task.name}
|
|
56
|
+
|
|
57
|
+
[Timer]
|
|
58
|
+
OnCalendar=${cronToOnCalendar(task.cronExpression)}
|
|
59
|
+
Persistent=true
|
|
60
|
+
|
|
61
|
+
[Install]
|
|
62
|
+
WantedBy=timers.target
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class LinuxAdapter implements PlatformAdapter {
|
|
67
|
+
async create(task: ScheduledTask): Promise<string> {
|
|
68
|
+
mkdirSync(SYSTEMD_USER_DIR, { recursive: true });
|
|
69
|
+
const logDir = join(homedir(), '.soleri', 'logs', 'scheduler');
|
|
70
|
+
mkdirSync(logDir, { recursive: true });
|
|
71
|
+
const logPath = join(logDir, task.name);
|
|
72
|
+
|
|
73
|
+
writeFileSync(unitPath(task.name, 'service'), buildService(task, logPath), 'utf-8');
|
|
74
|
+
writeFileSync(unitPath(task.name, 'timer'), buildTimer(task), 'utf-8');
|
|
75
|
+
|
|
76
|
+
execFileSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'pipe' });
|
|
77
|
+
|
|
78
|
+
if (task.enabled) {
|
|
79
|
+
execFileSync('systemctl', ['--user', 'enable', '--now', `soleri-${task.name}.timer`], {
|
|
80
|
+
stdio: 'pipe',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return `soleri-${task.name}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async remove(platformId: string): Promise<void> {
|
|
88
|
+
try {
|
|
89
|
+
execFileSync('systemctl', ['--user', 'disable', '--now', `${platformId}.timer`], {
|
|
90
|
+
stdio: 'pipe',
|
|
91
|
+
});
|
|
92
|
+
} catch {
|
|
93
|
+
// OK — may not exist
|
|
94
|
+
}
|
|
95
|
+
const name = platformId.replace(/^soleri-/, '');
|
|
96
|
+
for (const ext of ['service', 'timer'] as const) {
|
|
97
|
+
const path = unitPath(name, ext);
|
|
98
|
+
if (existsSync(path)) rmSync(path);
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
execFileSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'pipe' });
|
|
102
|
+
} catch {
|
|
103
|
+
// Best-effort
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async exists(platformId: string): Promise<boolean> {
|
|
108
|
+
const name = platformId.replace(/^soleri-/, '');
|
|
109
|
+
return existsSync(unitPath(name, 'timer'));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async pause(platformId: string): Promise<void> {
|
|
113
|
+
execFileSync('systemctl', ['--user', 'disable', `${platformId}.timer`], { stdio: 'pipe' });
|
|
114
|
+
execFileSync('systemctl', ['--user', 'stop', `${platformId}.timer`], { stdio: 'pipe' });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async resume(platformId: string): Promise<void> {
|
|
118
|
+
execFileSync('systemctl', ['--user', 'enable', '--now', `${platformId}.timer`], {
|
|
119
|
+
stdio: 'pipe',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|